原生的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 配置
root
root
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8
10
5
5
50
100
10
2.2 数据库连接工具类
封装获取数据库连接和关闭数据库连接资源的方法
public class DbConnUtil {
// c3P0配置名
private static final String c3p0PoolName = "myC3p0Pool";
// 配置数据源
private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName);
// 配置本地连接
private static ThreadLocal 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
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static ThreadLocal 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 getService(Class 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"));
}无锡妇科医院 http://www.bhnnk120.com/
@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 userPOS = this.userService.queryAll();
List 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 userPOS = this.userService.queryAll();
List 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);
}
}