难题解决:Mycat数据库中间件+Mybatis批量插入数据并返回行记录的所有主键ID

  • Post author:
  • Post category:其他



1、解决过程


一、mybatis的版本必须为3.3.1及其以上

项目所依赖的mybatis的版本必须为3.3.1及其以上,低版本的不行,保证hap项目的依赖的mybatis的jar的版本必需为需要的版本:


二、在Dao层不能使用@Param注解,且Mapper.xml文件中使用list变量接收Dao层中的集合

数据库库结构设计:

对应的Dto层:

对应的Mapper接口:

对应的Mapper.xml文件:

具体代码如下,需要使用到mycat的注解,指定该插入为批量数据插入:

<insert id="insertByBatch">
/*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */
    insert into test_sharding_by_accounting_date (rule_code,name,accounting_date)
    values
    <foreach collection="list" item="item" index="index" separator=",">
        (#{item.ruleCode,jdbcType=DECIMAL},#{item.name,jdbcType=VARCHAR},#{item.accountingDate,jdbcType=DATE})
    </foreach>
</insert>


三、Mycat为分库表做全局序列的配置

schema.xml文件:

server.xml文件的全局序列方式设置为本地读取:

sequence_conf.properties文件的配置:

2、Mybatis的KeyGenerator源码分析

在Mybatis中,在执行insert操作时,如果我们希望返回数据库的自增主键的ID值,那么需要使用到KeyGenerator对象。需要注意的是,KeyGenerator的作用是返回数据库的自增的主键值,而不是去生成数据库的自增主键值,返回的主键值存放在parameter Object的主键属性上的。

KeyGenerator接口的申明如下:

/**
 * @author Clinton Begin
 */
public interface KeyGenerator {

  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

主要就是在insert前、insert后去处理主键的值:

Jdbc3KeyGenerator:用于处理数据库支持自增主键的情况,如MySQL的auto_increment。

NoKeyGenerator:空实现,不需要处理主键。

SelectKeyGenerator:用于处理数据库不支持自增主键的情况,比如Oracle的sequence序列。


原生的jdbc的方式生成主键ID的原理:

lass.forName("com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "123");
conn.setAutoCommit(false);
PreparedStatement pstm = conn.prepareStatement("insert into students(name, email) values(?, ?)",
Statement.RETURN_GENERATED_KEYS);

pstm.setString(1, "name1");
pstm.setString(2, "email1");
pstm.addBatch();
pstm.setString(1, "name2");
pstm.setString(2, "email2");
pstm.addBatch();
pstm.executeBatch();
// 返回自增主键值
ResultSet rs = pstm.getGeneratedKeys();
while (rs.next()) {
		Object value = rs.getObject(1);
		System.out.println(value);
	}
conn.commit();
rs.close();
pstm.close();
conn.close();

output:
246
247


Jdbc3KeyGenerator源码解读:

public class Jdbc3KeyGenerator implements KeyGenerator {
  @Override
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    // do nothing
  }

  @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    processBatch(ms, stmt, getParameters(parameter));
  }

  public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
    ResultSet rs = null;
    try {
      // 获得返回的主键值结果集
      rs = stmt.getGeneratedKeys();
      final Configuration configuration = ms.getConfiguration();
      final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      final String[] keyProperties = ms.getKeyProperties();
      final ResultSetMetaData rsmd = rs.getMetaData();
      TypeHandler<?>[] typeHandlers = null;
      if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
        // 给参数object对象的属性赋主键值(批量插入,可能是多个)
        for (Object parameter : parameters) {
          // there should be one row for each statement (also one for each parameter)
          if (!rs.next()) {
            break;
          }
          final MetaObject metaParam = configuration.newMetaObject(parameter);
          if (typeHandlers == null) {
            typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
          }
          // 赋值
          populateKeys(rs, metaParam, keyProperties, typeHandlers);
        }
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    } finally {
      if (rs != null) {
        try {
          rs.close();
        } catch (Exception e) {
          // ignore
        }
      }
    }
  }
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
    // 主键字段,可能是多个(一般情况下,是一个)
    for (int i = 0; i < keyProperties.length; i++) {
      TypeHandler<?> th = typeHandlers[i];
      if (th != null) {
        Object value = th.getResult(rs, i + 1);
       // 反射赋值
        metaParam.setValue(keyProperties[i], value);
      }
    }
  }
//...


SelectKeyGenerator的原理:

<insert id="insertStudent" parameterType="Student" >
		<selectKey keyProperty="studId" resultType="int" order="BEFORE"> 
			SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL 
		</selectKey>
		INSERT INTO
		STUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)
		VALUES(#{studId}, #{name},
		#{email}, #{dob}, #{phone})
</insert>

在执行insert之前,先发起一个sql查询,将返回的序列值赋值给Student的stuId属性,然后再执行insert操作,这样表中的stud_id字段就有值了。

order=”BEFORE”表示insert前执行,比如取sequence序列值;order=”AFTER”表示insert之后执行;

比如使用触发器给主键stud_id赋值。由于selectKey本身返回单个序列主键值,也就无法支持批量insert操作并返回主键id列表了。如果要执行批量insert,请选择使用for循环执行多次插入操作。

参考地址:

https://my.oschina.net/zudajun/blog/673612



版权声明:本文为jiaqingShareing原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。