19. jdbc实战-实现类Spring声明式事务

原生的jdbc 对事务管理也是比较繁琐的, 需要手工进行提交和回滚, 还要一堆try-catch. 而熟悉spring 的同学都知道, spring采用了声明式事务方式来管理事务, 使事务管理变得很简单. Spring 事务很强大, 笔者这里仅使用jdbc 来模拟简单的几个属性.

1. 声明式事务方案设计

  • 声明式事务主要依据java 动态代理实现
  • 通过将Connection 存放在ThreadLocal 变量中, 来解决并发问题. Spring 底层也是用的ThreadLocal.
  • 通过记录Connection 的创建者, 来解决事务的嵌套问题.
  • 自定义注解@EnableTranscation: 用于标明方法是否开启事务
  • Service工厂: 用于模拟Spring容器创建Bean过程, 如果Service 中包含使用@EnableTranscation修饰的方法, 则创建Service的代理对象, 否则返回Service 实例
  • 自定义Dao时, 不能直接创建Connection, 需要获取当前线程中保存的Connection.

2. 连接池管理

笔者对数据库的连接采用c3p0 连接池.

2.1 c3po 配置



<c3p0-config>
    
    <named-config name="myC3p0Pool">
        <property name="user">rootproperty>
        <property name="password">rootproperty>
        <property name="driverClass">com.mysql.jdbc.Driverproperty>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8property>

        
        <property name="acquireIncrement">10property>
        
        <property name="initialPoolSize">5property>
        
        <property name="minPoolSize">5property>
        
        <property name="maxPoolSize">50property>

        
        
        <property name="maxStatements">100property>
        
        <property name="maxStatementsPerConnection">10property>
    named-config>
c3p0-config>

2.2 数据库连接工具类

封装获取数据库连接和关闭数据库连接资源的方法

public class DbConnUtil {

    // c3P0配置名
    private static final String c3p0PoolName = "myC3p0Pool";

    // 配置数据源
    private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName);

    // 配置本地连接
    private static ThreadLocal<TxConnection> txConnectionLocal = new ThreadLocal<>();

    /** 获取数据库连接
     * @param autoCommitTx 是否开启提供提交事务
     * @return Connection 数据库连接
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static Connection getConnection(boolean autoCommitTx) {
        try {
            Connection connection = dataSource.getConnection();
            connection.setAutoCommit(autoCommitTx);
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @Description: 获取本线程连接
     * @return: Connection 数据库连接
     * @author: zongf
     * @time: 2019-06-26 14:37:00
     * @since 1.0
     */
    public static TxConnection getTxConnection() {
        TxConnection txConnection = null;

        // 如果ThreadLocal 中连接为空, 则创建新的连接
        if (txConnectionLocal.get() == null || txConnectionLocal.get().getConnection() == null) {
            txConnection = new TxConnection(getConnection(true));
            txConnectionLocal.set(txConnection);
        } else {
            txConnection = txConnectionLocal.get();
        }
        return txConnection;
    }


    /** 获取当前线程内的数据库连接
     * @return Connection
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static Connection getLocalConnection() {
        return getTxConnection().getConnection();
    }


    /** 获取当前线程的数据库连接对象
     * @return ThreadLocal
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static ThreadLocal<TxConnection> getLocalTxConnection() {
        return txConnectionLocal;
    }

    /** 当归还连接时, 需要设置自动提交事务为true.
     * @param connection
     * @return null
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static void release(Connection connection) throws SQLException {
        try {
            if (connection != null && !connection.isClosed()) {
                connection.setAutoCommit(true);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            connection.close();
        }
    }

}

2.3 定义事务连接对象

由于在事务嵌套时, 需要遵循哪一层动态代理开启的事务, 由哪一层动态代理负责事务的开启和回滚, 因此需要记录事务的开启者. 因此笔者创建了一个TxConnneciton 对象.

public class TxConnection {

    private Connection connection;

    private String creator;

   // 省略setter/getter 方法
}

3. 自定义开启事务注解

  • 定义一个类似于spring @Transcation 的注解, 用于开启事务.
  • openNewTx 用于模拟spring七种事务传播行为之一的 Propagation.REQUIRES_NEW
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableTranscation {

    // 是否开启新的事务
    boolean openNewTx() default false;
}

4. 动态代理处理器

/**事务动态代理处理器
 * @since 1.0
 * @author zongf
 * @created 2019-07-18
 */
public class TranscationHandler implements InvocationHandler {

    private Object target;

    public TranscationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 获取当前数据库连接
        TxConnection txConnection = DbConnUtil.getTxConnection();

        // 保存老的连接对象
        TxConnection oldTxConnection = null;

        try {

            // 获取目标对象方法
            Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());

            // 看当前方法是否开启了事务
            boolean enableTx = targetMethod.isAnnotationPresent(EnableTranscation.class);

            // 如果开启事务, 则设置当前连接为手动提交事务
            if (enableTx) {

                // 获取注解信息
                EnableTranscation annotation = targetMethod.getAnnotation(EnableTranscation.class);

                // 获取是否开启新事务
                boolean openNewTx = annotation.openNewTx();

                if (!txConnection.getConnection().getAutoCommit()) { //为false, 表示已经开启了事务
                    if (openNewTx) { // 如果需要开启的事务

                        // 保存原数据库连接
                        oldTxConnection = txConnection;

                        // 获取新的连接
                        txConnection = new TxConnection(DbConnUtil.getConnection(false), this.toString());

                        // 替换当前线程中的数据库连接
                        DbConnUtil.getLocalTxConnection().set(txConnection);

                    }
                } else { // 为true, 表示未开启事务
                    // 没有开启事务, 设置自动提交为false. 表示已经开始了事务
                    txConnection.getConnection().setAutoCommit(false);
                    txConnection.setCreator(this.toString());
                }
            }

            // 执行目标方法
            Object object = targetMethod.invoke(this.target, args);

            // 如果事务是当前handler对象创建, 那么提交事务
            if (this.toString().equals(txConnection.getCreator())) {
                txConnection.getConnection().commit();
            }

            return object;
        } catch (Exception e) {
            if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
                if (txConnection.getConnection() != null && !txConnection.getConnection().isClosed()) {
                    txConnection.getConnection().rollback();
                    txConnection.getConnection().setAutoCommit(true);
                }
            }
            throw new RuntimeException("发生异常, 事务已回滚!", e);
        } finally {
            // 释放数据库连接
            if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
                DbConnUtil.release(txConnection.getConnection());
            }

            // 如果新连接不为null, 则表示开启了新事务. 则回滚原连接
            if (oldTxConnection != null) {
                DbConnUtil.getLocalTxConnection().set(oldTxConnection);
            }
        }
    }

}

5. ServiceFactory 工厂

创建Service 工厂类, 用于模拟Spring 容器. 当目标Service中包含@EnableTransaction 注解时, 创建Service 的动态代理, 否则创建Service 对象.


/** Service工厂, 模拟spring 容器
 * @since 1.0
 * @author zongf
 * @created 2019-07-18
 */
public class ServiceFactory {

    /** 获取Service 实例
     * @param clz Service 实现类类型
     * @return T Service 对象或动态代理对象
     * @since 1.0
     * @author zongf
     * @created 2019-07-18 
     */
    public static <T> T getService(Class<T> clz) {

        T t = null;
        try {
            t = clz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建对象失败");
        }

        // 判断不能是接口, 接口不能创建实现类
        if(clz.isInterface()){
            throw new RuntimeException("接口不能创建实例!");
        }

        // 是否开启动态代理
        boolean enableTx = false;

        // 遍历所有非私有方法, 如果方法有@EnableTx注解, 则说明需要创建代理
        Method[] methods = clz.getMethods();
        for (Method method : methods) {
            if (method.getAnnotation(EnableTranscation.class) != null) {
                enableTx = true;
                break;
            }
        }

        // 如果需要创建代理, 则返回代理对象
        if (enableTx) {
            return (T) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new TranscationHandler(t));
        }
        return t;
    }

}

6. 声明式事务测试

测试用例, 笔者借助于之前写的BaseDao来简化基本步骤的开发.

1.1 定义接口


public interface IMixService {

    // 模拟正常
    void success();

    // 模拟异常操作, 事务回滚
    void error();

    void show();
}

6.2 定义实现类

public class MixService implements IMixService {

    private IUserService userService = ServiceFactory.getService(UserService.class);

    private IPersonService personService = ServiceFactory.getService(PersonService.class);

    @EnableTranscation
    @Override
    public void success() {

        this.userService.save(new UserPO("user-01", "123456"));

        this.personService.save(new PersonPO("person-01", "abcdefg"));

    }

    @EnableTranscation
    @Override
    public void error() {
        this.userService.save(new UserPO("user-01", "123456"));

        this.personService.save(new PersonPO("person-01", "abcdefg"));

        // 模拟异常会馆
        int a = 1/0;
    }

    @Override
    public void show() {
        List<UserPO> userPOS = this.userService.queryAll();
        List<PersonPO> personPOS = this.personService.queryAll();

        System.out.println("\n****** t_user: *****");
        userPOS.forEach(System.out::println);

        System.out.println("\n****** t_person: *****");
        personPOS.forEach(System.out::println);
    }
}

6.3 测试用例

public class MixService implements IMixService {

    private IUserService userService = ServiceFactory.getService(UserService.class);

    private IPersonService personService = ServiceFactory.getService(PersonService.class);

    @EnableTranscation
    @Override
    public void success() {

        this.userService.save(new UserPO("user-01", "123456"));

        this.personService.save(new PersonPO("person-01", "abcdefg"));

    }

    @EnableTranscation
    @Override
    public void error() {
        this.userService.save(new UserPO("user-01", "123456"));

        this.personService.save(new PersonPO("person-01", "abcdefg"));

        // 模拟异常会馆
        int a = 1/0;
    }

    @Override
    public void show() {
        List<UserPO> userPOS = this.userService.queryAll();
        List<PersonPO> personPOS = this.personService.queryAll();

        System.out.println("\n****** t_user: *****");
        userPOS.forEach(System.out::println);

        System.out.println("\n****** t_person: *****");
        personPOS.forEach(System.out::println);
    }
}

你可能感兴趣的:(19. jdbc实战-实现类Spring声明式事务)