如何不加事务也能做到同一个线程内使用同一个数据库连接?

如何不加事务也能做到同一个线程内使用同一个数据库连接?

这个问题一般在使用数据库的临时表的时候会碰到。mysql的临时表是直接“挂在”数据库连接(即connection)上的,这就意味着如果不是同一个数据库connection,对应的临时表就会不存在,强行使用会直接报错。
所以解决这个问题的唯一的出发点就是“要保证同一个线程内反复和数据库交互都是同一个数据库连接”。
既然涉及“同一线程”,那ThreadLocal肯定跑不了(Spring的声明式事务也是用ThreaLocal做的)。现在就是在哪里用ThreadLocal的问题。
其实不用想的太复杂,直接在数据源那里"做手脚"即可,这里假设你用的是druid数据源(用什么数据源不是重点),对应的数据源类为com.alibaba.druid.pool.DruidDataSource。所以你的配置大概会是这样:

spring:
  application:
    name: xxxxxx
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: ${jdbc.url}
    username: ${jdbc.username}
    password: ${jdbc.password}
    type: com.alibaba.druid.pool.DruidDataSource

现在只需要写一个自己的数据源就能解决问题,如下:

public class OneConnectionDataSource extends DruidDataSource implements SmartDataSource {

    /**
     * 当前线程是否一个线程用一个连接
     */
    public static ThreadLocal<Boolean> needOneConnectionThreadLocal = new ThreadLocal<>();
    /**
     * 持有当前线程的连接,键为数据源,值为对应的连接
     */
    public static ThreadLocal<Map<DataSource, Connection>> connectionThreadLocal = new ThreadLocal<>();

    @Override
    public Connection getConnection() throws SQLException {
        final Boolean needOneConnection = needOneConnectionThreadLocal.get();
        if (needOneConnection != null && needOneConnection) {
            // 如果当前线程需要保持一个连接,则从connectionThreadLocal取
            final DataSource dataSource = determineTargetDataSource();
            Map<DataSource, Connection> dataSourceConnectionMap = connectionThreadLocal.get();
            if (dataSourceConnectionMap == null) {
                dataSourceConnectionMap = new HashMap<>();
            }
            Connection connection = dataSourceConnectionMap.get(dataSource);
            if (connection != null && !connection.isClosed()) {
                return connection;
            }
            connection = super.getConnection();
            dataSourceConnectionMap.put(dataSource, connection);
            connectionThreadLocal.set(dataSourceConnectionMap);
            return connection;
        }
        return super.getConnection();
    }


    @Override
    public boolean shouldClose(Connection con) {
        final Boolean needOneConnection = needOneConnectionThreadLocal.get();
        // 如果需要一个线程用一个连接,通知spring不要关这个连接
        return needOneConnection == null || !needOneConnection;
    }
}

然后把配置改成自己写的数据源:

spring:
  application:
    name: xxxxxx
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: ${jdbc.url}
    username: ${jdbc.username}
    password: ${jdbc.password}
    type: com.xxx.xxx.xxx.OneConnectionDataSource

这里做几点说明:

  1. 为了表达方便,这里用了两个ThreadLocal,一个持有当前线程时候是否要维持一个数据库连接的标记,一个持有当前线程要使用的数据库连接。这里当然可以用一个ThreadLocal来做,持有一个对象,对象里同时放标记和连接即可。
  2. 必须要实现SmartDataSource接口的shouldClose方法来“通知”Spring不要关闭我们自己打开的连接,不然Spring可能会在org.springframework.jdbc.datasource.DataSourceUtils#doCloseConnection这个方法里关掉我们打开的数据库连接
  3. connectionThreadLocalDateSource做键是考虑到多数据源的场景需要区分数据库连接所属数据源
  4. 理论上目前是可以直接用了,在要保持同一个连接的方法最开始标记needOneConnectionThreadLocal,然后再方法最后释放并关闭连接即可。但这样每个方法都要加一样的代码,显然很麻烦也不优雅。最方便的用法肯定是在要保持同一个数据库连接的方法上加个注解,其它任何代码都不加。所以我们需要一个注解和一个切面。
注解和切面

首先定义一个注解:

/**
 * 使用这个注解可以保证方法内的所有数据库连接用的是同一个
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface OneConnection {
}

然后再写一个切面,切所有打了OneConnection注解的方法:

@Component
@Aspect
public class OneConnectionAdvice {

    @Around("@annotation(com.xxx.xxx.xxx.utils.OneConnection)")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
    	// 进入方法之前做好标记
        OneConnectionDataSource.needOneConnectionThreadLocal.set(true);
        final Object proceed = jp.proceed();
        // 释放ThreadLocal并关闭连接
        final Map<DataSource, Connection> dataSourceConnectionMap = OneConnectionDataSource.connectionThreadLocal.get();
        for (Connection connection : dataSourceConnectionMap.values()) {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        }
        OneConnectionDataSource.needOneConnectionThreadLocal.remove();
        return proceed;
    }

}

到这里就完美了,以后在遇到需要维持同一个数据库连接的需求时,只需要像Spring加事务一样在方法上加一个注解就能轻松获得“保持同一个数据库连接”的能力。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值