[由零开始] 十一、手写实现简易Spring框架事务控制
手写实现简易Spring框架事务控制
现在我们开始利用AOP思想手写实现简易的Spring框架的事务控制
那么 什么是事务呢?
事务 一般是指要做或者已经做的事,就是由一系列对系统中数据进行访问与更新的操作所组成的一个程序 执行逻辑单元(Unit)。
事务的四个特性
1 、原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
3 、隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
根据这四个特性 我们明白 我们至少需要commit与rollback两种方法来实现事务控制
commit 用于提交事务 rollback用于回滚事务 我们还需要一个方法来开启事务
传统方式手写事务
这是一串示例代码
userMapper中有两个方法 分别是insert和update
public class UserServiceImpl implements UserService {
@Aotuwried
private UserMapper userMapper;
@Override
public void user(){
User user = new User;
user.setId(1);
user.setName("mrsoon");
userMapper.insert(user);
user.setName("mrsoon2");
userMapper.update(user);
}
}
如果不添加事务控制 我们在update前放入一个错误代码
@Override
public void user(){
User user = new User;
user.setId(1);
user.setName("mrsoon");
userMapper.insert(user);
user.setName("mrsoon2");
//加入一行必然报错的代码
int i =1/0;
userMapper.update(user);
}
执行之后 程序报错了 然后 依然会insert一条mrsoon数据 而不会更改为mrsoon2这明显不符合原子性和一致性
那么我们如何加入事务处理呢?
手写事务
我们最开始的说了事务的四个特性
原子性、一致性、隔离性、持久性
首先原子性与一致性 我们首先要保证的就是 这个程序的所有与数据库的操作 必须全部执行 如果其中有一条执行失败 那么全部回滚
隔离性 保证每次执行时 一个事务对应一个线程 这样每次的事务控制互补干扰
持久性 一次修改永久有效
既然已经明白了这些 我们就可以对刚才的代码进行改造
创建ConnectionUtils
public class ConnectionUtils {
//存储当前线程的连接
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//从当前线程获取连接
public Connection getCurrentThreadConn() throws SQLException {
//判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取⼀个连接绑定到当前线程
Connection connection = threadLocal.get();
if(connection == null) {
// 从连接池拿连接并绑定到线程
connection = DruidUtils.getInstance().getConnection();
// 绑定到当前线程
threadLocal.set(connection);
}
return connection;
}
}
事务控制本质上就是手动控制 JDBC 的Connection 事务,但要注意将Connection和当前线程绑定(即保证⼀个线程只有⼀个Connection,这样操作才针对的是同⼀个 Connection,进⽽控制的是同⼀个事务)
public class TransactionManager {
//@Autowried (这里把@Autowried注掉是因为后面我们还要手写@Autowried注解)
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启事务
public void beginTransaction() throws SQLException {
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
// 提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentThreadConn().commit();
}
// 回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentThreadConn().rollback();
}
}
这里 我们定义了一个事务控制器TransactionManager 里面就包含了我们最开始说的
开始事务、commit、rollback方法
那么接下来
我们就可以对原有代码进行改造
@Override
public void user(){
try{
// 开启事务
transactionManager.beginTransaction();
// 调⽤原有业务逻辑
User user = new User;
user.setId(1);
user.setName("mrsoon");
userMapper.insert(user);
user.setName("mrsoon2");
//加入一行必然报错的代码
int i =1/0;
userMapper.update(user);
// 提交事务
transactionManager.commit();
}catch(Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 异常向上抛出,便于servlet中捕获
throw e.getCause();
}
}
这样我们就对我们原本的方法添加完事务控制了
AOP改造
仔细思考之后我们发现
事务控制我们会需要修改业务代码
try{
// 开启事务
transactionManager.beginTransaction();
// 调⽤原有业务逻辑(这里才是业务代码 此上和此下都是横切逻辑)
XXXXXXXXXXXXX
// 提交事务
transactionManager.commit();
}catch(Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 异常向上抛出,便于servlet中捕获
throw e.getCause();
}
经过分析我们发现 只有xxxxx那里才是真正和业务有关的代码
此上和此下都只是横切逻辑
我们是不是可以利用AOP的思想对我们之前的代码进行一个改造
也就是说 如果业务的实现上利用动态代理来处理 这样我们就可以不用去在任何事务控制前后都加上这些重复的与业务无关的横切代码了
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public Object getProxy(Object target) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
// 开启事务
transactionManager.beginTransaction();
// 调⽤原有业务逻辑
result = method.invoke(target,args);
// 提交事务
transactionManager.commit();
}catch(Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 异常向上抛出
}
return result;
}
});
}
}
这里我们建立了一个代理工厂
后续我们会写一个@Transactional注解 通过注解扫描来确定需要代理的类
也可以写个xml来控制
这里我就不在定义这些了
本质上还是要体会其中的思想 实现不是很重要
毕竟实现的方式可以有很多种