mybatis-封装工具类SqlSessionUtil,使用动态代理封装事务调用处理器

mybatis-sqlSession工具类,事务处理器的实现

1.作用域(Scope)和生命周期

理解不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

1.1SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了sqlSessionFactory,我们就不在需要它了

1.2SqlSessionFactory

一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,因此我们可以将它写在静态方法中,初始化实例对象,只初始化一次

1.3SqlSession

每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是安全的,因此不能被共享,所以它最佳的作用域应该是一次请求或者是方法作用域

2.sqlSession工具类的实现

2.1实现思路

思路:

  1. 在类加载的时候,我们初始化sqlSessionFactory对象

  2. 获取当前线程中的sqlSession对象,当我们第一次获取时一定为空,这时我们进行为空判断,创建sqlSession对象,并绑定到当前线程中

  3. 释放资源的方法,解除sqlSession与当前线程的绑定关系

    重要:tomcat自带线程池,并发量不高时,新的请求进入,会自动分配一个全新的线程。在高并发的情况下,用过的t1线程,下一个请求进入时,很有可能会使用这个线程,这时进行步骤2必然会获取到有sqlSession的线程,所以我们这里要进行解绑也就是调用remove()方法

  4. 回滚事务的方法

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提供的方法

主要方法:

  1. void set(Object value)
    设置当前线程的线程局部变量的值。
  2. public Object get()
    该方法返回当前线程所对应的线程局部变量。
  3. 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.如何使用这两个工具

照着下面代码抄就完事了
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41520945/article/details/109106940