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();
    }
}

业务代码中错误能成功回滚

image-20210217235518124

将数据库配置提取出来

将配置放在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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值