19. jdbc实战-实现类Spring声明式事务

it2022-05-05  179

原生的jdbc 对事务管理也是比较繁琐的, 需要手工进行提交和回滚, 还要一堆try-catch. 而熟悉spring 的同学都知道, spring采用了声明式事务方式来管理事务, 使事务管理变得很简单. Spring 事务很强大, 笔者这里仅使用jdbc 来模拟简单的几个属性.

1. 声明式事务方案设计

声明式事务主要依据java 动态代理实现通过将Connection 存放在ThreadLocal 变量中, 来解决并发问题. Spring 底层也是用的ThreadLocal.通过记录Connection 的创建者, 来解决事务的嵌套问题.自定义注解@EnableTranscation: 用于标明方法是否开启事务Service工厂: 用于模拟Spring容器创建Bean过程, 如果Service 中包含使用@EnableTranscation修饰的方法, 则创建Service的代理对象, 否则返回Service 实例自定义Dao时, 不能直接创建Connection, 需要获取当前线程中保存的Connection.

2. 连接池管理

笔者对数据库的连接采用c3p0 连接池.

2.1 c3po 配置

<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- 定义连接池, 指定名称. 程序中通过连接池名称来获取定义的连接池 --> <named-config name="myC3p0Pool"> <property name="user">root</property> <property name="password">root</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8</property> <!-- 若数据连接池中连接数不足时, 一次向数据库申请多少个连接 --> <property name="acquireIncrement">10</property> <!-- 初始化数据库连接池时,创建连接数 --> <property name="initialPoolSize">5</property> <!-- 数据库连接池中活跃的最小连接数--> <property name="minPoolSize">5</property> <!-- 数据库连接池中最大的连接数--> <property name="maxPoolSize">50</property> <!-- c3p0 数据库连接池还可以管理Statment的个数 --> <!-- 数据库中最大的Statment 个数 --> <property name="maxStatements">100</property> <!-- 每个连接最多拥有的Statement个数--> <property name="maxStatementsPerConnection">10</property> </named-config> </c3p0-config>

2.2 数据库连接工具类

封装获取数据库连接和关闭数据库连接资源的方法

public class DbConnUtil { // c3P0配置名 private static final String c3p0PoolName = "myC3p0Pool"; // 配置数据源 private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName); // 配置本地连接 private static ThreadLocal<TxConnection> txConnectionLocal = new ThreadLocal<>(); /** 获取数据库连接 * @param autoCommitTx 是否开启提供提交事务 * @return Connection 数据库连接 * @since 1.0 * @author zongf * @created 2019-07-18 */ public static Connection getConnection(boolean autoCommitTx) { try { Connection connection = dataSource.getConnection(); connection.setAutoCommit(autoCommitTx); return connection; } catch (SQLException e) { e.printStackTrace(); } return null; } /** * @Description: 获取本线程连接 * @return: Connection 数据库连接 * @author: zongf * @time: 2019-06-26 14:37:00 * @since 1.0 */ public static TxConnection getTxConnection() { TxConnection txConnection = null; // 如果ThreadLocal 中连接为空, 则创建新的连接 if (txConnectionLocal.get() == null || txConnectionLocal.get().getConnection() == null) { txConnection = new TxConnection(getConnection(true)); txConnectionLocal.set(txConnection); } else { txConnection = txConnectionLocal.get(); } return txConnection; } /** 获取当前线程内的数据库连接 * @return Connection * @since 1.0 * @author zongf * @created 2019-07-18 */ public static Connection getLocalConnection() { return getTxConnection().getConnection(); } /** 获取当前线程的数据库连接对象 * @return ThreadLocal<TxConnection> * @since 1.0 * @author zongf * @created 2019-07-18 */ public static ThreadLocal<TxConnection> getLocalTxConnection() { return txConnectionLocal; } /** 当归还连接时, 需要设置自动提交事务为true. * @param connection * @return null * @since 1.0 * @author zongf * @created 2019-07-18 */ public static void release(Connection connection) throws SQLException { try { if (connection != null && !connection.isClosed()) { connection.setAutoCommit(true); } } catch (SQLException e) { e.printStackTrace(); } finally { connection.close(); } } }

2.3 定义事务连接对象

由于在事务嵌套时, 需要遵循哪一层动态代理开启的事务, 由哪一层动态代理负责事务的开启和回滚, 因此需要记录事务的开启者. 因此笔者创建了一个TxConnneciton 对象.

public class TxConnection { private Connection connection; private String creator; // 省略setter/getter 方法 }

3. 自定义开启事务注解

定义一个类似于spring @Transcation 的注解, 用于开启事务.openNewTx 用于模拟spring七种事务传播行为之一的 Propagation.REQUIRES_NEW @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface EnableTranscation { // 是否开启新的事务 boolean openNewTx() default false; }

4. 动态代理处理器

/**事务动态代理处理器 * @since 1.0 * @author zongf * @created 2019-07-18 */ public class TranscationHandler implements InvocationHandler { private Object target; public TranscationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取当前数据库连接 TxConnection txConnection = DbConnUtil.getTxConnection(); // 保存老的连接对象 TxConnection oldTxConnection = null; try { // 获取目标对象方法 Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes()); // 看当前方法是否开启了事务 boolean enableTx = targetMethod.isAnnotationPresent(EnableTranscation.class); // 如果开启事务, 则设置当前连接为手动提交事务 if (enableTx) { // 获取注解信息 EnableTranscation annotation = targetMethod.getAnnotation(EnableTranscation.class); // 获取是否开启新事务 boolean openNewTx = annotation.openNewTx(); if (!txConnection.getConnection().getAutoCommit()) { //为false, 表示已经开启了事务 if (openNewTx) { // 如果需要开启的事务 // 保存原数据库连接 oldTxConnection = txConnection; // 获取新的连接 txConnection = new TxConnection(DbConnUtil.getConnection(false), this.toString()); // 替换当前线程中的数据库连接 DbConnUtil.getLocalTxConnection().set(txConnection); } } else { // 为true, 表示未开启事务 // 没有开启事务, 设置自动提交为false. 表示已经开始了事务 txConnection.getConnection().setAutoCommit(false); txConnection.setCreator(this.toString()); } } // 执行目标方法 Object object = targetMethod.invoke(this.target, args); // 如果事务是当前handler对象创建, 那么提交事务 if (this.toString().equals(txConnection.getCreator())) { txConnection.getConnection().commit(); } return object; } catch (Exception e) { if (txConnection != null && this.toString().equals(txConnection.getCreator())) { if (txConnection.getConnection() != null && !txConnection.getConnection().isClosed()) { txConnection.getConnection().rollback(); txConnection.getConnection().setAutoCommit(true); } } throw new RuntimeException("发生异常, 事务已回滚!", e); } finally { // 释放数据库连接 if (txConnection != null && this.toString().equals(txConnection.getCreator())) { DbConnUtil.release(txConnection.getConnection()); } // 如果新连接不为null, 则表示开启了新事务. 则回滚原连接 if (oldTxConnection != null) { DbConnUtil.getLocalTxConnection().set(oldTxConnection); } } } }

5. ServiceFactory 工厂

创建Service 工厂类, 用于模拟Spring 容器. 当目标Service中包含@EnableTransaction 注解时, 创建Service 的动态代理, 否则创建Service 对象.

/** Service工厂, 模拟spring 容器 * @since 1.0 * @author zongf * @created 2019-07-18 */ public class ServiceFactory { /** 获取Service 实例 * @param clz Service 实现类类型 * @return T Service 对象或动态代理对象 * @since 1.0 * @author zongf * @created 2019-07-18 */ public static <T> T getService(Class<T> clz) { T t = null; try { t = clz.newInstance(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("创建对象失败"); } // 判断不能是接口, 接口不能创建实现类 if(clz.isInterface()){ throw new RuntimeException("接口不能创建实例!"); } // 是否开启动态代理 boolean enableTx = false; // 遍历所有非私有方法, 如果方法有@EnableTx注解, 则说明需要创建代理 Method[] methods = clz.getMethods(); for (Method method : methods) { if (method.getAnnotation(EnableTranscation.class) != null) { enableTx = true; break; } } // 如果需要创建代理, 则返回代理对象 if (enableTx) { return (T) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new TranscationHandler(t)); } return t; } }

6. 声明式事务测试

测试用例, 笔者借助于之前写的BaseDao来简化基本步骤的开发.

1.1 定义接口

public interface IMixService { // 模拟正常 void success(); // 模拟异常操作, 事务回滚 void error(); void show(); }

6.2 定义实现类

public class MixService implements IMixService { private IUserService userService = ServiceFactory.getService(UserService.class); private IPersonService personService = ServiceFactory.getService(PersonService.class); @EnableTranscation @Override public void success() { this.userService.save(new UserPO("user-01", "123456")); this.personService.save(new PersonPO("person-01", "abcdefg")); } @EnableTranscation @Override public void error() { this.userService.save(new UserPO("user-01", "123456")); this.personService.save(new PersonPO("person-01", "abcdefg")); // 模拟异常会馆 int a = 1/0; } @Override public void show() { List<UserPO> userPOS = this.userService.queryAll(); List<PersonPO> personPOS = this.personService.queryAll(); System.out.println("\n****** t_user: *****"); userPOS.forEach(System.out::println); System.out.println("\n****** t_person: *****"); personPOS.forEach(System.out::println); } }

6.3 测试用例

public class MixService implements IMixService { private IUserService userService = ServiceFactory.getService(UserService.class); private IPersonService personService = ServiceFactory.getService(PersonService.class); @EnableTranscation @Override public void success() { this.userService.save(new UserPO("user-01", "123456")); this.personService.save(new PersonPO("person-01", "abcdefg")); } @EnableTranscation @Override public void error() { this.userService.save(new UserPO("user-01", "123456")); this.personService.save(new PersonPO("person-01", "abcdefg")); // 模拟异常会馆 int a = 1/0; } @Override public void show() { List<UserPO> userPOS = this.userService.queryAll(); List<PersonPO> personPOS = this.personService.queryAll(); System.out.println("\n****** t_user: *****"); userPOS.forEach(System.out::println); System.out.println("\n****** t_person: *****"); personPOS.forEach(System.out::println); } }

最新回复(0)