簡單探討spring整合mybatis時sqlSession不須要釋放關閉的問題

https://blog.csdn.net/RicardoDing/article/details/79899686spring

近期,在使用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
版權聲明:本文爲博主原創文章,轉載請附上博文連接!sql

相關文章
相關標籤/搜索