mybatis-sqlSession工具类,事务处理器的实现
1.作用域(Scope)和生命周期
理解不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。
1.1SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了sqlSessionFactory,我们就不在需要它了
1.2SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,因此我们可以将它写在静态方法中,初始化实例对象,只初始化一次
1.3SqlSession
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是安全的,因此不能被共享,所以它最佳的作用域应该是一次请求或者是方法作用域
2.sqlSession工具类的实现
2.1实现思路
思路:
在类加载的时候,我们初始化sqlSessionFactory对象
获取当前线程中的sqlSession对象,当我们第一次获取时一定为空,这时我们进行为空判断,创建sqlSession对象,并绑定到当前线程中
释放资源的方法,解除sqlSession与当前线程的绑定关系
重要:tomcat自带线程池,并发量不高时,新的请求进入,会自动分配一个全新的线程。在高并发的情况下,用过的t1线程,下一个请求进入时,很有可能会使用这个线程,这时进行步骤2必然会获取到有sqlSession的线程,所以我们这里要进行解绑也就是调用remove()方法
回滚事务的方法
2.2具体实现
package com.yth.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
/**
* SqlSession工具类
* @author yth
* @date 2020/10/14 - 8:08
*/
public class SqlSessionUtils {
//工具类的构造方法都是一般都是私有化的,因为工具类的使用不需要实例化
private SqlSessionUtils(){
}
private static SqlSessionFactory sqlSessionFactory;
private static ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
//类加载的时候,初始化sqlSessionFactory对象,初始化一次
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取当前线程中的sqlSession对象
* @return
*/
public static SqlSession getCurrentSqlSession(){
SqlSession sqlSession = local.get();
if(sqlSession == null){
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession); //将sqlSession绑定到当前线程上
}
return sqlSession;
}
/**
* 释放资源
* @param sqlSession
*/
public static void close(SqlSession sqlSession){
if(sqlSession!=null){
sqlSession.close();
//重要 接触sqlSession与当前线程的绑定关系
local.remove(); //tomcat自带线程池,用过的线程t1,下次可能还会使用线程t1
}
}
/**
* 回滚事务
* @param sqlSession
*/
public static void rollback(SqlSession sqlSession){
if(sqlSession!=null){
sqlSession.rollback();
}
}
}
2.3关于ThreadLocal类
2.3.1ThreadLocal是什么?
ThreadLocal是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap类,该类为Thread类的一个局部变量,该Map存储的key为ThreadLocal对象自身,value为我们要存储的对象,这样一来,在不同线程中,持有的其实都是当前线程的变量副本,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。
2.3.2ThreadLocal提供的方法
主要方法:
- void set(Object value)
设置当前线程的线程局部变量的值。- public Object get()
该方法返回当前线程所对应的线程局部变量。- public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
因此我们可以使用ThreadLocal来保存上下文信息,在有需要的任意地方来获取,通过源码可以看出ThreadLocalMap中用于存储数据的entry定义
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
从中我们会发现这个Map的key是ThreadLocal类的实例对象,value为用户设置的值,所以这个工作原理决定了:每个线程独自拥有一个变量,并非是共享的。
当ThreadLocal被垃圾回收之后,在ThreadLocalMap对应的Entry的键值会变为null,但是Entry是强引用,索引Entry存储的Object类型的无法回收,所以我们在实践的时候,在不用ThreadLocal我们应该主动调用remove方法进行清理。
3.事务处理器的实现
SqlSession工具类准备好了,现在需要使用动态代理来处理事务,首先需要实现InvocationHandler接口,提供一个构造方法,利用SqlSession工具类获取sqlSession对象,调用commit方法,处理异常时调用回滚的方法,在finally中调用释放资源的方法,通过反射机制,Method.invoke(对象名,参数值)方法动态的获取service中的目标方法并返回。这样我们就可以通过getProxy()获取代理对象实例。
package com.yth.utils;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 事务处理器
* 动态代理
* @author yth
* @date 2020/10/14 - 8:36
*/
public class TransactionInvocationHandler implements InvocationHandler {
private Object target;
public TransactionInvocationHandler(Object target){
this.target = target;
}
/**
* 获取代理对象实例
*/
public Object getProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = null;
Object retValue = null;
try {
sqlSession = SqlSessionUtils.getCurrentSqlSession();
//调用service当中的目标方法
retValue = method.invoke(target, args);//重要!重要!重要!!!
sqlSession.commit();
} catch (Exception e) {
SqlSessionUtils.rollback(sqlSession);
e.printStackTrace();
}finally {
SqlSessionUtils.close(sqlSession);
}
return retValue;
}
}
retValue = method.invoke(target, args);//重要!重要!重要!!!
在这个代码之前和之后都可以添加扩展代码,不一定是事务,也可以是发邮件,打电话,比如当一个系统在某处出现了问题需要打电话给领导来处理,自动打电话,这里就可以调用相应的方法来动态代理,这些和业务逻辑没有关系,这样就可以用到这种模式,跟控制事务差不多,否在在控制层都要重复写代码。这样可以解决代码冗余问题。
4.如何使用这两个工具
照着下面代码抄就完事了