自己手写Mybatis通用batchInsert问题

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

自己手写Mybatis通用batchInsert问题

OK_boom   2022-11-19 我要评论

自己手写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)

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们