Spring整合MyBatis之SqlSession对象的产生

目录

  • 前言
  • `SqlSession` 对象的产生
    • `SqlSessionTemplate` 类登场
      • `SqlSessionTemplate` 类简介
      • 对象 `sqlSessionProxy` 初始化
  • 小结
  • `SqlSessionTemplate` 的使用
    • `Mapper` 映射文件
    • 测试类
    • 测试类的衍生

前言

在 上一篇 文章中,我们知道 MyBatis 有四大核心对象,分别是 SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession,Mapper 映射器,而 MyBatis 在和 Spring 整合之后,Spring 会帮助我们管理 SqlSessionFactory、SqlSession、Mapper 映射器这些核心 bean;在 这一篇 文章中已经阐述了 SqlSessionFactory 对象的产生过程,在本篇文章中就是来阐述 SqlSession 这个对象是如何产生的

SqlSession 对象的产生

MyBatis 中我们获取 SqlSession 的实例,实际上使用的都是 new DefaultSqlSession() 的方式,源码在 DefaultSqlSessionFactory 类中如下

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
	Transaction tx = null;
    try {
      	final Environment environment = configuration.getEnvironment();
      	final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      	tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      	final Executor executor = configuration.newExecutor(tx, execType);
      	// MyBatis源码:使用的是 new DefaultSqlSession()的方式,返回一个 SqlSession 对象
      	return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      	closeTransaction(tx); // may have fetched a connection so lets call close()
      	throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      	ErrorContext.instance().reset();
    }
}

MyBatis 在和 Spring 整合之后,我们是不能使用 new DefaultSqlSession() 的方式来创建 SqlSession 的,因为 DefaultSqlSession 类在 MyBatis 中是线程不安全的

Spring整合MyBatis之SqlSession对象的产生_第1张图片
Spring 为了解决类 DefaultSqlSession 线程不安全的问题,为我们提供了一个线程安全的 SqlSessionTemplate

SqlSessionTemplate 类登场

SqlSessionTemplate 类简介

该类同我们在 MyBatais 中使用的 DefaultSqlSession 类相同,也都为我们提供了直接操作数据库的一些方法,比如:selectOne(),selectList(),insert(),update(),delete()

  • SqlSessionTemplatemybatis-spring 的核心。这个类负责管理 MyBatisSqlSession,调用 MyBatisSQL 方法。SqlSessionTemplate 是线程安全的,可以被多个 DAO 所共享使用
  • SqlSessionTemplate 实现了 SqlSession 接口,SqlSessionTemplate 通常是被用来替代默认的 MyBatis 实现的 DefaultSqlSession , 因为模板可以参与到 Spring 的事务中并且被多个注入的映射器类所使用时也是线程安全的

对象 sqlSessionProxy 初始化

我们从 SqlSessionTemplate 类的 selectOne() 方法入手来分析,发现此处使用的是 SqlSession 类型的 sqlSessionProxy 的一个对象,这个对象我们通过名称可以看出它是一个代理

public class SqlSessionTemplate implements SqlSession, DisposableBean {

	private final SqlSessionFactory sqlSessionFactory;

  	private final ExecutorType executorType;

  	private final SqlSession sqlSessionProxy;
  
  	@Override
  	public <T> T selectOne(String statement) {
  		// 使用 sqlSessionProxy 来调用 selectOne() 方法
    	return this.sqlSessionProxy.<T> selectOne(statement);
  	}
	
	// 部分代码省略......	
}

那么 sqlSessionProxy 在哪里初始化呢?接着看源码如下

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

	notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 初始化 sqlSessionProxy 代理对象,基于JDK动态代理创建代理对象
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
		SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}

SqlSessionTemplate 类中共有 3 个构造方法,其最终内部都是调用上述的构造方法来完成初始化的。这个方法的关键是内部通过调用 JDK 动态代理 Proxy.newProxyInstance() 方法创建了一个 SqlSession 代理对象并赋值给了 sqlSessionProxy;此时,我们需要把目光聚焦到 SqlSessionInterceptor 类的实现逻辑上,因为其中包含该代理对象的代理逻辑,其代码逻辑如下

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	// 首先会使用工厂类、执行器类型、异常解析器创建一个 sqlSession
      	SqlSession sqlSession = getSqlSession(
          	SqlSessionTemplate.this.sqlSessionFactory,
          	SqlSessionTemplate.this.executorType,
          	SqlSessionTemplate.this.exceptionTranslator);
      	try {
      		// 然后再调用sqlSession的实现类,实际上就是在这里调用了DefaultSqlSession的方法
        	Object result = method.invoke(sqlSession, args);
        	// 此处是通过 Transactional 事务的方式
        	if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          		// sqlSession提交
          		sqlSession.commit(true);
        	}
        	return result;
      	} catch (Throwable t) {
        	// 省略......
        }
        throw unwrapped;
      	} finally {
        	if (sqlSession != null) {
	        	// 关闭sqlSession
          		closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        	}
      	}
    }
}

发现了没,在代理逻辑中先通过 getSqlSession() 方法获取 SqlSession,而后执行 method.invoke() 方法,之后通过 isSqlSessionTransactional() 方法判断当前操作是否是事务操作,如果属于事务操作则执行 sqlSession.commit() 方法,以此完成了事务的提交

我们继续对 getSqlSession() 方法进行分析,代码如下

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
	// 此处,通过事务管理器的 getResource()方法,将我们的sqlSessionFactory工厂类传入
	// 返回的是一个 SqlSessionHolder持久器类
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      	return session;
    }
    if (LOGGER.isDebugEnabled()) {
      	LOGGER.debug("Creating a new SqlSession");
    }
    session = sessionFactory.openSession(executorType);
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
  }

我们继续对 getResource() 方法进行分析,代码如下

public abstract class TransactionSynchronizationManager {
	private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		// 执行 doGetResource()方法
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		return value;
	}

	private static Object doGetResource(Object actualKey) {
		// 我们通过 resources.get() 的方式来获得,我们对这个 resources 属性进行分析
		// 发现它返回的 SqlSession的持久器 SqlSessionHolder 也是一个ThreadLocal对象
		// 即:我们每一个获取到的 SqlSession都是有自己的SqlSessionHolder对象的
		// 此处,一个事务一个SqlSession。本身ThreadLocal 就是线程安全的,所以每一个SqlSession 也都是线程安全的
		Map<Object, Object> map = (Map)resources.get();
		if (map == null) {
			return null;
		} else {
			Object value = map.get(actualKey);
			if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
				map.remove(actualKey);
				if (map.isEmpty()) {
					resources.remove();
				}
				value = null;
			}
			return value;
		}
	}
}

由此通过 SqlSessionTemplate 就可以创建线程安全的 SqlSession 对象了

小结

SqlSessionTemplate 类中有一个SqlSession 类型的 sqlSessionProxy
变量,他其实是 SqlSession 的代理类 SqlSessionInterceptor

  • 在每次 SqlSessionTemplate 执行方法(增删改查)的时候,最后都给交给 SqlSessionInterceptor 来代理执行
  • SqlSessionInterceptor 每次在获取 SqlSession 的时候,都会使用事务管理器从 Threadlocal 中获取,所以必然是线程安全的!对于 Spring 而言,每个事务都会使用同一个 SqlSession,其实也就是 DefaultSqlSession,用于操作相应的 executor 来进行 db 交互

SqlSessionTemplate 的使用

Mapper 映射文件



 
<mapper namespace="com.jorg.mybatis.mappers.StudentMapper">
    <update id="update" parameterType="java.util.Map">
        UPDATE
        student
        SET
        `name` = #{name}
        WHERE
        id = #{id}
    update>
mapper>

测试类

public class SqlSessionTemplateTest {
 
    public static void main(String[] args) {
		Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");
		// 获取 SqlSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        HashMap<String, Object> paramMaps = Maps.newHashMap();
        paramMaps.put("name", "hello");
        paramMaps.put("id", "100");
        sqlSessionTemplate.update("update", paramMaps);
    }
}
  • 在使用 SqlSessionTemplate 时,是无需通过调用 commit() 方法就可以完成真正的更新。原因就在于 SqlSessionInterceptor 类中会执行 method.invoke() 方法,之后通过 isSqlSessionTransactional() 方法判断当前操作是否是事务操作,如果属于事务操作则执行 sqlSession.commit() 方法,以此完成了事务的提交

在日常开发中,应该很少以上述的方式来使用 MyBatis,更多的则是将对应 Mapper 配置关联到相应的 interface,并直接调用 interface 中定义的方法来操作数据库。如下所示

测试类的衍生

public class SqlSessionTemplateTest {
 
    public static void main(String[] args) {
		Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");
		// 获取 SqlSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);
        studentMapper.update(100, "hello");
    }
}

上述通过 interface 来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?请看下回分解 点这里

你可能感兴趣的:(中间件,#,mybatis,/,jpa,MyBatis)