zoukankan      html  css  js  c++  java
  • 简单探讨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
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    期末考试(优先队列)
    看病要排队《优先队列》
    Windows Message Queue(优先队列)
    Stones(优先队列)
    懒省事的小明(优先队列)
    产生冠军(set,map,拓扑结构三种方法)
    Web轻量级扫描工具Skipfish
    Web侦察工具HTTrack (爬取整站)
    文件上传漏洞绕过技巧
    Python爬虫之selenium的使用(八)
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/10712115.html
Copyright © 2011-2022 走看看