spring事务简化版实现
spring事务
注意jdbc的事务默认是自动提交的
dao层
@Repository
public class OrderDaoImpl implements OrderDao {
@Override
public List<Order> findOrders() {
List<Order> ordersList = new ArrayList<>();
ordersList.add(new Order(111, "图书", "2020-11-20", 128));
ordersList.add(new Order(112, "苹果", "2020-11-20", 12811));
ordersList.add(new Order(113, "锤子", "2020-11-20", 12821));
ordersList.add(new Order(114, "小米", "2020-11-20", 12800));
System.out.println("查询订单......");
return ordersList;
}
@Override
public int addOrder(Order order) {
System.out.println("新增订单......");
return 1;
}
@Override
public int transfer(String name, Double money) {
Connection conn = null;
int result = 0;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3305/spring_tx?characterEncoding=utf-8", "root", "ahulml005");
PreparedStatement ps = conn.prepareStatement("update t_account set money=money+? where name=?");
ps.setObject(1, money);
ps.setObject(2, name);
result = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return result;
}
}
service层
mysql的事务默认是自动提交的,下面的代码中有两个事务(使用的不是同一个connection),如果中间执行异常两个事务都需要回滚
@Override
public int transfer(String fromName, String toName, double Money) {
int transfer1 = orderDao.transfer(fromName, -Money);
System.out.println(transfer1 > 0 ? "转账成功" : "转账失败");
System.out.println("=============================");
int transfer2 = orderDao.transfer(toName, Money);
System.out.println(transfer2 > 0 ? "收款成功" : "收款失败");
return 1;
}
要做成一个事务必须使用同一个connection
实现
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) //定义在属性上面
@Documented //会被javadoc处理
public @interface Transactional {
}
简化版,只用在方法上
在实例化class放入容器的那一步进行操作,类方法中带@Transactinoal注解的需要事务控制,给带有事务的方法做代理,前置、后置以及异常增强(框架底层使用的是CGLIB代理)
使用Cglib代理
public class CglibProxy {
//要代理的类
private Class<?> targetClass;
//需要被代理的方法名
List<String> declaredMethods = new ArrayList<>();
public CglibProxy(Class<?> targetClass, List<String> declaredMethods) {
this.targetClass = targetClass;
this.declaredMethods = declaredMethods;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用目标方法
Object result = null;
if(declaredMethods.contains(method.getName())) {
System.out.println("开启事务,关闭自动提交......");
try {
result = methodProxy.invokeSuper(o, args);
System.out.println("手动提交事务......");
} catch (Throwable throwable) {
System.out.println("回滚事务......");
// throwable.printStackTrace();
} finally {
}
} else {
result = methodProxy.invokeSuper(o, args);
}
return result;
}
});
return enhancer.create();
}
}
ClassPathXmlApplicationContext中在将class实例化对象注入容器过程中进行增强
//判断实例化的这个类是否带了注解
if(c.isAnnotationPresent(Controller.class) || c.isAnnotationPresent(Service.class) || c.isAnnotationPresent(Repository.class)) {
//实例化
Object instance = c.newInstance();
//判断类方法中是否有带注解@Transactional的,需要事务控制
List<String> declaredMethods = new ArrayList<>();
Method[] methods = c.getDeclaredMethods();
if(methods != null && methods.length > 0) {
for (Method method : methods) {
if(method.isAnnotationPresent(Transactional.class)) {
declaredMethods.add(method.getName());
}
}
//这些方法需要事务
if(declaredMethods.size() > 0) {
CglibProxy cglibProxy = new CglibProxy(c, declaredMethods);
//获取Cglib动态代理的对象
instance = cglibProxy.getProxyInstance();
}
}
//省略...
}
事务管理类TransactionManager
模仿mybatis的DataSourceTransactionManager
使用ThreadLocal保证转账和收款使用的是同一个connection,
public class TransactionManager {
/**
* 本地线程局部变量
* 假设程序是多线程的,有很多个线程访问,A线程放的变量只有它自己能使用,其他的线程无法获取
* 同一个线程存取的数据是同一个
*/
static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
static {
Connection connection = getConnection();
threadLocal.set(connection);
}
public static Connection getThreadLocalConnection() {
return threadLocal.get();
}
private static Connection getConnection() {
try {
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3305/spring_tx?characterEncoding=utf-8", "root", "ahulml005");
return conn;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
业务代码中使用的connection替换为ThreadLocal取出的
@Transactional
@Override
public int transfer(String name, Double money) {
Connection conn = null;
int result = 0;
try {
conn = TransactionManager.getThreadLocalConnection();
PreparedStatement ps = conn.prepareStatement("update t_account set money=money+? where name=?");
ps.setObject(1, money);
ps.setObject(2, name);
result = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("执行数据库操作......");
return result;
}
Cglib中的connection为ThreadLocal中取出的
public class CglibProxy {
//要代理的类
private Class<?> targetClass;
//需要被代理的方法名
List<String> declaredMethods = new ArrayList<>();
public CglibProxy(Class<?> targetClass, List<String> declaredMethods) {
this.targetClass = targetClass;
this.declaredMethods = declaredMethods;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用目标方法
Object result = null;
if(declaredMethods.contains(method.getName())) {
System.out.println("开启事务,关闭自动提交......");
Connection connection = TransactionManager.getThreadLocalConnection();
connection.setAutoCommit(false);
try {
result = methodProxy.invokeSuper(o, args);
System.out.println("手动提交事务......");
connection.commit();
} catch (Throwable throwable) {
System.out.println("回滚事务......");
connection.rollback();
// throwable.printStackTrace();
} finally {
}
} else {
result = methodProxy.invokeSuper(o, args);
}
return result;
}
});
return enhancer.create();
}
}
业务代码中错误能成功回滚
将数据库配置提取出来
将配置放在db.properties文件中
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3305/spring_tx?characterEncoding=utf-8
jdbc.username=root
jdbc.password=ahulml005
applicationContext.xml添加解析
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<component-scan base-package="com.hodor"/>
<resources locatioin="db.properties"/>
</beans>
使用解析类解析
package org.springframework.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author :hodor007
* @date :Created in 2021/2/17
* @description :
* @version: 1.0
*/
public class DbConfigParser {
public static String driver;
public static String url;
public static String username;
public static String password;
private static String applicationContext;
public DbConfigParser() {
}
//获取配置文件数据库配置
public static String getProperties(String springconfig) {
String properties = "";
InputStream is = null;
try {
SAXReader reader = new SAXReader();
//使用当前线程的类加载器得到得到流对象
// is = SpringConfigPaser.class.getClassLoader().getResourceAsStream(springconfig);
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(springconfig);
Document document = reader.read(is);
//得到根节点beans
Element rootElement = document.getRootElement();
Element element = rootElement.element("resources");
Attribute attribute = element.attribute("locatioin");
properties = attribute.getText();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return properties;
}
static{
try {
Properties properties = new Properties();
properties.load(DbConfigParser.class.getClassLoader().getResourceAsStream(getProperties("applicationContext.xml")));
driver = (String) properties.get("jdbc.driver");
url = (String) properties.get("jdbc.url");
username = (String) properties.get("jdbc.username");
password = (String) properties.get("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
}
}
}