如何实现多数据源配置网上一大堆,不重复说了,说说遇到的关键问题,那就是在一个Service方法中调用不同数据源的dao,总是不成功,研究了好几天总算搞定了,给大家分享下,有不对的地方欢迎指正
我是采用的动态路由DataSource的方式。
JPA多数据源
注意事项, 在同一个方法中调用自定义注解 aop 切换数据源
aop操作的 仅仅只是 替换 TreadLocal 中 线程私有的 DataSource 的key
自定义注解的使用,
AbstractRoutingDataSource 抽象类
这个类是spring 2.0 提供的 动态路由DataSource,原理其实 就是重写了一个DataSource ,在 自己实现getConnection方法,部分源码
@Nullable
private Map
我们自己写一个类 继承这个抽象类,在实现一个 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 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;
}
Jpa数据源 配置成功后 可以直接 进行使用 对mybatis 兼容。