动态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 }
依赖注入
首先把全部对象获取出来,遍历,获取标注了@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 }
事务扫描
对工厂中的全部对象进行遍历,如果某个对象所属类型标注了@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 }
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 }
三、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 }
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 }
在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 }