ToB企服应用市场:ToB评测及商务社交产业平台

标题: mybatis SelectKey解析 [打印本页]

作者: 火影    时间: 2022-8-27 18:41
标题: mybatis SelectKey解析
1.selectKey介绍及作用 

标签有如下属性 
resultType:sql返回的java类型
statementType:STATEMENT|PREPARED|CALLABLE三种默认PREPARED
keyProperty:列名对应的java属性名,可逗号分隔
keyColumn:列名,可逗号分隔
order:BEFORE|AFTER,BEFORE表示里的sql先执行然后再把获取到的值进行设置,AFTER则表示后执行,获取自增主键并设置肯定是需要用AFTER的,毕竟先等主sql插入才能获取到自增Id~
databaseId:数据库Id一般不需要填
mybatis的标签主要可以用来获取自增主键id的值并进行设置,SELECT LAST_INSERT_ID() 该sql的作用返回最近一次插入的id通常用来配合标签来使用 ,但要注意假如用insert同时插入多条sql,其只能返回插入的第一条记录的自增主键id因此是不支持批量插入获取主键值的
 
2.selectKey测试及解析

测试代码
  1. #mapper
  2. int insert(UserDO userDO);
  3. #mapper.xml
  4.   <insert id="insert">
  5.      <selectKey keyProperty="userId" keyColumn="user_id" order="AFTER" resultType="integer">
  6.        select last_insert_id()
  7.      </selectKey>
  8.      insert into user(username, password, nickname)
  9.      values(#{username}, #{password}, #{nickname})
  10.   </insert>
  11.   
  12. #java测试代码
  13. public class Test {
  14.   public static void main(String[] args) throws IOException {
  15.     try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
  16.       // 构建session工厂 DefaultSqlSessionFactory
  17.       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  18.       SqlSession sqlSession = sqlSessionFactory.openSession();
  19.       UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  20.       UserDO userDO = new UserDO();
  21.       userDO.setUsername("monian");
  22.       userDO.setPassword("123");
  23.       userDO.setNickname("monianx");
  24.       userMapper.insert(userDO);
  25.       System.out.println("自增主键userId:" + userDO.getUserId());
  26.     }
  27.   }
  28. }
复制代码

从输出可以看到成功获取到自增主键userId并已经设置到userDO参数对象中了,下面来看看具体解析
  1. public class PreparedStatementHandler extends BaseStatementHandler {
  2.   public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  3.     super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  4.   }
  5.   @Override
  6.   public int update(Statement statement) throws SQLException {
  7.     PreparedStatement ps = (PreparedStatement) statement;
  8.     ps.execute();
  9.     int rows = ps.getUpdateCount();
  10.     Object parameterObject = boundSql.getParameterObject();
  11.     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  12.     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  13.     return rows;
  14.   }
  15. }
复制代码
当为标签时会调用此update方法,执行完sql后调用 keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  而根据标签解析出来的keyGenerator为SelectKeyGenerator,下面具体分析下这个类它是怎么获取主键值并设置的。
  1. public class SelectKeyGenerator implements KeyGenerator {
  2.   public static final String SELECT_KEY_SUFFIX = "!selectKey";
  3.   private final boolean executeBefore;
  4.   private final MappedStatement keyStatement;
  5.   public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
  6.     // 主sql前面执行还是后面执行
  7.     this.executeBefore = executeBefore;
  8.     this.keyStatement = keyStatement;
  9.   }
  10.   @Override
  11.   public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  12.     if (executeBefore) {
  13.       processGeneratedKeys(executor, ms, parameter);
  14.     }
  15.   }
  16.   @Override
  17.   public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  18.     if (!executeBefore) {
  19.       processGeneratedKeys(executor, ms, parameter);
  20.     }
  21.   }
  22.   // 处理生成的键
  23.   private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
  24.     try {
  25.       if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
  26.         // 获取需要设置的属性值 如id
  27.         String[] keyProperties = keyStatement.getKeyProperties();
  28.         final Configuration configuration = ms.getConfiguration();
  29.         final MetaObject metaParam = configuration.newMetaObject(parameter);
  30.         // Do not close keyExecutor.
  31.         // The transaction will be closed by parent executor.
  32.         Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
  33.         // 查询sql如 select last_insert_id()获取主键id
  34.         List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
  35.         if (values.size() == 0) {
  36.           throw new ExecutorException("SelectKey returned no data.");
  37.         } else if (values.size() > 1) {
  38.           throw new ExecutorException("SelectKey returned more than one value.");
  39.         } else {
  40.           MetaObject metaResult = configuration.newMetaObject(values.get(0));
  41.           // 将主键id的值设置到parameter参数中
  42.           if (keyProperties.length == 1) {
  43.             if (metaResult.hasGetter(keyProperties[0])) {
  44.               setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
  45.             } else {
  46.               // no getter for the property - maybe just a single value object
  47.               // so try that
  48.               setValue(metaParam, keyProperties[0], values.get(0));
  49.             }
  50.           } else {
  51.             // 若查询的属性有多个则分别设置
  52.             handleMultipleProperties(keyProperties, metaParam, metaResult);
  53.           }
  54.         }
  55.       }
  56.     } catch (ExecutorException e) {
  57.       throw e;
  58.     } catch (Exception e) {
  59.       throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
  60.     }
  61.   }
  62.   private void handleMultipleProperties(String[] keyProperties,
  63.       MetaObject metaParam, MetaObject metaResult) {
  64.     String[] keyColumns = keyStatement.getKeyColumns();
  65.     if (keyColumns == null || keyColumns.length == 0) {
  66.       // no key columns specified, just use the property names
  67.       for (String keyProperty : keyProperties) {
  68.         setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
  69.       }
  70.     } else {
  71.       if (keyColumns.length != keyProperties.length) {
  72.         throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
  73.       }
  74.       for (int i = 0; i < keyProperties.length; i++) {
  75.         setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
  76.       }
  77.     }
  78.   }
  79.   private void setValue(MetaObject metaParam, String property, Object value) {
  80.     if (metaParam.hasSetter(property)) {
  81.       metaParam.setValue(property, value);
  82.     } else {
  83.       throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
  84.     }
  85.   }
  86. }
复制代码
可以看到上述代码会先查询sql获取返回结果之后再把值设置到参数对象中,但可以看到当查询结果value.size() > 1的时候就会抛出异常,因此标签中的sql返回行数不能大于1。从这边也能看出不支持批量获取主键值
3. useGeneratedKeys 

那么有什么办法可以获取到批量插入的主键id呢,答案是有的可以使用标签中的useGeneratedKeys、keyProperty、keyColumn属性进行设置 
  1. #mapper
  2. void batchInsert(@Param("userDOList") List<UserDO> userDOList);
  3. #mapper.xml
  4.     insert into user(username, password, nickname)
  5.     values
  6.     <foreach collection="userDOList" item="userDO" separator=",">
  7.       (#{userDO.username}, #{userDO.password}, #{userDO.nickname})
  8.     </foreach>
  9.   </insert>
  10.   
  11. #java测试代码
  12. public class Test {
  13.   public static void main(String[] args) throws IOException {
  14.     try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
  15.       // 构建session工厂 DefaultSqlSessionFactory
  16.       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  17.       SqlSession sqlSession = sqlSessionFactory.openSession();
  18.       UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  19.       UserDO userDO = new UserDO();
  20.       userDO.setUsername("monian");
  21.       userDO.setPassword("123");
  22.       userDO.setNickname("monianx");
  23.       UserDO userDO1 = new UserDO();
  24.       userDO1.setUsername("monian");
  25.       userDO1.setPassword("123");
  26.       userDO1.setNickname("monianx");
  27.       userMapper.batchInsert(Arrays.asList(userDO, userDO1));
  28.       System.out.println("自增主键userId:" + Arrays.asList(userDO.getUserId(), userDO1.getUserId()));
  29.     }
  30.   }
  31. }
复制代码
输出结果可以看到批量插入成功获取到主键userId的值了,原理的话感兴趣的同学可以去阅读下Jdbc3KeyGenerator这个类的源码,这里就不细说啦

 
4.selectKey和useGeneratedKeys

最后谈谈笔者对这两个的理解,selectKey可以自定义查询的sql更加的灵活不单单只是获取自增主键但查询结果行数不能有多行否则会抛出异常,而useGeneratorKeys主要是获取自动生成主键且能支持多行支持批量插入获取主键值,至于在实际开发中使用哪一种就看业务需求是怎样的了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4