原生的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>
<property name="maxStatements">100
</property>
<property name="maxStatementsPerConnection">10
</property>
</named-config>
</c3p0-config>
2.2 数据库连接工具类
封装获取数据库连接和关闭数据库连接资源的方法
public class DbConnUtil {
private static final String c3p0PoolName
= "myC3p0Pool";
private static final DataSource dataSource
= new ComboPooledDataSource(c3p0PoolName
);
private static ThreadLocal
<TxConnection> txConnectionLocal
= new ThreadLocal<>();
public static Connection
getConnection(boolean autoCommitTx
) {
try {
Connection connection
= dataSource
.getConnection();
connection
.setAutoCommit(autoCommitTx
);
return connection
;
} catch (SQLException e
) {
e
.printStackTrace();
}
return null
;
}
public static TxConnection
getTxConnection() {
TxConnection txConnection
= null
;
if (txConnectionLocal
.get() == null
|| txConnectionLocal
.get().getConnection() == null
) {
txConnection
= new TxConnection(getConnection(true));
txConnectionLocal
.set(txConnection
);
} else {
txConnection
= txConnectionLocal
.get();
}
return txConnection
;
}
public static Connection
getLocalConnection() {
return getTxConnection().getConnection();
}
public static ThreadLocal
<TxConnection> getLocalTxConnection() {
return txConnectionLocal
;
}
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
;
}
3. 自定义开启事务注解
定义一个类似于spring @Transcation 的注解, 用于开启事务.openNewTx 用于模拟spring七种事务传播行为之一的 Propagation.REQUIRES_NEW
@Documented
@Target(ElementType
.METHOD
)
@Retention(RetentionPolicy
.RUNTIME
)
public @
interface EnableTranscation {
boolean openNewTx() default false;
}
4. 动态代理处理器
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()) {
if (openNewTx
) {
oldTxConnection
= txConnection
;
txConnection
= new TxConnection(DbConnUtil
.getConnection(false), this.toString());
DbConnUtil
.getLocalTxConnection().set(txConnection
);
}
} else {
txConnection
.getConnection().setAutoCommit(false);
txConnection
.setCreator(this.toString());
}
}
Object object
= targetMethod
.invoke(this.target
, args
);
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());
}
if (oldTxConnection
!= null
) {
DbConnUtil
.getLocalTxConnection().set(oldTxConnection
);
}
}
}
}
5. ServiceFactory 工厂
创建Service 工厂类, 用于模拟Spring 容器. 当目标Service中包含@EnableTransaction 注解时, 创建Service 的动态代理, 否则创建Service 对象.
public class ServiceFactory {
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;
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
);
}
}