zoukankan      html  css  js  c++  java
  • 【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();
        }
    }
  • 相关阅读:
    webpack和webpack-dev-server安装配置
    webpack和webpack-dev-server安装配置
    Json和Jsonp
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    读undo 导致db file sequential read
    direct path read 研究
    SpringCloud的版本
    521忘记送大家礼物了,补上!
  • 原文地址:https://www.cnblogs.com/mindzone/p/15363490.html
Copyright © 2011-2022 走看看