zoukankan      html  css  js  c++  java
  • 架构探险笔记8-实现事务控制特性

    定义事务注解

    我们之前实现过一个Service 注解,用于定义服务类,而在服务类中会包括若干方法, 有些方法是具备事务性的,比如创建、修改、删除等。如何保证这类方法具有事务性呢? 我们可以利用这个Proxy 框架来实现一个简单的事务控制特性。只需要开发者使用Transaction 注解,将其定义在需要事务控制的方法上即可。
    下面我们就来实现这个事务管理框架。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Transaction {
    }

    在Transaction 注解中使用了@Target(ElementType.METHOD), 说明该注解只能用于方法级别。也就是说,我们需要将该注解应用在每个具有耶务性的方法上, 比如:

    @Service
    public class CustomerService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);
    
        /**
         * 获取客户列表
         */
        public List<Customer> getCustomerList(String keyWord){
            List<Customer> customerList = null;
            String sql = "select * from customer";
            customerList = DBHelper.queryEntityList(Customer.class,sql);
            return customerList;
        }
    
        /**
         * 根据id获取客户
         */
        public Customer getCustomer(long id){
            String sql = "select * from customer where id=?";
            Customer customer = DBHelper.queryEntity(Customer.class,sql,id);
            return customer;
        }
    
        /**
         * 创建客户
         */
        @Transaction
        public boolean createCustomer(Map<String,Object> fieldMap){
            Boolean b = DBHelper.insertEntity(Customer.class,fieldMap);
            return b;
        }
    
        /**
         * 更新客户
         */
        @Transaction
        public boolean updateCustomer(long id,Map<String,Object> fieldMap){
            Boolean b = DBHelper.updateEntity(Customer.class,id,fieldMap);
            return b;
        }
    
        /**
         * 删除客户
         */
        @Transaction
        public boolean deleteCustomer(long id){
            Boolean b = DBHelper.deleteEntity(Customer.class,id);
            return b;
        }
    
    }

    code

    以上createCustomer、updateCustomer、deleteCustomer 方法都带有Transaction 注解,表明它们都是具备事务性的。可以认为,凡是对数据库有变更的方法,都建议带上Transaction 注解,这样就可以保证一旦方法中有一个更新操作失败了,整个方法都可以回滚。

    提供事务相关操作

    JDBC 提供了郁务常用的操作,比如开启事务、提交事务、回滚事务等,找们可以将这些操作统一封装在DatabaseHelper 中,就像下面这样:

    /**
     * 数据库操作助手类
     */
    public final class DatabaseHelper {
    
        /**
         * 开启事务
         */
        public static void beginTransaction(){
            Connection conn = getConnection();   //获取连接(同一线程每次获取的是统一conn)
            if (conn!=null){
                try {
                    conn.setAutoCommit(false);  //将自动提交属性改为false
                } catch (SQLException e) {
                    LOGGER.error("begin transaction failure",e);
                    throw new RuntimeException(e);
                    //e.printStackTrace();
                }finally {
                    CONNECTION_HOLDER.set(conn);   //将改动属性的conn放入容器中
                }
            }
        }
    
        /**
         * 提交事务
         */
        public static void commitTransaction(){
            Connection conn = getConnection();
            if (conn != null){
                try {
                    conn.commit();   //提交事务
                    conn.close();    //关闭连接
                } catch (SQLException e) {
                    LOGGER.error("commit transaction failure",e);
                    throw new RuntimeException(e);
                    //e.printStackTrace();
                } finally {
                    CONNECTION_HOLDER.remove();   //从容器中移除连接
                }
            }
        }
    
        /**
         * 回滚事务
         */
        public static void rollbackTransaction(){
            Connection conn = getConnection();
            if (conn != null){
                try {
                    conn.rollback();   //回滚
                    conn.close();    //关闭连接
                } catch (SQLException e) {
                    LOGGER.error("rollback transaction failure",e);
                    throw new RuntimeException(e);
                    //e.printStackTrace();
                } finally {
                    CONNECTION_HOLDER.remove();   //从容器中移除连接
                }
            }
        }
    }

    code

    需要注意的是, 默认是自动提交事务的, 所以需要将自动提交属性设置为false。在开启事务完毕后, 市~将Connection 对象放入木地线程变;在中。当事务提交或回滚后, 需要移除本地线程变盘中的Connection 对象。

    编写事务代理切面类

    我们需要编写一个名为TransactionProxy 的类, 让它实现Proxy 接口, 在doProxy 方法中完成事务控制的相关逻辑,代码如下:

    public class TransactionProxy implements Proxy{
        private static final Logger LOGGER = LoggerFactory.getLogger(TransactionProxy.class);
    
        private static final ThreadLocal<Boolean>  FLAG_HOLDER = new ThreadLocal<Boolean>(){
            @Override
            protected Boolean initialValue() {
                return false;
            }
        };
        
        @Override
        public Object doProxy(ProxyChain proxyChain) throws Throwable {
            Object result;
            boolean flag = FLAG_HOLDER.get();
            Method method = proxyChain.getTargetMethod();
            if (!flag && method.isAnnotationPresent(Transaction.class)){
                FLAG_HOLDER.set(true);
                try {
                    DBHelper.beginTransaction();
                    LOGGER.debug("begin transaction");
                    result = proxyChain.doProxyChain();
                    DBHelper.commitTransaction();
                    LOGGER.debug("commit transaction");
                }catch (Exception e){
                    DBHelper.rollbackTransaction();
                    LOGGER.debug("rollback transaction");
                    throw  e;
                }finally {
                    FLAG_HOLDER.remove();
                }
            }else {
                result = proxyChain.doProxyChain();
            }
            return result;
        }
    }

    code

    这里定义了一个名为FLAG_HOLDER 的本地线程变量,它是一个标志,可以保证同一个线程中事务控制相关逻辑只会执行一次。通过ProxyChain 对象可获取目标方法,进而判断该方法是否带有Transaction 注解。首先调用口atabaseHelper.beginTransaction 方法开启事务,然后调用ProxyChain 对象的doProxyChain 方法执行目标方法,接着调用DatabaseHelper.commitTransaction提交事务,或者在异常处理中调用DatabaseHelper.rollbackTransacti on 方法回滚事务,最后别忘了移除FLAG_HOLDER 本地线程变量中的标志。

    在框架中添加事务代理机制

    只需对AopHelper 类的createProxyMap 方法稍作调整,即可将事务代理机制添加到框架中。由于之前添加到AOP 框架中的只是普通切面代理,现在需要将事务代理也添加进去。我们定义两个私有方法, 一个用于添加普通切面代理, 另一个用于添加事务代理,就像下面这样:

    public class AopHelper {
        /**
         * 创建所有Map<代理类,Set<代理目标类>>的映射关系
         * @return Map<代理类,Set<代理目标类>>
         * @throws Exception
         */
        private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception{
            Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();   //结果集<代理类,Set<代理目标类>>
            addAspectProxy(proxyMap);   //添加普通切面
            addTransactionProxy(proxyMap);   //添加事务代理
            return proxyMap;
        }
    
        private static void addAspectProxy(Map<Class<?>,Set<Class<?>>> proxyMap){
            //获取所有的AspectProxy的子类(代理类集合),即切面,
            /*这个是入口,根据基类来查找所有的切面(代理类),获取所有的切面!!!*/
            Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
            for (Class<?> proxyClass : proxyClassSet){   //遍历代理类(切面),如ControllerAspect
                if (proxyClass.isAnnotationPresent(Aspect.class)){    //验证基类为AspectProxy且要有Aspect注解的才能为切面。如果代理类的的注解为Aspect(也就是说代理类一定要都切点(注解)才能是切面),例如ControllerAspect代理类的注解为@Aspect(Controller.class)
                    Aspect aspect = proxyClass.getAnnotation(Aspect.class);   //获取代理类(切面)的注解
    
                    /*根据注解获取所有的目标类*/
                    Set<Class<?>> targetClassSet = createTargetClassSet(aspect);   //获取所有的代理目标类集合
    
                    proxyMap.put(proxyClass,targetClassSet);   //加入到结果集Map<代理类,Set<代理目标类>>中
                }
            }
        }
    
        /**
         * 获取Transaction的class - targets映射关系
         * @param proxyMap
         */
        private static void addTransactionProxy(Map<Class<?>,Set<Class<?>>> proxyMap) {
            Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
            proxyMap.put(TransactionProxy.class,serviceClassSet);
        }
    }

    code

    当运行应用程序后,就可和到事务代卫!!.切而中输山的日志信息了。到此为止, 一个简单的事务控制框架就开发元毕了。

    也可以用AOP的切面实现

    /**
     * 拦截器方式实现切面
     */
    @Aspect(Service.class)
    public class TransactionAspect extends AspectProxy {
        private static final Logger LOGGER = LoggerFactory.getLogger(TransactionAspect.class);
    
        /**
         * 前置增强
         * @param cls    目标类
         * @param method 目标方法
         * @param params 目标方法参数
         */
        @Override
        public void before(Class<?> cls, Method method, Object[] params) throws Throwable {
            LOGGER.debug("TransactionAspect---------begin---------");
            LOGGER.debug(String.format("class: %s",cls.getName()));
            LOGGER.debug(String.format("method: %s",method.getName()));
            try {
                DBHelper.beginTransaction();   //开启事务
                LOGGER.debug("begin transaction");
            }catch (Exception e){
                DBHelper.rollbackTransaction();   //异常回滚
                LOGGER.debug("rollback transaction");
                throw  e;
            }finally {
            }
    
        }
    
        /**
         * 后置增强
         * @param cls    目标类
         * @param method 目标方法
         * @param result 目标方法返回结果
         */
        @Override
        public void after(Class<?> cls, Method method, Object result) throws Throwable {
            try {
                DBHelper.commitTransaction();   //提交方法
                LOGGER.debug("commit transaction");
            }catch (Exception e){
                DBHelper.rollbackTransaction();  //异常回滚
                LOGGER.debug("rollback transaction");
                throw  e;
            }finally {
            }
        }
    
        /**
         * 拦截只有Transaction注解的方法
         * @param cls    目标类
         * @param method 目标方法
         * @param params 目标方法参数
         * @return 返回是否拦截
         */
        @Override
        public boolean intercept(Class<?> cls, Method method, Object[] params) throws Throwable {
            if (method.isAnnotationPresent(Transaction.class)){
                return true;
            }
            return false;
        }
    }

    code

    但是如果两个都有会报错,因为一个先开启事务后,执行下一个事务,第二个事务执行完毕并且提交然后再执行第一个事务的提交部分。此时Connection已经变为自动提交状态。

    如果没有看到日志打印,一定要注意log4j.properties中的log4j.logger.com.autumn=DEBUG配置,否则默认为log4j.rootLogger的等级,Class的根包路径必须是org.smart4j,换做其他是不可以的,会导致日志打印不出来。至于smart4j的jar包,也要有配置log4j.logger.org.smart4j=DEBUG的properties文件。但是最终只会读取项目的properties文件。所以要在项目的properties中加入log4j.logger.org.smart4j=DEBUG。

    总结

    在本章中,我们首先开发一个Proxy框架,然后通过该框架实现了AOP特性,通过模板方法模式提供了一个抽象的切面类,扩展AspectProxy抽象类并定义Aspect注解,然后完成特定的钩子方法,即可将横切逻辑与业务逻辑相分离,这就是AOP要做的事情。最后,我们使用Proxy框架实现了一个简单的事务代理框架,可在Service类的方法中使用Transaction注解来定义需要进行事务控制的方法。

    源码 

  • 相关阅读:
    CQUOJ 10819 MUH and House of Cards
    CQUOJ 9920 Ladder
    CQUOJ 9906 Little Girl and Maximum XOR
    CQUOJ 10672 Kolya and Tandem Repeat
    CQUOJ 9711 Primes on Interval
    指针试水
    Another test
    Test
    二分图匹配的重要概念以及匈牙利算法
    二分图最大匹配
  • 原文地址:https://www.cnblogs.com/aeolian/p/10081145.html
Copyright © 2011-2022 走看看