MyBatis 自动关闭 session

在某些小项目中,需要单独使用到 mybatis,但是网上常见工具类方法获取的 session 通常要么需要手动关闭,这样即麻烦,而且有时又容易出错,要么要需要结合使用spring,但是我们只想写个简单的增删改,不想引入太多框架。

而下面将要介绍的SqlSessionManager所获取的session以及mapper就无需关心连接关闭的事情了。

环境搭建

数据库与实体类:

工具类:

public class MybatisUtils {
	
	private static SqlSessionManager sessionManager = null;

	static {
		try {
			InputStream inputStream = MybatisUtils.class.getClassLoader()
                                                 .getResourceAsStream("mybatis-config1.xml");
			sessionManager = SqlSessionManager.newInstance(inputStream);
		} catch (Exception e) {
			throw new RuntimeException("the Configuration of Mybatis is not exist!");
		}
	}
	
	/**
	 * 获取 Mapper
	 * @param <T>
	 * @param clazz
	 * @return
	 */
	public static <T> T getMapper(Class<T> clazz) {
		return sessionManager.getMapper(clazz);
	}
	
	/**
	 * 获取 SqlSessionManager 
	 * @return  
	 */
	public static SqlSessionManager getSessionManager() {
		return sessionManager;
	}
}

mapper:

public interface AccountMapper {

	@Select("select * from account")
	public List<Account> selectAccountList();
	
	@Select("select * from account where id = #{id}")
	public Account selectAccountById(Long id);
	
	@Update("update account set money = #{money} where id = #{userId}")
	public void updateAccount(Account account);
}

原理

它的原理就是使用了JDK的动态代理,先看看SqlSessionManager的构造方法

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

从上面可以看出SqlSessionManager使用了Proxy.newProxyInstance()即JDK动态代理对默认的session进行了增强,增强类为SqlSessionInterceptor

下面来结合具体的例子来看一下增强方法

@Test
public void test1() {
    AccountMapper mapper = MybatisUtils.getMapper(AccountMapper.class);
    List<Account> list = mapper.selectAccountList();
    System.out.println(list);
}

上面是一个很简单的查询方法,

然后我们在源码画线处打一个断点

然后执行

因为我们并没有设置localSqlSession,所以很显然跳转到了else块中,当然localSqlSession的设置会在后面事务管理中说到。

然后我们看②处代码,他使用了增强的try语句,可以自动帮我们释放 session。还有③④处代码,会发现SqlSessionManager自动帮我们进行事务的提交与回滚,所以之后我们使用mapper的方法时,可以随意使用,不再关心连接释放的问题了。

事务管理

前面说到通过SqlSessionManager获取的mapper进行了增强,在我们使用单一的增删改时就不用再考虑连接释放的问题了,但是有些问题需要执行多个mapper方法,这时就要通过事务来完成了,并且SqlSessionManager也为我们提供了一些事务提交回滚的方法,下面就来看看。

假设有个简单的业务转账,用户1向用户2转账指定金额,当程序出错时要能回滚,不能一方扣了钱,另一方钱却没有增加。

这里最关键的就是①处的startManagedSession()方法,我们能发现这里给localSqlSession设置了一个值

再回到前面的SqlSessionInterceptor类中

就能发现,当我们调用了startManagedSession()方法后,SqlSessionManager就不会帮助我们进行session的管理,一切连接的关闭,事务的提交与回滚就需要我们自己手动完成了

小结

通过SqlSessionManager获取的mapper,使用单一的接口方法操作时,我们可以不用关心连接的释放。

只有需要多个接口方法组合操作时我们才需要开启session管理,手动来进行连接的释放,与事务提交回滚。

作者: 贺墨于
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/hemou/p/14700091.html