ibatis源码分析
背景:调试模式下,单步运行一个查询订单协议操作,记录了ibatis框架的执行动作,侧面剖析其原理。
一、简介:
1. dal 层的dao接口实现类通常会继承SqlMapClientDaoSupport。spring容器在初始化一个dao bean实例时,通常会注入两块信息DataSource(数据源)和sqlMapClient(主要是sql语句),这两块信息会封装到SqlMapClientTemplate
2. 其中数据源的实例通常采用apache的开源项目dbcp
代码配置如下:
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="driverClassName" value="com.mysql.jdbc.Driver" />
- <property name="url" value="xxxx" />
- <property name="username"><value>xxxx</value></property>
- <property name="password"><value>xxxxx</value></property>
- <property name="maxActive"><value>20</value></property>
- <property name="initialSize"><value>1</value></property>
- <property name="maxWait"><value>60000</value></property>
- <property name="maxIdle"><value>20</value></property>
- <property name="minIdle"><value>3</value></property>
- <property name="removeAbandoned"><value>true</value></property>
- <property name="removeAbandonedTimeout"><value>180</value></property>
- <property name="connectionProperties"><value>clientEncoding=GBK</value></property>
- </bean>
各配置参数的具体含义可参照:dbcp基本配置和重连配置
3. sqlMapClient
- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="configLocation">
- <value>classpath:sqlmap.xml</value>
- </property>
- </bean>
- <bean id="sqlMapClientTddl" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="dataSource" ref="tGroupDataSource" />
- <property name="configLocation" value="classpath:sqlmap.xml"/>
- </bean>
sqlMapClient,主要是借助于实现FactoryBean和InitializingBean两个接口,加载sql.xml文件资源信息,得到sqlMapClient组件
注:上面的sqlMapClient默认不配置数据源,后面的SqlMapClientTemplate优先从全局变量中取,如果没有再从sqlMapClient中查找。
- public DataSource getDataSource() {
- DataSource ds = super.getDataSource();
- return (ds != null ? ds : this.sqlMapClient.getDataSource());
- }
构造sqlMapClient组件的代码块。
- public void afterPropertiesSet() throws Exception {
- if (this.lobHandler != null) {
- // Make given LobHandler available for SqlMapClient configuration.
- // Do early because because mapping resource might refer to custom types.
- configTimeLobHandlerHolder.set(this.lobHandler);
- }
- try {
- this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
- // Tell the SqlMapClient to use the given DataSource, if any.
- if (this.dataSource != null) {
- TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
- DataSource dataSourceToUse = this.dataSource;
- if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
- dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
- }
- transactionConfig.setDataSource(dataSourceToUse);
- transactionConfig.initialize(this.transactionConfigProperties);
- applyTransactionConfig(this.sqlMapClient, transactionConfig);
- }
- }
- finally {
- if (this.lobHandler != null) {
- // Reset LobHandler holder.
- configTimeLobHandlerHolder.set(null);
- }
- }
- }
4. SqlMapExecutor
该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法
5. SqlMapTransactionManager
该接口是对事务行为的抽象,提供了事务执行过程中涉及的所有方法。
6. SqlMapClient
该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行和事务处理的能力,该接口的核心实现类是SqlMapClientImpl。
7. SqlMapSession
该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl
8. MappedStatement
该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。
9. ParameterMap/ResultMap
该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。
整体类图:
二、具体调用
接下来就到了数据持久层的代码调用,所有的数据库DML操作(增、删、改、查)都是借助于SqlMapClientTemplate来实现的。
- public OrderEnsureProtocolDO getOrderEnsureProtocolByOrderId(Long orderId) {
- if (orderId == null) {
- return null;
- }
- return (OrderEnsureProtocolDO) this.getSqlMapClientTemplate().queryForObject("MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID",
- orderId);
- }
如果一次执行的sql较多,我们会采用批处理的形式
- public void batchDeleteOfferSaleRecord(final List<Long> orderEntryIds) throws Exception {
- if (orderEntryIds == null || orderEntryIds.size() < 1 || orderEntryIds.size() > 50) {
- return;
- }
- this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
- @Override
- public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
- executor.startBatch();
- for (Long entryId : orderEntryIds) {
- executor.insert(MS_DELETE_SALE_RECORD, entryId);
- }
- return executor.executeBatch();
- }
- });
- }
不管采用上面哪种方式,查看源代码会发现,最后都是在调用execute(SqlMapClientCallback action)方法
- public Object queryForObject(final String statementName, final Object parameterObject)
- throws DataAccessException {
- return execute(new SqlMapClientCallback() {
- public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
- return executor.queryForObject(statementName, parameterObject);
- }
- });
- }
- public Object execute(SqlMapClientCallback action) throws DataAccessException {
- Assert.notNull(action, "Callback object must not be null");
- Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");
- //获取session信息(SqlMapSessionImpl实例)
- 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);
- try {
- //获取数据获连接
- ibatisCon = session.getCurrentConnection();
- if (ibatisCon == null) {
- springCon = (transactionAware ?
- dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
- //将数据源set到session会话中
- session.setUserConnection(springCon);
- 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);
- }
- try {
- //执行SQL
- return action.doInSqlMapClient(session);
- }
- catch (SQLException ex) {
- throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
- }
- finally {
- 省略。。。一系列的关闭工作
- }
- }
SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理(里面注入了很多功能对象,负责具体的sql执行)
- public Object queryForObject(String id, Object paramObject) throws SQLException {
- return delegate.queryForObject(session, id, paramObject);
- }
经过N层重载,最后调用内部的通用方法
- 入参:
- id=MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID
- paramObject=26749329
- public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject) throws SQLException {
- Object object = null;
- //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成
- //调试中的ms为SelectStatement,具体的执行器
- MappedStatement ms = getMappedStatement(id);
- // 用于事务执行
- Transaction trans = getTransaction(session);
- boolean autoStart = trans == null;
- try {
- trans = autoStartTransaction(session, autoStart, trans);
- // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope
- RequestScope request = popRequest(session, ms);
- try {
- // 执行sql
- object = ms.executeQueryForObject(request, trans, paramObject, resultObject);
- } finally {
- pushRequest(request); //归还RequestScope
- }
- autoCommitTransaction(session, autoStart);
- } finally {
- autoEndTransaction(session, autoStart);
- }
- return object;
- }
接下来由MappedStatement.executeQueryForObject()来执行
- public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)
- throws SQLException {
- try {
- Object object = null;
- DefaultRowHandler rowHandler = new DefaultRowHandler();
- //执行sql语句
- executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);
- //结果处理,返回结果
- 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);
- }
- 。。。。。。。。。
- }
MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程
- protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)
- throws SQLException {
- //预先封装错误信息,如果报错时便于排查问题
- ErrorContext errorContext = request.getErrorContext();
- errorContext.setActivity("preparing the mapped statement for execution");
- errorContext.setObjectId(this.getId());
- errorContext.setResource(this.getResource());
- try {
- //验证入参
- parameterObject = validateParameter(parameterObject);
- //获取SQL对象
- Sql sql = getSql();
- errorContext.setMoreInfo("Check the parameter map.");
- // 入参映射
- ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);
- errorContext.setMoreInfo("Check the result map.");
- //获取结果
- ResultMap resultMap = sql.getResultMap(request, parameterObject);
- request.setResultMap(resultMap);
- request.setParameterMap(parameterMap);
- errorContext.setMoreInfo("Check the parameter map.");
- //获取参数值
- Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject);
- errorContext.setMoreInfo("Check the SQL statement.");
- //获取拼装后的sql语句
- String sqlString = sql.getSql(request, parameterObject);
- errorContext.setActivity("executing mapped statement");
- errorContext.setMoreInfo("Check the SQL statement or the result map.");
- RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
- //sql执行
- sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);
- ....省略
- }
最后调用com.ibatis.sqlmap.engine.execution.SqlExecutor.executeQuery(RequestScope, Connection, String, Object[], int, int, RowHandlerCallback)
- public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
- ...省略
- PreparedStatement ps = null;
- ResultSet rs = null;
- setupResultObjectFactory(request);
- try {
- errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
- Integer rsType = request.getStatement().getResultSetType();
- //初始化PreparedStatement,设置sql、参数值等
- if (rsType != null) {
- ps = prepareStatement(request.getSession(), conn, sql, rsType);
- } else {
- ps = prepareStatement(request.getSession(), conn, sql);
- }
- setStatementTimeout(request.getStatement(), ps);
- Integer fetchSize = request.getStatement().getFetchSize();
- if (fetchSize != null) {
- ps.setFetchSize(fetchSize.intValue());
- }
- errorContext.setMoreInfo("Check the parameters (set parameters failed).");
- request.getParameterMap().setParameters(request, ps, parameters);
- errorContext.setMoreInfo("Check the statement (query failed).");
- //执行
- ps.execute();
- errorContext.setMoreInfo("Check the results (failed to retrieve results).");
- //结果集处理
- rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);
- 。。。省略
- }
ibatis源码分析的更多相关文章
- 【异常及源码分析】org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping
一.异常出现的场景 1)异常出现的SQL @Select("SELECT\n" + " id,discount_type ,min_charge, ${cardFee} ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(5)——内置DataSource实现
@(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...
- MyBatis源码分析(4)—— Cache构建以及应用
@(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...
- 《深入理解Spark:核心思想与源码分析》(前言及第1章)
自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...
- 《深入理解Spark:核心思想与源码分析》一书正式出版上市
自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...
- 《深入理解Spark:核心思想与源码分析》正式出版上市
自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...
- MyBatis源码分析(1)-MapConfig文件的解析
1.简述 MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下: String re ...
- Mybatis源码分析-BaseExecutor
根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...
- 【MyBatis源码分析】Configuration加载(下篇)
元素设置 继续MyBatis的Configuration加载源码分析: private void parseConfiguration(XNode root) { try { Properties s ...
随机推荐
- 【漏洞分析】OSN 代币攻击事件:一笔资金伪造多个分红大户
背景 OSN 是一种 fee on transfer 代币,会根据用户分红账户的余额对用户发放分红.攻击者利用漏洞增发分红账户的余额,随后触发分红机制完成获利. OSN:https://bscscan ...
- vue serve 部署 步骤说明
1. 构建镜像 docker build -t 镜像名称:镜像TAG --build-arg URL=http://localhost:8081 --build-arg PORT=2000 --bui ...
- 【YashanDB知识库】调整NUMBER精度,再执行统计信息收集高级包偶现数据库异常退出
[问题分类]功能使用 [关键字]NUMBER类型精度修改,统计信息收集 [问题描述]存量的表将NUMBER类型的字段精度从小精度调整为大精度时,数据库收集这张业务表的统计信息时,会导致数据库异常退出. ...
- Linux 终端运行命令时出现多行带有加号的信息(详见文章内容)
++_vte_ prompt_ command +++ HISTTIMEFORMAT= +++ history 1 +++ sed 's/^ *[0-9] \+ *//' ++ local ' com ...
- CSS – vw, vh, position fixed and ICB (initial containing block)
什么是 vw, vh? vh 的 v 指的是 viewport, h 就是 height. 它是 CSS 值的单位就像 px, %. .container { height: 30vh; backgr ...
- Azure 入门系列 (第二篇 Backup 和 Disaster Recovery)
本系列 这个系列会介绍从 0 到 1 搭建一个 Web Application 的 Server. 间中还会带上一些真实开发常用的功能. 一共 6 篇 1. Virtual Machine (VM) ...
- Spring —— bean生命周期
bean生命周期 生命周期:从创建到消亡的完整过程 bean生命周期:bean从创建到销毁的整体过程 bean生命周期控制:在bean创建后到销毁前做一些事情 方式一:配置控制生命周期 <b ...
- BOOST 环形队列circular_buffer
BOOST库的环形队列比较灵活,前插或后插,删除队首或删除队尾元素,都支持. 只贴代码: #include <boost/circular_buffer.hpp> #include < ...
- 为什么用 AWS CLI?因为我懒得点鼠标!
在这篇博客中,我们一起深入探索 AWS CLI 的世界,从零开始,逐步构建在云端的家园.将介绍 AWS CLI 的基本功能和使用场景,如何创建 IAM 用户.VPC.子网.安全组.EC2 实例等,甚至 ...
- Codeforces Round 977 (Div. 2)
手速局,因为水平不够三题遗憾离场. A. Meaning Mean 题意 你一个序列,你每次可以选择两个数删掉,并把他们的平均数加入到序列的末尾.当序列长度为 \(1\) 的时候,剩下的数最大值是多少 ...