spring+ibatis插入操作简析
来源:互联网 发布:java 反射获取运行时 编辑:程序博客网 时间:2024/06/09 15:54
一、常用使用方式
在使用ibatis时,常常看到很多配置sql的xml文件,里面的描述如下所示:
<insert id="insert" parameterClass="MarginCount" > INSERT INTO margincount ( ID ,TRUSTCODE ,STATISTICDATE ,TRANSFERAMOUNT ,REPAYAMOUNT ,CHANNELFEERATE ,ISPUSH ,STATUS ,LASTUPDATE ,CREATETIME)values ( #id# ,#trustCode# ,#statisticDate# ,#transferAmount# ,#repayAmount# ,#channelFeeRate# ,#isPush# ,#status# ,now() ,now())</insert>
这条语句的功能是向数据库表margincount 中插入一条数据。在大多数场景下该语句可以很好的工作,但当后面业务流程需要新记录主键时,基于Ibatis机制,该方法并不能返回主键值。因此我们需要稍微修改下sql语句,如下所示:
<insert id="insert" parameterClass="MarginCount" > INSERT INTO margincount ( ID ,TRUSTCODE ,STATISTICDATE ,TRANSFERAMOUNT ,REPAYAMOUNT ,CHANNELFEERATE ,ISPUSH ,STATUS ,LASTUPDATE ,CREATETIME)values ( #id# ,#trustCode# ,#statisticDate# ,#transferAmount# ,#repayAmount# ,#channelFeeRate# ,#isPush# ,#status# ,now() ,now())<!--修改处--> <selectKey resultClass="long" keyProperty="id"> SELECT LAST_INSERT_ID() AS ID </selectKey> </span></insert>
在加入selectkey语句后,就能在插入记录后返回新纪录主键。
这样使用之后,已经能够解决问题,但是,在会让人思考在并发情况下这样使用会不会有问题。假设两个用户A和B,同时分别插入一条数据,这样返回的主键有可能并非是各自想要主键值。
实际情况中,这种问题并不会出现,因为SELECT LAST_INSERT_ID() 在mysql(本人环境使用的是mysql)中返回的是同一客户端对数据库insert操作后的主键值,所以两个不同用户不会互相影响selectkey语句的查询。
现在业务项目中,ibatis往往与spring集成使用,这些代码在经过多层封装后,开发人员很难理解这里面的实现, ibatis是如何控制一个用户的?上面的sql实际上是两条语句,是一次性提交还是分开提交的?这就是我们接下来需要考虑的。
二、ibatis源码分析
ibatis执行插入语句主要涉及一下几个类:
1) org.springframework.orm.ibatis.SqlMapClientTemplate:spring中用来集成ibatis的SqlMapClient对象,该对象是通过数据库配置文件生成,自动装配到spring中。
2)com.ibatis.sqlmap.engine.impl. SqlMapSessionImpl:是由SqlMapClient创建,每次请求都会创建一个session,即用session来区分是否是一次新的数据库操作。该对象包含SqlMapExecutorDelegate、SessionScope对象。
3) com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate:该对象比较重要,里面封装jdbc的接口,用来实际发送数据库请求。该对象是服务器启动时自动创建,只有一个,所有数据库操作均使用该对象完成。
4) com.ibatis.sqlmap.engine.scope.SessionScope:每个SqlMapSessionImpl中都会有一个SessionScope对象,即每次调用sql ID时都会创建一个SqlMapSessionImpl对象,这个对象会再创建一个SessionScope对象。该对象中包含了事务对象,并记录该sql ID 执行时的大部分信息。
5) com.ibatis.sqlmap.engine.transaction.user.UserProvidedTransaction:事务操作对象,集成在SessionScope中,是在创建Connection时一并创建的。
了解以上五个类的基本功能,下面看源码。SqlMapExecutorDelegate对象的insert方法:
/** * Call an insert statement by ID * * @param sessionScope - the session * @param id - the statement ID * @param param - the parameter object * @return - the generated key (or null) * @throws SQLException - if the insert fails */ public Object insert(SessionScope sessionScope, String id, Object param) throws SQLException { Object generatedKey = null; MappedStatement ms = getMappedStatement(id); Transaction trans = getTransaction(sessionScope); boolean autoStart = trans == null; try { trans = autoStartTransaction(sessionScope, autoStart, trans); SelectKeyStatement selectKeyStatement = null; if (ms instanceof InsertStatement) { selectKeyStatement = ((InsertStatement) ms).getSelectKeyStatement(); } // Here we get the old value for the key property. We'll want it later if for some reason the // insert fails. Object oldKeyValue = null; String keyProperty = null; boolean resetKeyValueOnFailure = false; if (selectKeyStatement != null && !selectKeyStatement.isRunAfterSQL()) { keyProperty = selectKeyStatement.getKeyProperty(); oldKeyValue = PROBE.getObject(param, keyProperty); generatedKey = executeSelectKey(sessionScope, trans, ms, param); resetKeyValueOnFailure = true; } StatementScope statementScope = beginStatementScope(sessionScope, ms); try { //----------------------------insert语句执行 ms.executeUpdate(statementScope, trans, param);</span> }catch (SQLException e){ // uh-oh, the insert failed, so if we set the reset flag earlier, we'll put the old value // back... if(resetKeyValueOnFailure) PROBE.setObject(param, keyProperty, oldKeyValue); // ...and still throw the exception. throw e; } finally { endStatementScope(statementScope); } if (selectKeyStatement != null && selectKeyStatement.isRunAfterSQL()) { <pre name="code" class="java"> //------------------------------select语句执行generatedKey = executeSelectKey(sessionScope, trans, ms, param);</span> } autoCommitTransaction(sessionScope, autoStart); } finally { autoEndTransaction(sessionScope, autoStart); } return generatedKey; }
改函数主要有两个sql执行操作,从名字上就能区分一个执行insert操作,另一个执行查询操作,所以在xml配置的sql语句实际上是分成两步进行执行的,ibatis分别提交。
再来分别看下这条语句执行时是如何保证一个客户端提交的。
executeUpdate中:
public int executeUpdate(StatementScope statementScope, Transaction trans, Object parameterObject) throws SQLException { ErrorContext errorContext = statementScope.getErrorContext(); errorContext.setActivity("preparing the mapped statement for execution"); errorContext.setObjectId(this.getId()); errorContext.setResource(this.getResource()); statementScope.getSession().setCommitRequired(true); try { parameterObject = validateParameter(parameterObject); Sql sql = getSql(); errorContext.setMoreInfo("Check the parameter map."); ParameterMap parameterMap = sql.getParameterMap(statementScope, parameterObject); errorContext.setMoreInfo("Check the result map."); ResultMap resultMap = sql.getResultMap(statementScope, parameterObject); statementScope.setResultMap(resultMap); statementScope.setParameterMap(parameterMap); int rows = 0; errorContext.setMoreInfo("Check the parameter map."); Object[] parameters = parameterMap.getParameterObjectValues(statementScope, parameterObject); errorContext.setMoreInfo("Check the SQL statement."); String sqlString = sql.getSql(statementScope, parameterObject); errorContext.setActivity("executing mapped statement"); errorContext.setMoreInfo("Check the statement or the result map."); //----------------------获取connection对象,实际执行insert语句 rows = sqlExecuteUpdate(statementScope, trans.getConnection(), sqlString, parameters);</span> errorContext.setMoreInfo("Check the output parameters."); if (parameterObject != null) { postProcessParameterObject(statementScope, parameterObject, parameters); } errorContext.reset(); sql.cleanup(statementScope); notifyListeners(); return rows; } catch (SQLException e) { errorContext.setCause(e); throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e); } catch (Exception e) { errorContext.setCause(e); throw new NestedSQLException(errorContext.toString(), e); } }
executeSelectKey的方法中有如下语句electKeyStatement.executeQueryForObject(statementScope, trans, param, null);
该方法的执行过程:
public Object executeQueryForObject(StatementScope statementScope, Transaction trans, Object parameterObject, Object resultObject) throws SQLException { try { Object object = null; DefaultRowHandler rowHandler = new DefaultRowHandler(); //-------------------获取connection对象,实际执行查询语句 executeQueryWithCallback(statementScope, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);</span> List list = rowHandler.getList(); if (list.size() > 1) { throw new SQLException("Error: executeQueryForObject returned too many results."); } else if (list.size() > 0) { object = list.get(0); } return object; } catch (TransactionException e) { throw new NestedSQLException("Error getting Connection from Transaction. Cause: " + e, e); } }
对比两个绿色标记的执行函数,都使用trans.getConnection()获取的是同一个connection对象,即是SqlMapClientTemplate中拿到的,代码如下绿色标记所示:
public <T> T execute(SqlMapClientCallback<T> action) throws DataAccessException {Assert.notNull(action, "Callback object must not be null");Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");// We always need to use a SqlMapSession, as we need to pass a Spring-managed// Connection (potentially transactional) in. This shouldn't be necessary if// we run against a TransactionAwareDataSourceProxy underneath, but unfortunately// we still need it to make iBATIS batch execution work properly: If iBATIS// doesn't recognize an existing transaction, it automatically executes the// batch for every single statement...SqlMapSession session = this.sqlMapClient.openSession();if (logger.isDebugEnabled()) {logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");}Connection ibatisCon = null;try {Connection springCon = null;DataSource dataSource = getDataSource();boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);// Obtain JDBC Connection to operate on...try {ibatisCon = session.getCurrentConnection();if (ibatisCon == null) { //------------------没有session都有一个connection对象 springCon = (transactionAware ?dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));session.setUserConnection(springCon);</span>if (logger.isDebugEnabled()) {logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");}}else {if (logger.isDebugEnabled()) {logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");}}}catch (SQLException ex) {throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);}// Execute given callback...try {return action.doInSqlMapClient(session);}catch (SQLException ex) {throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);}finally {try {if (springCon != null) {if (transactionAware) {springCon.close();}else {DataSourceUtils.doReleaseConnection(springCon, dataSource);}}}catch (Throwable ex) {logger.debug("Could not close JDBC Connection", ex);}}// Processing finished - potentially session still to be closed.}finally {// Only close SqlMapSession if we know we've actually opened it// at the present level.if (ibatisCon == null) {session.close();}}}
因此,虽然是两次提交,但用的是一个connection对象,所以mysql会识别出来,并返回第一节中的正确结果。
结论:
1) 在ibatis 配置文件中的sql语句会分别执行,但使用的是一个connction对象
2)每次选择一个sql id执行时,都会创建一个session
0 0
- spring+ibatis插入操作简析
- Spring+ ibatis批量插入数据
- spring ibatis操作CLOB
- spring ibatis 操作BLOB
- Ibatis + Spring 操作 Clob 详解
- Ibatis + Spring 操作 Blob 详解
- Ibatis + Spring 操作 Blob 详解
- ibatis+spring+dbcp 操作数据源
- Ibatis + Spring 操作 Clob 详解
- iBatis批量插入数据记录操作
- [转]ibatis +spring ,hibernate 批量操作心得
- ibatis+spring操作数据库工具类
- spring配合ibatis操作数据库完整例子
- ibatis 插入操作 对于空值的处理
- ibatis 插入操作 对于空值的处理
- 利用Ibatis执行批量插入更新数据库操作
- Spring+ibatis
- ibatis+spring
- 最长公共子串和最长公共子序列
- JAVA进阶5.6——网格布局管理器
- Android中的异步任务机制
- 多线程杂谈
- android ListView条目的子布局设置android:layout_height="match_parent"无效
- spring+ibatis插入操作简析
- VC/MFC如何添加启动界面
- iOS苹果开发者账号申请流程
- 铺砖问题
- Mini Parser
- Https 是如何工作的?
- HTTP协议的论述
- Emit基本概念介绍
- 设计模式(4)------单态模式