网站屏蔽省份,做推广网站有什么,做网站费用多少钱,嵌入式软件开发面试1.目的
这一章的目的主要是插入语句以后返回插入记录的id#xff0c;因为插入语句可分为要返回记录id#xff0c;不要返回记录id的以及不同数据源类型执行的时机也不同#xff08;如#xff1a;oracle不支持主键#xff0c;需要先插入序列再增加#xff0c;Mysql支持主键…1.目的
这一章的目的主要是插入语句以后返回插入记录的id因为插入语句可分为要返回记录id不要返回记录id的以及不同数据源类型执行的时机也不同如oracle不支持主键需要先插入序列再增加Mysql支持主键增加一条记录就会有索引。
如下图insert里包含selectKey由selectKey去执行查询此次新增的id记录我们看到selectKey标签上的属性有keyProperty、order、resultType。
keyProperty这个是把返回索引的索引放入id里order为after则是在insert后执行selectKeyresultType则是查询返回的类型示例里为Long
我们最主要的目的就是解析这样的selectKey然后存入MappedStatement里到时执行时使用执行Update时则执行selectKey的方法最后在activity实体id赋值执行完的记录id。 2.设计说明
标黄色的是修改的其余都是新增的方法。 1.构建时builder
在构建builder时我们需要解析下selectKey的标签而selectKey是在语句里所以需要更改XMLStatementBuilder类添加解析selectKey的标签。最后把解析好的标签放入到addMappedStatement()这里的动作是两次addMappedStatement()为什么是两次呢ps:可以看下面两个图。
第一次SqlCommandType是“SELECT”代表selectKey本身的语句需要存储一次MappedStatement第二次SqlCommandType是“INSERT”代表是selectKey父级的INSERT标签它下面要包含这个selectKey的MappedStatement信息然后如果有selectKey则创建SelectKeyGenerator对象需要再一次存储MappedStatement。
ps此处看不懂可以调试全局看下多看几遍就懂了为什么这样设计了。 2.selectKey执行时executor keygen
2.1 定义接口
针对上述情况需要定义接口KeyGenerator定义方法为processBefore()针对Oracle不支持主键的和processAfter()支持主键的如MySql
2.2 实现接口
NoKenGenerator和SelectKeyGenerator不要求返回记录的就执行NoKenGenerator实现方法什么都不操作。SelectKeyGenerator的实现则是执行查询索引操作并将结果添加到insert的操作的实体里这样就得到了索引。 3.执行时executor
我们存储到MappedStatement以后就要流转到执行时因为SelectKey是查询所以在Executor时
添加了重载的query()方法。
BaseExecutor实现新的query()方法拿到了sql语句后调用原有的doQuery由SimpleExecutor类实现此时就会调用BaseStatementHandler构造方法这里新添加了generateKeys()方法主要是检查下是否需要在insert前执行selectKey如Mysql是insert后Oracle是insert前。
执行了新增方法时则需要添加调用KeyGenerator的processAfter()方法这个方法依然要检查是否在insert后执行selectKey。 4.结果集的更改(resultset)
由于之前的结果集是只处理bean对象 一般返回selectKey需要的基本类型如ID是Long所以这里需要添加createPrimitiveResultObject()方法获取类型处理器。
在createResultObject()此方法里则需要判断是否是基本类型如果基本类型则调用createPrimitiveResultObject()。其余类型则还按之前代码走。 5.connection连接的更改
由于要两个语句用一个连接如果两个连接则无法返回id索引所以需要在获取连接处判断下是否有连接如果有就不创建没有就创建。
3.代码
3.1 selectKey执行操作
包package cn.bugstack.mybatis.executor.keygen;
添加接口KeyGenerator主要是用来执行SelectKey查询操作的此接口定义了两个方法
processBefore是在不支持主键需要获取序列时调用执行insert语句前调用
processAfter是在支持主键在执行insert语句后嗲用
public interface KeyGenerator {/*** 针对Sequence主键而言在执行insert sql前必须指定一个主键值给要插入的记录* 如Oracle、DB2KeyGenerator提供了processBefore()方法。*/void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);/*** 针对自增主键的表在插入时不需要主键而是在插入过程自动获取一个自增的主键* 比如MySQL、PostgreSQLKeyGenerator提供了processAfter()方法*/void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}实现类SelectKeyGenera有selectKey时实现执行selectKey的逻辑。
// step13新增
public class SelectKeyGenerator implements KeyGenerator {public static final String SELECT_KEY_SUFFIX !selectKey;private boolean executeBefore;private MappedStatement keyStatement;public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {this.executeBefore executeBefore;this.keyStatement keyStatement;}Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {if (executeBefore) {processGeneratedKeys(executor, ms, parameter);}}Overridepublic void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {if (!executeBefore) {processGeneratedKeys(executor, ms, parameter);}}/*** 执行selectKey的SQL语句执行完毕后把返回的id结果放入对应实体中。*/private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {try {if (parameter ! null keyStatement ! null keyStatement.getKeyProperties() ! null) {String[] keyProperties keyStatement.getKeyProperties();final Configuration configuration ms.getConfiguration();final MetaObject metaParam configuration.newMetaObject(parameter);if (keyProperties ! null) {Executor keyExecutor configuration.newExecutor(executor.getTransaction());ListObject values keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);if (values.size() 0) {throw new RuntimeException(SelectKey returned no data.);} else if (values.size() 1) {throw new RuntimeException(SelectKey returned more than one value.);} else {MetaObject metaResult configuration.newMetaObject(values.get(0));if (keyProperties.length 1) {if (metaResult.hasGetter(keyProperties[0])) {setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));} else {setValue(metaParam, keyProperties[0], values.get(0));}} else {handleMultipleProperties(keyProperties, metaParam, metaResult);}}}}} catch (Exception e) {throw new RuntimeException(Error selecting key or setting result to parameter object. Cause: e);}}private void handleMultipleProperties(String[] keyProperties,MetaObject metaParam, MetaObject metaResult) {String[] keyColumns keyStatement.getKeyColumns();if (keyColumns null || keyColumns.length 0) {for (String keyProperty : keyProperties) {setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));}} else {if (keyColumns.length ! keyProperties.length) {throw new RuntimeException(If SelectKey has key columns, the number must match the number of key properties.);}for (int i 0; i keyProperties.length; i) {setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));}}}private void setValue(MetaObject metaParam, String property, Object value) {if (metaParam.hasSetter(property)) {metaParam.setValue(property, value);} else {throw new RuntimeException(No setter found for the keyProperty property in metaParam.getOriginalObject().getClass().getName() .);}}
}
实现类NoKeyGenerator在没有selectKey时的逻辑也就是不写具体的逻辑。
public class NoKeyGenerator implements KeyGenerator {Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// Do Nothing}Overridepublic void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// Do Nothing}
}
3.2 builder操作部分的修改
XMLStatementBuilder修改parseStatementNode()添加了解析selectKey的节点方法processSelectKeyNodes()
如果有多个就遍历每个节点信息存储到MappedStatement下
public class XMLStatementBuilder extends BaseBuilder {// 省略其他public void parseStatementNode() {// 省略其他// 解析selectKey step-13 新增processSelectKeyNodes(id, parameterTypeClass, langDriver);}/*** 解析selectKey标签*/private void processSelectKeyNodes(String id, Class? parameterTypeClass, LanguageDriver langDriver) {// 得到selectKey标签ListElement selectKeyNodes element.elements(selectKey);parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver);}/*** for循环遍历selectKey标签* 取出selectKey的父id拼接select的Key标识*/private void parseSelectKeyNodes(String parentId, ListElement list, Class? parameterTypeClass, LanguageDriver langDriver) {for (Element nodeToHandle : list) {String id parentId SelectKeyGenerator.SELECT_KEY_SUFFIX;parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver);}}/*** 开始解析每一个selectKey标签的内容,并存储MappedStatement里* selectKey keyPropertyid orderAFTER resultTypelong* SELECT LAST_INSERT_ID()* /selectKey*/private void parseSelectKeyNode(String id, Element nodeToHandle, Class? parameterTypeClass, LanguageDriver langDriver) {String resultType nodeToHandle.attributeValue(resultType);// 得到resultType类型Class? resultTypeClass resolveClass(resultType);// 执行前还是执行后boolean executeBefore BEFORE.equals(nodeToHandle.attributeValue(order, AFTER));String keyProperty nodeToHandle.attributeValue(keyProperty);// defaultString resultMap null;KeyGenerator keyGenerator new NoKeyGenerator();// 解析成SqlSourceDynamicSqlSource/RawSqlSourceSqlSource sqlSource langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);// SELECT标签SqlCommandType sqlCommandType SqlCommandType.SELECT;// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,keyGenerator,keyProperty,langDriver);// 给id加上namespace前缀id builderAssistant.applyCurrentNamespace(id, false);// 存放键值生成器配置MappedStatement keyStatement configuration.getMappedStatement(id);// 将KeyGenerator放入map中configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));}
}
MapperAnnotationBuilder类注解版的构建也是一样的需要修改解析的语句方法为parseStatement()
public class MapperAnnotationBuilder {private void parseStatement(Method method) {// 省略其他// step-13 新增-----------------------------------------------------------KeyGenerator keyGenerator;String keyProperty id;if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {keyGenerator configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator new NoKeyGenerator();}// step-14 新增-----------------------------------------------------------boolean isSelect sqlCommandType SqlCommandType.SELECT;String resultMapId null;if (isSelect) {resultMapId parseResultMap(method);}// step-13 部分参数新增( keyGenerator,keyProperty,)-------------------------// 调用助手类assistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,parameterTypeClass,resultMapId,getReturnType(method),keyGenerator,keyProperty,languageDriver);}
}
因为MappedStatement要存储数据所以MappedStatement要加相应的字段keyGenerator以及keyProperties和keyColumns
public class MappedStatement {// 其余省略// step-13 新增private String resource;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;public static class Builder {public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class? resultType) {mappedStatement.keyGenerator configuration.isUseGeneratedKeys() SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();}public Builder resource(String resource) {mappedStatement.resource resource;return this;}public Builder keyGenerator(KeyGenerator keyGenerator) {mappedStatement.keyGenerator keyGenerator;return this;}public Builder keyProperty(String keyProperty) {mappedStatement.keyProperties delimitedStringToArray(keyProperty);return this;}}public String[] getKeyColumns() {return keyColumns;}public String[] getKeyProperties() {return keyProperties;}public KeyGenerator getKeyGenerator() {return keyGenerator;}
}
MapperBuilderAssistant类此类修改了addMappedStatement()方法需要增加几个参数并把参数放入MappedStatement中。 public MappedStatement addMappedStatement(String id,SqlSource sqlSource,SqlCommandType sqlCommandType,Class? parameterType,String resultMap,Class? resultType,KeyGenerator keyGenerator,String keyProperty,LanguageDriver lang) {// 给id加上namespace前缀cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoByIdid applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);// step-13新增/添加三个属性statementBuilder.resource(resource);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);// 结果映射给 MappedStatement#resultMapssetStatementResultMap(resultMap, resultType, statementBuilder);MappedStatement statement statementBuilder.build();// 映射语句信息建造完存放到配置项中configuration.addMappedStatement(statement);return statement;}3,3 insert执行SQL部分修改
包package cn.bugstack.mybatis.executor
Executor接口需要新增个query方法SelectKeyGenerator执行查询时使用。 // step-13新增-----------------------------------E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
BaseExecutor基础实现类去实现这个query方法然后调用原有的其他的query方法然后顺着会调用SimpleExecutor的doQuery()这里的处理没有改变所以就不提供代码拉。这块就和查询逻辑一样。 // step-14新增-----------------------------------------Overridepublic E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql ms.getBoundSql(parameter);return query(ms, parameter, rowBounds, resultHandler, boundSql);}在上面SimpleExecutor执行doQuery()时需要调用到BaseStatementHandler的构造方法此时就需要检查一下是否需要在insert前要执行如果需要就执行SelectKeyselectKey又会调用上边的query进行查询操作如果否则不会执行任何操作判断则在keyGenerator.processBefore()处理。
public abstract class BaseStatementHandler implements StatementHandler {public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {if (boundSql null) {// 针对Sequence主键而言在执行insert sql前必须指定一个主键值给要插入的记录。generateKeys(parameterObject);}}// 针对Sequence主键而言在执行insert sql前必须指定一个主键值给要插入的记录。KeyGenerator#processBeforeprotected void generateKeys(Object parameter) {KeyGenerator keyGenerator mappedStatement.getKeyGenerator();keyGenerator.processBefore(executor, mappedStatement, null, parameter);}
}
然后就要修改下执行insert语句了在Mybatis中所有的insert、del、update都统一为update方法所以我们需要修改下update()修改为执行完insert操作后调用keyGenerator.processAfter()后置处理有主键情况下使用修改如下
PreparedStatementHandler类
包package cn.bugstack.mybatis.executor.statement; Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps (PreparedStatement) statement;ps.execute();// step-13新增----------------------------------------// 执行 selectKey 语句int rows ps.getUpdateCount();Object parameterObject boundSql.getParameterObject();KeyGenerator keyGenerator mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);// step-13新增----------------------------------------return rows;}
最后最重要的是执行insert再执行查询当前插入记录的主键必须是一个连接如果是两个连接结果就是空所以需要更改获取连接的操作这里很简单判断下就行了。
修改的包cn.bugstack.mybatis.transaction.jdbc
修改的类JdbcTransaction Overridepublic Connection getConnection() throws SQLException {// step-13新增------------------------------------// 本章节新增多个SQL在同一个JDBC连接下才能完成事务特性if (connection ! null) {return connection;}// step-13新增------------------------------------connection dataSource.getConnection();connection.setTransactionIsolation(level.getLevel());connection.setAutoCommit(autoCommit);return connection;}4.准备测试
xml如下 insert idinsert parameterTypecn.bugstack.mybatis.test.po.ActivityINSERT INTO activity(activity_id, activity_name, activity_desc, create_time, update_time)VALUES (#{activityId}, #{activityName}, #{activityDesc}, now(), now())selectKey keyPropertyid orderAFTER resultTypelongSELECT LAST_INSERT_ID()/selectKey/insert
单元测试如下 private Logger logger LoggerFactory.getLogger(ApiTest.class);private SqlSession sqlSession;Beforepublic void init() throws IOException {// 1. 从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(mybatis-config-datasource.xml));sqlSession sqlSessionFactory.openSession();}Testpublic void test_insert() {// 1. 获取映射器对象IActivityDao dao sqlSession.getMapper(IActivityDao.class);Activity activity new Activity();activity.setActivityId(10004L);activity.setActivityName(测试活动);activity.setActivityDesc(测试数据插入);activity.setCreator(xdf);// 2. 测试验证Integer res dao.insert(activity);sqlSession.commit();logger.info(测试结果count{} idx{}, res, JSON.toJSONString(activity.getId()));}
执行结果 执行的两条都展示了正确的索引。