前面已经手写了数据库查询框架,现在开始进入正题手写事务管理器,什么是事务啥定义的这里不废话,请自行查阅书籍或网络文献。
这里来手写JDBC事务管理器,思考spring中使用注解@Transactional为什么就能进行事务控制?
好了直接进入正题,需要具备的基础知识如下:
- Java反射,注解技术
- 动态代理技术
- ThreadLocal(方便理解,就是一个HashMap,key是当前执行线程的ID,value则是自定义设置的值,每个线程有独立的值)
这里复习下JDBC事务是如何实现的:
Connection conn=getConnection();//自定义实现
try{
conn.setAutoCommit(false);
//做多个数据库update操作
//提交
conn.commit();
} catch (Exception e) {
conn.rollback();
}finally{
}
JDBC事务的逻辑就是
- 关闭自动提交
- 执行数据库更新业务
- 手动提交事务
- 若发生异常则回滚
可以看出跨Dao控制事务的原理,就是多个dao共同使用同一个连接不能被关系,且连接关闭自动提交。
我们思考spring的事务管理是在service层,对service层的每个方法进行代理拦截,判断该方法若有@Transactional事务注解,那么就为该方法增强,添加事务控制,实现事务service方法代理增加事务拦截,接下来我们开始实现
新建事务标识注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
String value() default "";
}
新建事务管理器,实现事务控制的几个方法行为,ThreadLocal可以保证每条线程获取的资源为同一个
public class TranscationalManager {
/**
* 开启事务
*/
final static ThreadLocal<Boolean> transcational = new ThreadLocal<Boolean>();
/**
* 数据库连接
*/
final static ThreadLocal<Connection> currentConnection = new ThreadLocal<Connection>();
static DataSource dataSource;
/**
* 事务开启
*/
static void openTranscational() {
transcational.set(true);
}
/**
* 是否开启事务 true开启/false未开启
*/
static boolean isOpenTranscational() {
boolean result = false;
if (transcational.get() == null) {
result = false;
} else {
result = transcational.get();
}
return result;
}
/**
* 事务关闭
*/
static void closeTranscational() {
transcational.set(false);
}
/**
* 事务提交
*/
static void commit() throws SQLException {
Connection conn = currentConnection.get();
if (conn != null) {
conn.commit();
}
}
/**
* 回滚
*/
static void rollback() throws SQLException {
Connection conn = currentConnection.get();
if (conn != null) {
conn.rollback();
}
}
/**
* 关闭连接,需要先关闭事务在调用此方法
*/
static void closeConnection() throws SQLException {
closeTranscational();
Connection conn = currentConnection.get();
if (conn != null) {
conn.close();
}
}
/**
* 释放资源
*/
static void release() {
transcational.remove();
currentConnection.remove();
}
}
接下来是数据库连接代理,目的是代理close方法,在开启事务时候,不关闭连接
public class ConnectionProxy implements InvocationHandler {
Connection targetConn;
public Connection proxyConnection(Connection targetConn) {
this.targetConn = targetConn;
Connection proxyConn = (Connection) Proxy.newProxyInstance(targetConn.getClass().getClassLoader(),
new Class[] { Connection.class }, this);
return proxyConn;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("close")) {
// 清除当前的数据库连接
if (isOpenTranscational()) {
// 若开启事务的 连接将不做操作
return null;
}
targetConn.setAutoCommit(true);
targetConn.close();
return null;
}
Object result = method.invoke(targetConn, args);
return result;
}
public Connection getTargetConn() {
return targetConn;
}
}
事务管理切面实现,service的方法有加事务注解的,那么将事务管理控制,这里仅仅实现了,如果当前存在事务,那么新的事务就加入该事务,类似spring 事务还有其他传播特性(如新开事务等),这里没有实现,剩下留给大家了,这里主要是学习思想
public class TranscationalInvocationHandler implements InvocationHandler {
public static Object proxyTranscational(Object service) throws InstantiationException, IllegalAccessException {
// 此处若有 @Transactional 代理
Class<? extends Object> clazz = service.getClass();
Class<?>[] interfaces = clazz.getInterfaces();
boolean isProxy = false;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Transactional.class)) {
isProxy = true;
break;
}
}
if (isProxy) {
return Proxy.newProxyInstance(clazz.getClassLoader(), interfaces,
new TranscationalInvocationHandler(service));
} else {
return service;
}
}
Object traget;
public TranscationalInvocationHandler(Object bean) {
this.traget = bean;
}
public Object getTraget() {
return traget;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
Method declaredMethod = traget.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
// 不需要事务
if (!declaredMethod.isAnnotationPresent(Transactional.class)) {
try {
return method.invoke(traget, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} finally {
// 原本不存在事务时候 才释放资源
if (!TranscationalManager.isOpenTranscational()) {
TranscationalManager.closeConnection();// 防止开发者在dao层面拿取数据库连接 没有关闭导致连接泄漏
TranscationalManager.release();
}
}
}
// 如果原本存在事务,那么加入事务
if (TranscationalManager.isOpenTranscational()) {
try {
// 数据库连接处理
return invoke = method.invoke(traget, args);
} catch (InvocationTargetException e) {
TranscationalManager.rollback();
throw e.getTargetException();
}
}
// 需要开启事务
try {
// 有事务注解的开启事务
TranscationalManager.openTranscational();
// 数据库连接处理
invoke = method.invoke(traget, args);
TranscationalManager.commit();
} catch (InvocationTargetException e) {
TranscationalManager.rollback();
throw e.getTargetException();
} finally {
// 关闭连接 释放资源
TranscationalManager.closeConnection();
TranscationalManager.release();
}
return invoke;
}
}
事务管理器获取数据库连接代理主入口
/**
* 获取数据库连接
*/
public static Connection getConnection() throws SQLException {
if (currentConnection.get() != null) {
return currentConnection.get();
}
Connection conn = dataSource.getConnection();
ConnectionProxy connectionProxy = new ConnectionProxy();
Connection proxyConnection = connectionProxy.proxyConnection(conn);
currentConnection.set(proxyConnection);
if(TranscationalManager.isOpenTranscational()){
conn.setAutoCommit(false);
}else{
conn.setAutoCommit(true);
}
return proxyConnection;
}
事务管理器代理service层接口
/**
* 代理service层的事务控制
*/
@SuppressWarnings("unchecked")
public static <T> T proxyTranscationalService(T serviceBean) {
try {
return (T) TranscationalInvocationHandler.proxyTranscational(serviceBean);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
到这里我们已经实现了事务管理器,接下来开始测试
准备:数据库mysql,在test用户下新建user表
CREATE TABLE `user` (
`user_name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
新建service层的接口与实现类
/**
* 测试接口类
*/
public interface UserService{
void modifyUsers() throws Exception;
}
这里实现service俩个dao操作,分别往同一张user表插入数据,获取2次连接,使用完分别关闭连接,并没有手动控制事务(在mybatis更新操作中,用户是屏蔽数据库连接的,这里为了与其他知识解耦,这里就简单JDBC操作来替代了)
public class UserServiceImpl implements UserService{
/**
* 加上事务注解
*/
@Transactional
@Override
public void modifyUsers() throws Exception {
//操作1与操作2 同属于一个事务,
//模拟dao操作1
Connection conn = TranscationalManager.getConnection();
PreparedStatement pst = conn.prepareStatement(" insert into user(user_name,password) values('姓名1','123' ) ");
pst.executeUpdate();
pst.close();
conn.close();
if(true) {
// throw new RuntimeException("强制异常,让后面的执行不了,会发现前者并没有插入到数据库库中");
}
//模拟dao操作2
Connection conn1 = getConnection();
PreparedStatement pst1 = conn1.prepareStatement(" insert into user(user_name,password) values('姓名2','123' ) ");
pst1.executeUpdate();
pst1.close();
conn1.close();
if(true) {
throw new RuntimeException("强制异常,让后面的执行不了,会发现前者并没有插入到数据库库中");
}
}
}
接下来就是主函数测试,执行后发生了异常,可以观察数据库表并没有新增数据,可以自行注释修改 if(true)的代码段,分别表示发生异常的时候,数据将回滚,如果没有异常,那么会发现数据库新增2条数据
/**
* 主函数测试
*/
public static void main(String[] args) {
//原本的service层的
UserService userService = new UserServiceImpl();
//事务管理代理
userService = proxyTranscationalService(userService);
try {
//执行更新
userService.modifyUsers();
} catch (Exception e) {
e.printStackTrace();
}
}
跨Dao事务在service层实现了事务管理,可以发现这些框架都是基于动态代理的灵活运用,将逻辑可以在各个切面进行插入,这就是所谓的切面编程AOP
另外本人该分类章节下所有文字与程序代码都是鄙人纯所写,鄙人其实从来没看过开源框架源码,纯靠使用开源框架的特性逆向思考分析它是如何实现的(那些框架在我眼中最初版本也就是我这样的,然后不断迭代,程序框架不断的提炼,使用设计模式程序分层抽象,方便扩展),最后附上源码下载链接:TranscationalManager.java-互联网文档类资源-CSDN下载
接下来可以思考下"分布式事务"是如何的实现的呢? 看完了点个关注,评论下,谢谢支持! 后续我将分享"如何手写分布式事务框架"