简单探讨spring整合mybatis时sqlSession不需要释放关闭的问题
https://blog.csdn.net/RicardoDing/article/details/79899686
近期,在使用spring和mybatis框架编写代码时,sqlSession不需要手动关闭这一点引起了我的兴趣。我们都知道,单独使用mybatis时,sqlSeesion使用完毕后是需要进行手动关闭的,但为什么在和spring整合后就不需要了呢?在查阅了资料后得知,这是使用了spring中的AOP面向切面编程和动态代理技术。但是我认为脱离代码谈技术就是耍流氓。因此,我对 MyBatis-Spring 整合包的源码进行了简单的探究,水平有限,如有错漏,请多指教
首先,我们来编写一段简单的原始DAO开发的代码
mybatisConfig.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 懒加载设置为 true -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 积极加载设置为false -->
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode"/>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 自定义别名 -->
<typeAliases>
<package name="com.po"/>
</typeAliases>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlMap/StudentMapper.xml"/>
<!-- 批量加载映射文件 -->
<package name="com.mapper"/>
</mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这里插一段题外话,在配置文件中踩到了一个小坑,就是<mappers>节点下没办法同时使用<mapper>和<package>节点的问题,编译器一直报错,最后查询dtd约束才发现,配置文件要求先使用<mapper>节点,再使用<package>节点,顺序混乱就会报错
spring配置文件applicationContext.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描加载classpath路径下的所有properties文件,classpath路径就是target/classes文件夹下的路径-->
<context:property-placeholder location="classpath*:*.properties" />
<!--配置数据源,数据库连接池使用阿里巴巴的druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!--最大连接数-->
<property name="maxActive" value="60"></property>
<!--最小连接数-->
<property name="minIdle" value="10"></property>
<property name="testWhileIdle" value="true"></property>
</bean>
<!--在这个bean中配置sqlSeesionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--加载mybatis配置文件-->
<!--注入依赖,value填入mybatis配置文件的classpath路径即可-->
<property name="configLocation" value="mybatis/mybatisConfig.xml"></property>
<!--引用配置好的数据源DataSource的bean-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--原始Dao开发-->
<bean id="studentDaoImp" class="com.dao.StudentDaoImp">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
从spring配置文件中我们可以看到,首先我们扫描并加载了properties文件,然后我们使用druid连接池配置了数据源的bean,该bean被注入到org.mybatis.spring.SqlSessionFactoryBean 对象中作为全局的sqlSessionFactory使用,有了sqlSessionFactory后,一切就好办了,接下来我们编写mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="originalDao">
<select id="findInstructorsByStudentInfo" parameterType="Student" resultType="ClassTeacher">
SELECT * FROM class
LEFT JOIN teacher ON class.teacher_id=teacher.id
LEFT JOIN student ON student.class_id=class.id
WHERE student.name=#{name} and sex=#{sex}
</select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
入参是Student类型的po对象,出参是ClassTeacher类型的po对象,namespace是originalDao(原始DAO开发不对namespace命名规范做要求)
DAO接口如下:
package com.dao;
import com.po.ClassTeacher;
import com.po.Student;
public interface StudentDao {
public ClassTeacher findInstructorsByStudentInfo(Student student) throws Exception;
}
1
2
3
4
5
6
7
8
DAO实现类如下,我们可以看到,在selectOne执行结束后,并没有调用sqlSession.close()方法:
package com.dao;
import com.po.ClassTeacher;
import com.po.Student;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
public class StudentDaoImp extends SqlSessionDaoSupport implements StudentDao{
public ClassTeacher findInstructorsByStudentInfo(Student student) throws Exception{
SqlSession sqlSession=this.getSqlSession();
ClassTeacher classTeacher=sqlSession.selectOne("originalDao.findInstructorsByStudentInfo",student);
//这里的sqlSession是一个代理类SqlSessionTemplate,内部他会为每次请求创建线程安全的sqlsession,并与Spring进行集成.在方法调用完毕以后会自动关闭。
return classTeacher;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
那么关键到底在哪呢?在实现类中,唯一不是我们自己代码的就是extends SqlSessionDaoSupport这一段,看来猫腻八成出在这里,让我们点开它看一看:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.mybatis.spring.support;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.dao.support.DaoSupport;
import org.springframework.util.Assert;
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public SqlSessionDaoSupport() {
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
protected void checkDaoConfig() {
Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
请注意这两个方法setSqlSessionFactory和getSqlSession,通过getSqlSession我们发现,实际上获取sqlSession就是将SqlSessionDaoSupport持有的sqlSession对象作为返回值返回,那么sqlSession对象从哪儿来的呢?只有两个方法涉及到了sqlSeesion的赋值,setSqlSessionTemplate和setSqlSessionFactory,前者是给定一个外来的SqlSessionTemplate对象,而后者是通过外部传递一个sqlSessionFactory自己创建一个SqlSessionTemplate对象。看到这里你可能会惊讶,什么?原来我们用的sqlSession一直不是mybatis中原汁原味的sqlSession,而是SqlSessionTemplate这个对象?那么SqlSessionTemplate到底是何方神圣?
由于sqlSessionTemplate的代码太多,这里就不贴出全部源码了,只贴出关键部分
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
从这一段我们可以看到,SqlSessionTemplate 实现了sqlSession的接口,因此我们可以把它当做sqlSeesion来使用,请注意这一个属性private final SqlSession sqlSessionProxy;
我们在前面说了,我们是通过传递sqlSessionFactory来构建SqlSessionTemplate 对象的,请仔细查看它的构造函数,我们发现了关键的一段代码:
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
1
我们可以清楚的看到,这段代码使用了动态代理技术,它使用的调用处理器参数(第三个参数)是SqlSessionTemplate.SqlSessionInterceptor,它是新建了一个SqlSessionTemplate中的SqlSessionInterceptor类的实例,因此我们查看在SqlSessionTemplate类定义中是否有内部类SqlSessionInterceptor,最终我们发现了SqlSessionTemplate中的私有内部类:
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
我们在finally代码块中最终发现了我们苦苦寻找的closeSqlSession方法
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
Assert.notNull(session, "No SqlSession specified");
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
if (holder != null && holder.getSqlSession() == session) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
session.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
因此,我们最终得出结论,spring整合mybatis之后,通过动态代理的方式,使用SqlSessionTemplate持有的sqlSessionProxy属性来代理执行sql操作(比如下面SqlSessionTemplate类的insert方法)
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}
1
2
3
最终,insert方法执行完操作后,会执行finally里面的代码对sqlSeesion进行关闭,因此,spring整合mybatis之后,由spring管理的sqlSeesion在sql方法(增删改查等操作)执行完毕后就自行关闭了sqlSession,不需要我们对其进行手动关闭
本次探寻源码也到此位置了,这也是我第一次认真开始写博客(我更喜欢记笔记),以后我还是会尽量多写博客。本文原创,转载请和本人联系
---------------------
作者:旋律秋凉
来源:CSDN
原文:https://blog.csdn.net/RicardoDing/article/details/79899686
版权声明:本文为博主原创文章,转载请附上博文链接!
简单探讨spring整合mybatis时sqlSession不需要释放关闭的问题的更多相关文章
- 讨论Spring整合Mybatis时一级缓存失效得问题
问题 1.学习测试时发现了一级缓存并没有生效,先看案例: setting配置: <settings> <!-- (1) 提供可控制Mybatis框架运行行为的属性信息 --> ...
- Spring Boot 整合mybatis时遇到的mapper接口不能注入的问题
现实情况是这样的,因为在练习spring boot整合mybatis,所以自己新建了个项目做测试,可是在idea里面mapper接口注入报错,后来百度查询了下,把idea的注入等级设置为了warnin ...
- Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二
接着上一篇博客<Spring整合MyBatis(Maven+MySQL)一>继续. Spring的开放性和扩张性在J2EE应用领域得到了充分的证明,与其他优秀框架无缝的集成是Spring最 ...
- Mybatis学习(六)————— Spring整合mybatis
一.Spring整合mybatis思路 非常简单,这里先回顾一下mybatis最基础的根基, mybatis,有两个配置文件 全局配置文件SqlMapConfig.xml(配置数据源,全局变量,加载映 ...
- Mybatis(六) Spring整合mybatis
心莫浮躁~踏踏实实走,一步一个脚印,就算不学习,玩,能干嘛呢?人生就是那样,要找点有意思,打发时间的事情来做,而钻研技术,动脑动手的过程,还是比其他工作更有意思些~ so,努力啥的都是强迫自己做自以为 ...
- Spring整合MyBatis(一)MyBatis独立使用
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. MyBatis本是Apache的一个开源项目iBatis,2010年这 ...
- Spring整合Mybatis案例,献给初学的朋友
今天我们来学习Spring整合Mybatis. 开发环境:Ide:MyEclipse 2017 CI JDK:1.8 首先我们简单的认识下这两个框架 1.Mybatis MyBatis是一个支持普通S ...
- 深入源码理解Spring整合MyBatis原理
写在前面 聊一聊MyBatis的核心概念.Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的.(结合右侧目录了解吧) MyBatis相关核心概念粗略回顾 SqlSess ...
- Spring整合MyBatis小结
MyBatis在Spring中的配置 我们在Spring中写项目需要运用到数据库时,现在一般用的是MyBatis的框架来帮助我们书写代码,但是学习了SSM就要知道M指的就是MyBatis,在此,在Sp ...
随机推荐
- LeetCode(976. 三角形的最大周长)
问题描述 给定由一些正数(代表长度)组成的数组 A,返回由其中三个长度组成的.面积不为零的三角形的最大周长. 如果不能形成任何面积不为零的三角形,返回 0. 示例 1: 输入:[2,1,2] 输出:5 ...
- sqlserver数据类型转换
Insert into [Cet.4] Select CONVERT(VARCHAR(20),CONVERT(DECIMAL(20,7),F1)) FROM Sheet1$ 我从外部导入了一个学号表, ...
- 【最短路】道路重建 @upcexam5797
时间限制: 1 Sec 内存限制: 128 MB 题目描述 小L的家乡最近遭遇了一场洪水,城市变得面目全非,道路也都被冲毁了.生活还要继续,于是市政府决定重建城市中的道路. 在洪水到来前,城市中共有n ...
- Network Monitoring in Software-Defined Networking :A Review(综述)
来源:IEEE SYSTEMS JOURNAL 发表时间:2018 类型:综述 主要内容:概述了SDN监控的发展,并从收集信息.预处理.传送信息.分析.和描述五个阶段进行解读,并比较了传统网络和SDN ...
- Servlet(8)—GenericServlet和改进Servlet实例
概念genericservlet是个抽象的父类,必须给出子类才能实例化它.它给出了设计servlet的一些骨架,定义了servlet生命周期,还有一些得到名字.配置.初始化参数的方法,其设计的是和应用 ...
- 制作chrome插件/扩展程序,禁止谷歌浏览器访问某些网站
简单地说,浏览器插件,可以大大的扩展你的浏览器的功能.包括但不仅限于这些功能: 捕捉特定网页的内容 捕捉HTTP报文 捕捉用户浏览动作,改变浏览器地址栏/起始页/书签/Tab等界面元素的行为 与别的站 ...
- 20190131 经验总结:如何从rst文件编译出自己的sqlalchemy的文档
20190131 经验总结:如何编译sqlalchemy的文档 起因 www.sqlalchemy.org官网上不去了,不管是直接上,还是用代理都不行. sqlalchemy属于常用工具,看不到官方的 ...
- 使用Canvas制作画图工具
前 言 JRedu canvas是HTML5中重要的元素之一,canvas元素使用JavaScript在网页上绘制图像,画布是一个矩形区域,我们可以控制其每一个元素,并且canvas拥有多种的绘 ...
- 【纵谭 Python】系列直播(持续更新)
老周最近录了一些跟 Python 有关的直播,可以在“一直播”中搜索 ID 号 139251129 关注,也可以在微博中查看,反正都一样,同步的. 第一集:简单胡扯一下相关环境搭建.安装 Python ...
- Can't connect to MySQL server (10065)
在一台机器上连接另一台机器的MySQL服务器时,出现了下面的错误: Can't connect to MySQL server (10065) 这是对方机器没有关闭防火墙造成的,关闭对方机器防火墙以后 ...