自己手写Mybatis通用batchInsert问题
作者:OK_boom
这篇文章主要介绍了自己手写Mybatis通用batchInsert问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
自己手写Mybatis通用batchInsert
写完才在群里有人告知本来tk mybatis就提供了批量insert的功能,那就放上来做个纪念吧.
先写个数据字典(其实tk mybatis自身也有相应的功能)。
/** * Mybatis 带缓冲功能的数据字典 * Created by rocklee on 2019/8/10 23:27 */ public class MybatisDictionary { private static ConcurrentHashMap<Class<?>, String> entityClass2TableName=new ConcurrentHashMap<>(); private static ConcurrentHashMap<Class<?>, String> entityClass2Columns=new ConcurrentHashMap<>(); private static ConcurrentHashMap<Class<?>, List<String>> entityClass2FieldsList=new ConcurrentHashMap<>(); private static ConcurrentHashMap<Class<?>, Hashtable<Field,String>> entityClass2Fields=new ConcurrentHashMap<>(); public static String getTableName(Class<?> pvEntityClass){ String lvsRet=entityClass2TableName.get(pvEntityClass); if (lvsRet!=null)return lvsRet; Table lvTable= pvEntityClass.getAnnotation(Table.class); if (lvTable==null){ throw new RuntimeException(pvEntityClass.getName()+"不是有效的Entity类,缺少@Table标识"); } lvsRet=lvTable.name(); entityClass2TableName.put(pvEntityClass,lvsRet); return lvsRet; } public static String getColumns(Class<?> pvEntityClass){ String lvsRet=entityClass2Columns.get(pvEntityClass); if (lvsRet!=null)return lvsRet; List<String> lvItems=new ArrayList<>(); for (Field field:pvEntityClass.getDeclaredFields()){ Column lvColumn=field.getAnnotation(Column.class); if (lvColumn==null)continue; lvItems.add(lvColumn.name()); } lvsRet= Util.toString(lvItems,","); entityClass2Columns.put(pvEntityClass,lvsRet); return lvsRet; } public static List<String> getFieldsList(Class<?> pvEntityClass){ List<String> lvRet=entityClass2FieldsList.get(pvEntityClass); if (lvRet!=null)return lvRet; lvRet=new ArrayList<>(); for (Field field:pvEntityClass.getDeclaredFields()){ Column lvColumn=field.getAnnotation(Column.class); if (lvColumn==null)continue; lvRet.add(field.getName()); } entityClass2FieldsList.put(pvEntityClass,lvRet); return lvRet; } public static Hashtable<Field,String>getFields(Class<?> pvEntityClass){ Hashtable<Field,String> lvRet=entityClass2Fields.get(pvEntityClass); if (lvRet!=null)return lvRet; lvRet=new Hashtable<Field,String>(); for (Field field:pvEntityClass.getDeclaredFields()){ Column lvColumn=field.getAnnotation(Column.class); if (lvColumn==null)continue; lvRet.put(field,lvColumn.name()); } entityClass2Fields.put(pvEntityClass,lvRet); return lvRet; } }
再写SqlProvider:
/** 批处理SQL处理器 * Created by rocklee on 2019/8/10 23:11 */ public class BatchSqlProvider extends MapperTemplate { public BatchSqlProvider(Class<?> mapperClass, MapperHelper mapperHelper) { super(mapperClass, mapperHelper); } public String batchInsert(MappedStatement ms) { final Class<?> entityClass = getEntityClass(ms); StringBuilder sb=new StringBuilder(); sb.append("INSERT INTO "+MybatisDictionary.getTableName(entityClass)); sb.append("\n<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">\n"); sb.append(MybatisDictionary.getColumns(entityClass)); sb.append("</trim> \n"); sb.append("\nVALUES\n"); sb.append("<foreach collection=\"list\" item=\"record\" separator=\",\">"); sb.append("\n"); sb.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">\n"); String lvsValFields="#{record."+ Util.toString(MybatisDictionary.getFieldsList(entityClass),"},#{record.") +"}"; sb.append(lvsValFields); sb.append("\n</trim>\n</foreach>"); return sb.toString() ; } }
Mapper:
/** * Created by rocklee on 2019/8/11 0:24 */ @RegisterMapper public interface BatchMapper<T> { /*** * 批量插入 * @param pvToInsertList * @return */ @InsertProvider(type = BatchSqlProvider.class, method = "dynamicSQL") int batchInsert(List<? extends T> pvToInsertList); }
这是基于tk的MapperTemplate 写的sqlprovider,传入的是MappedStatement,这时候返回的SQL不是raw SQL,还能支持<if>,<foreach>这些mybatis表达式, 而如果用与mapper接口相同的参数方式返回sql,那这些表达式则不会被mybatis解释,而直接传到database服务器那边, 导致异常。
最后要提一下, tkmybatis带自了一个insertListMapper,我们extends它就可以实现批量insert了:
使用Mapper通用insert方法遇到的问题
环境
- spingboot
- sqlserver
- mybatis
- Mapper
insert抛出不能为标识列插入显式值的异常
原因:表的自增主键,通常情况下不需要直接在insert语句中指定设值。查看控制台打印语句,发现是对该字段做了插入。
解决方法:该表对应实体中,在不需要做插入的字段上增加@Column(insertable = false)。
延伸:@Column还有一些值可供设值,
name
被标注字段在数据库表中所对应字段的名称;unique
表示该字段是否为唯一标识,默认为false;nullable
表示该字段是否可以为null值,默认为true;insertable
表示在使用“INSERT”脚本插入数据时,是否需要插入该字段的值。updatable
表示在使用“UPDATE”脚本插入数据时,是否需要更新该字段的值。columnDefinition
表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时使用。table
定义了包含当前字段的表名。length
表示字段的长度,当字段的类型为varchar时,该属性才有效,默认为255个字符。precision
属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。insert
不能返回自增id的问题
这个问题,用过给自增id加 @GeneratedValue(generator =’“JDBC”)等,无效;
只能先不用Mapper通用的方法,自己定义一个insert接口,在xml中设置usergeneratedkeys=true ,keyProperty=“id” 这样会把自增得到的id值注入到实体id参数中。
(该问题待解决,有知道的朋友指点下,注意数据库是SqlServer)
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。