动态SQL数据层框架(5):AOP事务管理

 

这是一个简单的数据层框架,可以实现动态SQL查询、自动化事务管理、IOC和依赖注入,使用了以下技术:

1) Maven管理依赖,Github托管代码  2) DBUtils框架作为JDBC底层框架  3) JDK动态代理实现的AOP  4) 注解  5) Freemarker来做动态SQL模板的解析  6) 工厂设计模式  7) 反射和XML解析


从使用方式开始介绍,逐步深入地去理解框架的各个知识点。

框架主要分为两部分:
1) dynamic-sql-dao是框架主体
2) dynamic-sql-dao-test是一个demo含有测试用例


Github地址

https://github.com/xuguofeng/dynamic-sql-dao
https://github.com/xuguofeng/dynamic-sql-dao-test

 

文章目录

动态SQL数据层框架(0):一个基于FreeMarker和DBUtils的动态SQL框架
动态SQL数据层框架(1):DBUtils框架基础
动态SQL数据层框架(2):框架结构
动态SQL数据层框架(3):BaseDao接口和BaseDaoSupport抽象类
动态SQL数据层框架(4):DynamicSQLParser工具类和配置文件
动态SQL数据层框架(5):AOP事务管理

 

本篇文章主要介绍如何使用JDK动态代理技术、对象工厂模式、注解技术实现AOP自动化事务管理,关于对象工厂可以参考《多态和简单对象工厂

 

一、BeanFactory工厂

1、静态Map保存对象

1 /**
2  * 保存对象,接口名 —> 实现类对象
3  */
4 private static Map<String, Object> beans = new HashMap<String, Object>();

 

2、加载工厂配置

在类路径下创建beans.properties文件,配置需要使用工厂管理的对象,格式如下:

 

DepartmentDao=org.net5ijy.dao.jdbc.DepartmentDaoImpl
EmployeeDao=org.net5ijy.dao.jdbc.EmployeeDaoImpl
StudentDao=org.net5ijy.dao.jdbc.StudentDaoImpl
TeacherDao=org.net5ijy.dao.jdbc.TeacherDaoImpl

 

Key为接口简单名

Value为对应使用的实现类的全限定名

 

该类有一个静态代码块,用于加载配置,实例化对象、依赖注入以及事务代理对象的创建

 

实例化对象

 1 // 加载beans.properties文件
 2 prop.load(BeanFactory.class.getClassLoader().getResourceAsStream(
 3         "beans.properties"));
 4 
 5 Set<String> keys = prop.stringPropertyNames();
 6 
 7 // 遍历,实例化,放入对象缓存beans
 8 for (String key : keys) {
 9     String className = prop.getProperty(key);
10     try {
11      // 实例化对象
12         Class<?> clazz = Class.forName(className);
13         Object obj = clazz.newInstance();
14 
15         // 放入对象缓存beans
16         beans.put(key, obj);
17     } catch (ClassNotFoundException e) {
18     } catch (InstantiationException e) {
19     } catch (IllegalAccessException e) {
20     }
21 }
View Code

 

依赖注入

首先把全部对象获取出来,遍历,获取标注了@Resource注解的set方法,之后从方法名截取set后面的内容,从beans中获取对应的对象,最后调用这个set方法注入依赖对象

 1 Collection<Object> objects = beans.values();
 2 for (Object obj : objects) {
 3     Method[] methods = obj.getClass().getMethods();
 4     for (Method method : methods) {
 5         if (method.getAnnotation(Resource.class) != null) {
 6             String methodName = method.getName().replace("set", "");
 7             Object o = beans.get(methodName);
 8             try {
 9                 method.invoke(obj, o);
10             } catch (Exception e) {
11                 log.error("依赖注入出错", e);
12             }
13         }
14     }
15 }
View Code

 

事务扫描

对工厂中的全部对象进行遍历,如果某个对象所属类型标注了@Transactional注解,就使用TransactionProxy.proxyFor创建一个这个对象的代理对象,这个代理对象可以实现事务的管理,后面会有详细介绍

 1 Set<String> beanNames = beans.keySet();
 2 for (String name : beanNames) {
 3     Object obj = beans.get(name);
 4     Class<?> clazz = obj.getClass();
 5     if (clazz.getAnnotation(Transactional.class) != null) {
 6         // 使用JDK动态代理
 7         obj = TransactionProxy.proxyFor(obj);
 8         beans.put(name, obj);
 9     }
10 }
View Code

 

 

3、静态方法获取对象

 1 /**
 2  * 根据指定接口类Class对象获取实现类bean
 3  */
 4 public static <T> T getObject(Class<T> clazz) {
 5     Object bean = beans.get(clazz.getSimpleName());
 6     if (bean != null) {
 7         return (T) bean;
 8     }
 9     return null;
10 }

 

二、TransactionProxy和TransactionInvocationHandler

1、@Transactional注解

1 /**
2  * 标注在需要使用事务的业务层类和方法上
3  */
4 @Target({ ElementType.TYPE, ElementType.METHOD })
5 @Retention(RetentionPolicy.RUNTIME)
6 public @interface Transactional {
7 
8 }

 

之前在介绍“工厂事务扫描”时提到了工厂会为标注了@Transactional的类的对象创建能够管理事务的代理对象。

而且,这个注解还可以标注在方法上,代理对象内部会判断调用的方法是否标注了@Transactional注解,如果标注了才会进行事务管理。

 

2、TransactionProxy创建代理对象

该类有一个proxyFor静态方法使用JDK的动态代理机制为传入的Object对象创建代理对象。

1 public static Object proxyFor(Object object) {
2     return Proxy.newProxyInstance(object.getClass().getClassLoader(),
3             object.getClass().getInterfaces(),
4             new TransactionInvocationHandler(object));
5 }

 

内部类TransactionInvocationHandler实现了InvocationHandler接口。在创建TransactionInvocationHandler时需要传入被代理的对象

1 private Object proxy;
2 private Class<?> proxyClass;
3 
4 TransactionInvocationHandler(Object object) {
5     this.proxy = object;
6     this.proxyClass = object.getClass();
7 }

 

实现了invoke方法,可以对被代理对象进行方法拦截、事务管理。

 

首先判断拦截到的方法是否标注了@Transactional注解

Transactional t = this.proxyClass.getMethod(method.getName(),
        method.getParameterTypes()).getAnnotation(Transactional.class);

 

如果没有@Transactional注解直接执行方法并返回

1 if (t == null) {
2     return method.invoke(proxy, objects);
3 }

 

如果有@Transactional注解,首先使用TransactionManager.startTransacation()开启事务,然后执行方法获取返回值,使用TransactionManager.commit()提交事务。如果执行方法过程中发生异常,会使用TransactionManager.rollback()回滚事务,最后在finally里面使用TransactionManager.close()关闭数据库连接。

 

完整的invoke方法如下:

 1 public Object invoke(Object obj, Method method, Object[] objects)
 2         throws Throwable {
 3 
 4     Object result = null;
 5 
 6     // 判断是否有Transactional注解
 7     Transactional t = this.proxyClass.getMethod(method.getName(),
 8             method.getParameterTypes()).getAnnotation(Transactional.class);
 9     try {
10         // 没有Transactional注解直接执行返回
11         if (t == null) {
12             return method.invoke(proxy, objects);
13         }
14         // 有Transactional注解
15         // 开启事务
16         TransactionManager.startTransacation();
17         // 获取业务操作返回值
18         result = method.invoke(proxy, objects);
19         // 提交事务
20         TransactionManager.commit();
21     } catch (Exception e) {
22         log.error("业务操作失败", e);
23         // 异常时事务回滚
24         if (t != null) {
25             TransactionManager.rollback();
26         }
27         throw e;
28     } finally {
29         // 关闭连接
30         TransactionManager.close();
31     }
32     return result;
33 }
View Code

 

三、TransactionManager工具类

1、功能概述

之前的数据库连接工具类DBCPUtil管理着一个静态的数据源,调用getConnection()方法时会从数据源获取一个数据库连接。这个类并不能管理事务。

如果需要事务,开发者就要在DAO实现中显式地开启事务、提交、回滚和关闭连接。如下:

 1 public boolean addEmployee(Employee e) {
 2 
 3     Connection conn = null;
 4     PreparedStatement prep = null;
 5 
 6     try {
 7         conn = DBUtil.getConnection();
 8 
 9         conn.setAutoCommit(false);
10 
11         // ...
12 
13         conn.commit();
14 
15         return true;
16 
17     } catch (SQLException e1) {
18         try {
19             conn.rollback();
20         } catch (SQLException e2) {
21         }
22     } finally {
23         try {
24             if (prep != null) {
25                 prep.close();
26             }
27             if (conn != null) {
28                 conn.close();
29             }
30         } catch (SQLException e1) {
31         }
32     }
33     return false;
34 }
View Code

 

2、核心作用

在介绍TransactionInvocationHandler时,我们提到了:

使用TransactionManager.startTransacation()开启事务,TransactionManager.commit()提交事务,TransactionManager.rollback()回滚事务,TransactionManager.close()关闭数据库连接。

 

有了BeanFactory工厂、事务扫描、事务代理对象之后,我们只需要在Service实现类和需要使用事务的方法上标注@Transactional就可以实现自动化的事务管理了,如下:

 1 @Transactional
 2 public class AccountServiceImpl implements AccountService {
 3 
 4     private AccountDao accountDao;
 5 
 6     @Resource
 7     public void setAccountDao(AccountDao accountDao) {
 8         this.accountDao = accountDao;
 9     }
10 
11     @Override
12     @Transactional
13     public boolean doTransfer(Account from, Account to, Double amount) {
14 
15         // 判断转出账户余额是否充足
16         if (from.getBalance() < amount) {
17             throw new RuntimeException("转出账户余额不足,操作失败");
18         }
19         // 设置
20         from.setBalance(from.getBalance() - amount);
21         to.setBalance(to.getBalance() + amount);
22         // 修改数据
23         boolean b1 = this.accountDao.updateObject(from);
24 
25         boolean b2 = this.accountDao.updateObject(to);
26         // 操作全部成功才返回true
27         if (b1 && b2) {
28             return true;
29         }
30         throw new RuntimeException("转账操作失败");
31     }
32 }
View Code

 

在DAO中使用TransactionManager.getConnection()即可获取到当前线程上绑定的连接,在数据操作时不需要管理事务、关闭连接。

 

3、实现方式

内部使用ThreadLocal用于绑定当前线程和一个数据库连接

 

private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

 

所有的方法都只是对当前线程上绑定的连接进行操作

 1 /**
 2  * 获取当前线程绑定的Connection
 3  */
 4 public static Connection getConnection() throws SQLException {
 5     Connection conn = tl.get();
 6     if (conn == null) {
 7         conn = DBCPUtil.getConnection();
 8         tl.set(conn);
 9     }
10     return conn;
11 }
12 /**
13  * 开启事务
14  */
15 public static void startTransacation() throws SQLException {
16     getConnection().setAutoCommit(false);
17 }
18 /**
19  * 提交事务
20  */
21 public static void commit() throws SQLException {
22     getConnection().commit();
23 }
24 /**
25  * 回滚事务
26  */
27 public static void rollback() throws SQLException {
28     getConnection().rollback();
29 }
30 /**
31  * 关闭连接
32  */
33 public static void close() throws SQLException {
34     getConnection().close();
35     tl.remove();
36 }
View Code

 

posted @ 2018-08-31 14:33  用户不存在!  阅读(277)  评论(0编辑  收藏  举报