【JDBC】自定义事务注解实现

参考自:

https://blog.csdn.net/qq_28986619/article/details/94451889

数据源选型,我采用的是C3P0,下面是需要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.dzz</groupId>
    <artifactId>Persist-Framework</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        
    </dependencies>

</project>

c3p0-config.xml配置信息

我设有本机两套MySQL实例,一个8 一个5

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
<!--    <default-config>-->
<!--        <property name="user">zhanghanlun</property>-->
<!--        <property name="password">123456</property>-->
<!--        <property name="jdbcUrl">jdbc:mysql://localhost:3306/zhanghanlun</property>-->
<!--        <property name="driverClass">com.mysql.jdbc.Driver</property>-->
<!--        <property name="checkoutTimeout">30000</property>-->
<!--        <property name="idleConnectionTestPeriod">30</property>-->
<!--        <property name="initialPoolSize">3</property>-->
<!--        <property name="maxIdleTime">30</property>-->
<!--        <property name="maxPoolSize">100</property>-->
<!--        <property name="minPoolSize">2</property>-->
<!--        <property name="maxStatements">200</property>-->
<!--    </default-config>-->


    <!-- 命名的配置,可以通过方法调用实现 -->
    <named-config name="my-info">
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3308/my-info?serverTimezone=Asia/Shanghai</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <!-- 如果池中数据连接不够时一次增长多少个 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化数据库连接池时连接的数量 -->
        <property name="initialPoolSize">20</property>
        <!-- 数据库连接池中的最大的数据库连接数 -->
        <property name="maxPoolSize">25</property>
        <!-- 数据库连接池中的最小的数据库连接数 -->
        <property name="minPoolSize">5</property>
    </named-config>

    <named-config name="dev-base">
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3307/devbase?serverTimezone=Asia/Shanghai</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <!-- 如果池中数据连接不够时一次增长多少个 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化数据库连接池时连接的数量 -->
        <property name="initialPoolSize">20</property>
        <!-- 数据库连接池中的最大的数据库连接数 -->
        <property name="maxPoolSize">25</property>
        <!-- 数据库连接池中的最小的数据库连接数 -->
        <property name="minPoolSize">5</property>
    </named-config>
</c3p0-config>

获取全部数据源:

package cn.dzz.persist.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.io.File;
import java.net.URL;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DataSourceUtil {

    private DataSourceUtil() {}
    private static Map<String, DataSource> dataSourceMap;

    static {
        try {
            dataSourceMap = new HashMap<>();
            // 读取配置文件封装成文件对象
            URL resource = DataSourceUtil.class.getClassLoader().getResource("c3p0-config.xml");
            File f = new File(resource.getFile());

            // Dom4J转换成Dom对象
            SAXReader reader = new SAXReader();
            Document doc = reader.read(f);

            // 得到节点对象根据xml配置信息读取
            Element root = doc.getRootElement();
            List<Element> elements = root.elements("named-config");
            for (Element element : elements) {
                Attribute attribute = element.attribute("name");
                String value = attribute.getValue();
                // 逐一创建获取
                dataSourceMap.put(value, new ComboPooledDataSource(value));
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public static DataSource getDataSourceByConfigName(String configName) {
        return dataSourceMap.get(configName);
    }

    // 测试
    public static void main(String[] args) throws SQLException {
        System.out.println(getDataSourceByConfigName("my-info").getConnection());
    }
}

跨库暂时不考虑,要实现统一事务,这里要统一从线程中获取连接对象

package cn.dzz.persist.util;

import javax.sql.DataSource;
import java.sql.Connection;

public class ConnectionUtil {

    private static DataSource dataSource;
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    static {
        dataSource = DataSourceUtil.getDataSourceByConfigName("my-info");
    }

    public static Connection getConnection() {
        try {
            Connection connection = threadLocal.get();
            if (null == connection) {
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

用于标记声明事务类的注解

package cn.dzz.persist.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于标记需要事务操作的类
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TxConn {
}

JDK代理实现:

package cn.dzz.persist.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public class ProxyBean {
    private Object target;
    private Connection connection;

    public ProxyBean(Object target, Connection connection) {
        this.target = target;
        this.connection = connection;
    }

    public Object getBean() {
        Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                try {
                    connection.setAutoCommit(false);
                    method.invoke(target, args);
                    connection.commit();
                } catch (Exception e) {
                    connection.rollback();
                } finally {
                    connection.close();
                }
                return null;
            }
        });
        return o;
    }
}

然后通过工厂类去获取代理对象

package cn.dzz.persist.proxy;

import cn.dzz.persist.annotation.TxConn;
import cn.dzz.persist.util.ConnectionUtil;

import java.sql.Connection;

/**
 * 目标对象工程
 */
public class TargetBeanFactory {

    public static Object getTargetBean(Class<?> targetClass) throws Exception {
        Object t = targetClass.newInstance();
        if (t.getClass().isAnnotationPresent(TxConn.class)) {
            Connection connection = ConnectionUtil.getConnection();
            ProxyBean proxyBean = new ProxyBean(t , connection);
            return proxyBean.getBean();
        }
        return t;
    }

}

测试的业务类:

注意一些问题,就是里面的SQL执行和业务逻辑全都把异常抛出去,这样才能触发代理对象的事务

如果自己TryCatch了,直接方法调用里面自行处理异常,那代理的对象触发不到回滚就没意义了

package cn.dzz.persist.service;

import cn.dzz.persist.annotation.TxConn;
import cn.dzz.persist.util.ConnectionUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;


@TxConn
public class TestServiceImpl implements TestService{

    @Override
    public void updateTest() throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        final String sql = "UPDATE `my-info`.`school_class` SET `CLASS_NAME` = '修改1班级 - 0001' WHERE `CLASS_ID` = 1;
";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.execute();

        int a = 10 / 0;

        final String sql2 = "UPDATE `my-info`.`school_class` SET `CLASS_NAME` = '修改2班级 - 0002' WHERE `CLASS_ID` = 2;
";
        PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
        preparedStatement2.execute();
    }
}

测试事务是否有效:

import cn.dzz.persist.proxy.TargetBeanFactory;
import cn.dzz.persist.service.TestService;
import cn.dzz.persist.service.TestServiceImpl;
import cn.dzz.persist.util.JdbcUtil;
import org.junit.Test;

import java.util.List;
import java.util.Map;

public class TransactionTest {


    @Test
    public void transactionTest() throws Exception {
        TestService testService = (TestService)TargetBeanFactory.getTargetBean(TestServiceImpl.class);
        testService.updateTest();
    }
}
原文地址:https://www.cnblogs.com/mindzone/p/15363490.html