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注解来定义需要进行事务控制的方法。

    源码 

  • 相关阅读:
    Linux 查看dns运行状态
    Linux 查看网卡流量、网络端口
    Linux 查看磁盘读写速度IO使用情况
    Linux 查看系统状态
    Linux 查看进程
    Python RabbitMQ RPC实现
    [转]轻量级 Java Web 框架架构设计
    java集合总结【转】
    java 线程
    Geoserver基本使用、WMS服务发布与OpenLayers测试
  • 原文地址:https://www.cnblogs.com/aeolian/p/10081145.html
Copyright © 2011-2022 走看看