SpringBoot 动态切换数据源,注意问题(JPA,Mybatis兼容)

如何实现多数据源配置网上一大堆,不重复说了,说说遇到的关键问题,那就是在一个Service方法中调用不同数据源的dao,总是不成功,研究了好几天总算搞定了,给大家分享下,有不对的地方欢迎指正
我是采用的动态路由DataSource的方式。

JPA多数据源
注意事项, 在同一个方法中调用自定义注解 aop 切换数据源

aop操作的 仅仅只是 替换 TreadLocal 中 线程私有的 DataSource 的key
自定义注解的使用,

AbstractRoutingDataSource 抽象类
这个类是spring 2.0 提供的 动态路由DataSource,原理其实 就是重写了一个DataSource ,在 自己实现getConnection方法,部分源码

@Nullable
private Map<Object, Object> targetDataSources;   //多数据源多个DataSource getConnection时 通过我们自己的实现类实现获取key的方法,返回不同DataSource
@Nullable
private Object defaultTargetDataSource;   //配置的默认 数据源
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;

我们自己写一个类 继承这个抽象类,在实现一个 determineCurrentLookupKey 方法,给父类提供一个获取DataSource的Key的方法 就实现了返回不同DataSource的功能,

动态切换的 事物管理,

如果在一个Service中调用不同的 数据源的方法,切换失败,
这是因为事物环境 ,会导致数据源切换失败,如果是一个方法内调用的dao都是一个数据源,就不用加
aop切换数据源key是执行了的,但是却没有 调用 getConnection
​在一个事物中不配置事物的传播级别,是不会开启一个新的事务。因为spring默认的事物级别是 PROPAGATION_REQUIRED 如果不开启一个新的事务,就不会数据源的切换。这就说明了为什么,多次切换数据源获取到的session的hashCode相同的。
因为hibernate的session是transaction绑定的。

@Transactional(propagation=Propagation.REQUIRES_NEW) 在已有事物中 调用其他事物方法 ,将当前事物挂起,执行其他事物,执行完之后再恢复
缺点:无法回滚 异常之前的事物。
hibernate 是通过 session 和数据库交互, 这个session 不是web的那个session。默认实现是SessionImpl, 就是通过这个类来操作Session

通过 EntityManager 获取Session
SessionImplementor session =entityManager.unwrap(SessionImplementor.class);
System.out.println(session.connection());

#断开链接的方式:
1,Spring Data Jpa 的 JpaRepository 接口有个默认实现类,SimpleJpaRepository,我们自己写的接口是要去继承JpaRepository接口才能使用Jpa帮我们实现的方法, 这些方法都是通过SimpleJpaRepository类实现的,但是这个类默认是所有方法都开启了事物,所以 Spring Data Jpa是默认开启事物的。这就导致了之前为什么切换失败,因为在同一个事物中调用不同数据源的方法,事物环境是没有改变的
2,Jpa 的 EntityManager 是线程绑定的, hibernate 的session也是线程私有的。hibernate的Session默认实现是一个叫做
SessionImpl的类
https://www.oschina.net/uploads/doc/hibernate-3.2/org/hibernate/impl/SessionImpl.html#disconnect()

前面那部分是 在不改动原有 hibernate Session的 情况下,实现一个方法切换不同数据源,很繁杂不科学

经过摸索,终于找到了相对好点的解决方案,
那就是在aop 切面处理的时候, 执行完 被增强方法之后,手动 断开Session与当前JDBC连接的连接,下次在切换的时候就会重新获取连接,(经测试,此方法事物管理会失效)

SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
//最关键的一句代码, 手动断开连接,不用重新设置 ,会自动重新设置连接。
session.disconnect();

一定要多看文档
在断开之后会自动重新获取连接,这样就可以成功的在 一个方法里切换不同数据源

最终解决方案

为了去除了使用断开session链接的方法, 采用了动态加载数据源的时候,为每个数据源动态注册一个事物管理器DataSourceTransactionManager
最终达到了,可以适应任何连接池数据源的配置,通过yml 进行配置即可使用多数据源,也解决了事物管理的问题

    public Map ymlConfig() throws ClassNotFoundException {
        Map<Object, Object> dataSourceMap = new LinkedHashMap<>();
        DataSource dataSourceConfig = dataSource();
        ymlConfig.getDatasource().forEach((key, properties) -> {
            DataSource dataSource = null;
            try {
                dataSource = DataSourceBuilder.create().type((Class<? extends DataSource>) ClassLoader.getSystemClassLoader().loadClass(ymlConfig.getType())).build();
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            //spring默认单例,深拷贝达到配置继承的效果
            //不同连接池的DataSource 属性可能在set或初始化时不能为空,否则会copy失败,需要根据要求调整yml配置
            BeanUtils.copyProperties(dataSourceConfig, dataSource);
            bind(dataSource, properties);
            dataSourceMap.put(key, dataSource);
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
            beanUtil.registerBean("transactionManager"+key,DataSourceTransactionManager.class,dataSource);
            // 注释的这段代码支持任意数据源的配置,但是无法实现连接池配置
//             DataSourceProperties dataSourceProperties = new DataSourceProperties();
            dataSourceProperties.setUrl(String.valueOf(properties.get("url")));
            dataSourceProperties.setUsername((String.valueOf(properties.get("username"))));
            dataSourceProperties.setPassword(String.valueOf(properties.get("password")));
            try {
                if (properties.get("type") != null)
                    dataSourceProperties.setType((Class<? extends DataSource>) ClassLoader.getSystemClassLoader().loadClass(String.valueOf(properties.get("type"))));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            dataSourceProperties.setDriverClassName(String.valueOf(properties.get("driver-class-name")));
            DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().build();
        });
        return dataSourceMap;
    }

Mybatis 多数据源

Jpa数据源 配置成功后 可以直接 进行使用 对mybatis 兼容。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值