怎么做网站建设销售,长沙模板建站源码,惠州外贸网站建设推广,ps免费模板网站工作中我们时常会遇到跨数据库操作的情况#xff0c;这时候就需要配置多数据源#xff0c;那么如何配置呢#xff1f;常用的方式及其背后的原理支撑是什么呢#xff1f;我们下面来了解一下。
首先看看两种常见的配置方式#xff0c;分别为通过多个 Configuration 文件、利…工作中我们时常会遇到跨数据库操作的情况这时候就需要配置多数据源那么如何配置呢常用的方式及其背后的原理支撑是什么呢我们下面来了解一下。
首先看看两种常见的配置方式分别为通过多个 Configuration 文件、利用 AbstractRoutingDataSource 配置多数据源。
第一种方式多个数据源的 Configuration 的配置方法
这种方式的主要思路是不同 Package 下面的实体和 Repository 采用不同的 Datasource。所以我们改造一下我们的 example 目录结构来看看不同 Repositories 的数据源是怎么处理的。
第一步规划 Entity 和 Repository 的目录结构为了方便配置多数据源。
将 User 和 UserAddress、UserRepository 和 UserAddressRepository 移动到 db1 里面将 UserInfo 和 UserInfoRepository 移动到 db2 里面。如下图所示 我们把实体和 Repository 分别放到了 db1 和 db2 两个目录里面这时我们假设数据源 1 是 MySQLUser 表和 UserAddress 在数据源 1 里面那么我们需要配置一个 DataSource1 的 Configuration 类并且在里面配置 DataSource、TransactionManager 和 EntityManager。
第二步配置 DataSource1Config 类。
目录结构调整完之后接下来我们开始配置数据源完整代码如下
复制代码
Configuration
EnableTransactionManagement//开启事务
//利用EnableJpaRepositories配置哪些包下面的Repositories采用哪个EntityManagerFactory和哪个trannsactionManager
EnableJpaRepositories(basePackages {com.example.jpa.example1.db1},//数据源1的repository的包路径entityManagerFactoryRef db1EntityManagerFactory,//改变数据源1的EntityManagerFactory的默认值改为db1EntityManagerFactorytransactionManagerRef db1TransactionManager//改变数据源1的transactionManager的默认值改为db1TransactionManager)
public class DataSource1Config {/*** 指定数据源1的dataSource配置* return*/PrimaryBean(name db1DataSourceProperties)ConfigurationProperties(spring.datasource1) //数据源1的db配置前缀采用spring.datasource1public DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}/*** 可以选择不同的数据源这里我用HikariDataSource举例创建数据源1* param db1DataSourceProperties* return*/PrimaryBean(name db1DataSource)ConfigurationProperties(prefix spring.datasource.hikari.db1) //配置数据源1所用的hikari配置key的前缀public HikariDataSource dataSource(Qualifier(db1DataSourceProperties) DataSourceProperties db1DataSourceProperties) {HikariDataSource dataSource db1DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();if (StringUtils.hasText(db1DataSourceProperties.getName())) {dataSource.setPoolName(db1DataSourceProperties.getName());}return dataSource;}/*** 配置数据源1的entityManagerFactory命名为db1EntityManagerFactory用来对实体进行一些操作* param builder* param db1DataSource entityManager依赖db1DataSource* return*/PrimaryBean(name db1EntityManagerFactory)public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, Qualifier(db1DataSource) DataSource db1DataSource) {return builder.dataSource(db2DataSource)
.packages(com.example.jpa.example1.db1) //数据1的实体所在的路径
.persistenceUnit(db1)// persistenceUnit的名字采用db1
.build();}/*** 配置数据源1的事务管理者命名为db1TransactionManager依赖db1EntityManagerFactory* param db1EntityManagerFactory * return*/PrimaryBean(name db1TransactionManager)public PlatformTransactionManager transactionManager(Qualifier(db1EntityManagerFactory) EntityManagerFactory db1EntityManagerFactory) {return new JpaTransactionManager(db1EntityManagerFactory);}
}到这里数据源 1 我们就配置完了下面再配置数据源 2。
第三步配置 DataSource2Config类加载数据源 2。
复制代码
Configuration
EnableTransactionManagement//开启事务
//利用EnableJpaRepositories配置哪些包下面的Repositories采用哪个EntityManagerFactory和哪个trannsactionManager
EnableJpaRepositories(basePackages {com.example.jpa.example1.db2},//数据源2的repository的包路径entityManagerFactoryRef db2EntityManagerFactory,//改变数据源2的EntityManagerFactory的默认值改为db2EntityManagerFactorytransactionManagerRef db2TransactionManager//改变数据源2的transactionManager的默认值改为db2TransactionManager
)
public class DataSource2Config {/*** 指定数据源2的dataSource配置** return*/Bean(name db2DataSourceProperties)ConfigurationProperties(spring.datasource2) //数据源2的db配置前缀采用spring.datasource2public DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}/*** 可以选择不同的数据源这里我用HikariDataSource举例创建数据源2** param db2DataSourceProperties* return*/Bean(name db2DataSource)ConfigurationProperties(prefix spring.datasource.hikari.db2) //配置数据源2的hikari配置key的前缀public HikariDataSource dataSource(Qualifier(db2DataSourceProperties) DataSourceProperties db2DataSourceProperties) {HikariDataSource dataSource db2DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();if (StringUtils.hasText(db2DataSourceProperties.getName())) {dataSource.setPoolName(db2DataSourceProperties.getName());}return dataSource;}/*** 配置数据源2的entityManagerFactory命名为db2EntityManagerFactory用来对实体进行一些操作** param builder* param db2DataSource entityManager依赖db2DataSource* return*/Bean(name db2EntityManagerFactory)public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, Qualifier(db2DataSource) DataSource db2DataSource) {return builder.dataSource(db2DataSource).packages(com.example.jpa.example1.db2) //数据2的实体所在的路径.persistenceUnit(db2)// persistenceUnit的名字采用db2.build();}/*** 配置数据源2的事务管理者命名为db2TransactionManager依赖db2EntityManagerFactory** param db2EntityManagerFactory* return*/Bean(name db2TransactionManager)public PlatformTransactionManager transactionManager(Qualifier(db2EntityManagerFactory) EntityManagerFactory db2EntityManagerFactory) {return new JpaTransactionManager(db2EntityManagerFactory);}
}这一步你需要注意DataSource1Config 和 DataSource2Config 不同的是1 里面每个 Bean 都 Primary而 2 里面不是的。
第四步通过 application.properties 配置两个数据源的值代码如下
复制代码
###########datasource1 采用Mysql数据库
spring.datasource1.urljdbc:mysql://localhost:3306/test2?loggerSlf4JLoggerprofileSQLtrue
spring.datasource1.usernameroot
spring.datasource1.passwordroot
##数据源1的连接池的名字
spring.datasource.hikari.db1.pool-namejpa-hikari-pool-db1
##最长生命周期15分钟够了
spring.datasource.hikari.db1.maxLifetime900000
spring.datasource.hikari.db1.maximumPoolSize8
###########datasource2 采用h2内存数据库
spring.datasource2.urljdbc:h2:~/test
spring.datasource2.usernamesa
spring.datasource2.passwordsa
##数据源2的连接池的名字
spring.datasource.hikari.db2.pool-namejpa-hikari-pool-db2
##最长生命周期15分钟够了
spring.datasource.hikari.db2.maxLifetime500000
##最大连接池大小和数据源1区分开我们配置成6个
spring.datasource.hikari.db2.maximumPoolSize6第五步我们写个 Controller 测试一下。
复制代码
RestController
public class UserController {Autowiredprivate UserRepository userRepository;Autowiredprivate UserInfoRepository userInfoRepository;//操作user的RepositoryPostMapping(/user)public User saveUser(RequestBody User user) {return userRepository.save(user);}//操作userInfo的RepositoryPostMapping(/user/info)public UserInfo saveUserInfo(RequestBody UserInfo userInfo) {return userInfoRepository.save(userInfo);}
}第六步直接启动我们的项目测试一下。
请看这一步的启动日志 可以看到启动的是两个数据源其对应的连接池的监控也是不一样的数据源 1 有 8 个数据源 2 有 6 个。 如果我们分别请求 Controller 写的两个方法的时候也会分别插入到不同的数据源里面去。
通过上面的六个步骤你应该知道了如何配置多数据源那么它的原理基础是什么呢我们看一下
Datasource 与 TransactionManager、EntityManagerFactory 的关系和职责分别是怎么样的。
Datasource 与 TransactionManager、EntityManagerFactory 的关系分析
我们通过一个类的关系图来分析一下 其中
HikariDataSource 负责实现 DataSource交给 EntityManager 和 TransactionManager 使用EntityManager 是利用 Datasouce 来操作数据库而其实现类是 SessionImplEntityManagerFactory 是用来管理和生成 EntityManager 的而 EntityManagerFactory 的实现类是 LocalContainerEntityManagerFactoryBean通过实现 FactoryBean 接口实现利用了 FactoryBean 的 Spring 中的 bean 管理机制所以需要我们在 Datasource1Config 里面配置 LocalContainerEntityManagerFactoryBean 的 bean 的注入方式JpaTransactionManager 是用来管理事务的实现了 TransactionManager 并且通过 EntityFactory 和 Datasource 进行 db 操作所以我们要在 DataSourceConfig 里面告诉 JpaTransactionManager 用的 TransactionManager 是 db1EntityManagerFactory。
上一讲我们介绍了 Datasource 的默认加载和配置方式那么默认情况下 Datasource 的 EntityManagerFactory 和 TransactionManager 是怎么加载和配置的呢
默认的 JpaBaseConfiguration 的加载方式分析
上一讲我只简单说明了 DataSource 的配置其实我们还可以通过 HibernateJpaConfiguration找到父类 JpaBaseConfiguration 类如图所示 接着打开 JpaBaseConfiguration 就可以看到多数据源的参考原型如下图所示 通过上面的代码可以看到在单个数据源情况下的 EntityManagerFactory 和 TransactionManager 的加载方法并且我们在多数据源的配置里面还加载了一个类EntityManagerFactoryBuilder entityManagerFactoryBuilder也正是从上面的方法加载进去的看第 120 行代码就知道了。
那么除了上述的配置多数据源的方式还有没有其他方法了呢我们接着看一下。
第二种方式利用 AbstractRoutingDataSource 配置多数据源
我们都知道 DataSource 的本质是获得数据库连接而 AbstractRoutingDataSource 帮我们实现了动态获得数据源的可能性。下面还是通过一个例子看一下它是怎么使用的。
第一步定一个数据源的枚举类用来标示数据源有哪些。
复制代码
/*** 定义一个数据源的枚举类*/
public enum RoutingDataSourceEnum {DB1, //实际工作中枚举的语义可以更加明确一点DB2;public static RoutingDataSourceEnum findbyCode(String dbRouting) {for (RoutingDataSourceEnum e : values()) {if (e.name().equals(dbRouting)) {return e;}}return db1;//没找到的情况下默认返回数据源1}
}第二步新增 DataSourceRoutingHolder用来存储当前线程需要采用的数据源。
复制代码
/*** 利用ThreadLocal来存储当前的线程使用的数据*/
public class DataSourceRoutingHolder {private static ThreadLocalRoutingDataSourceEnum threadLocal new ThreadLocal();public static void setBranchContext(RoutingDataSourceEnum dataSourceEnum) {threadLocal.set(dataSourceEnum);}public static RoutingDataSourceEnum getBranchContext() {return threadLocal.get();}public static void clearBranchContext() {threadLocal.remove();}
}第三步配置 RoutingDataSourceConfig用来指定哪些 Entity 和 Repository 采用动态数据源。
复制代码
Configuration
EnableTransactionManagement
EnableJpaRepositories(//数据源的repository的包路径这里我们覆盖db1和db2的包路径basePackages {com.example.jpa.example1},entityManagerFactoryRef routingEntityManagerFactory,transactionManagerRef routingTransactionManager
)
public class RoutingDataSourceConfig {AutowiredQualifier(db1DataSource)private DataSource db1DataSource;AutowiredQualifier(db2DataSource)private DataSource db2DataSource;/*** 创建RoutingDataSource引用我们之前配置的db1DataSource和db2DataSource** return*/Bean(name routingDataSource)public DataSource dataSource() {MapObject, Object dataSourceMap Maps.newHashMap();dataSourceMap.put(RoutingDataSourceEnum.DB1, db1DataSource);dataSourceMap.put(RoutingDataSourceEnum.DB2, db2DataSource);RoutingDataSource routingDataSource new RoutingDataSource();//设置RoutingDataSource的默认数据源routingDataSource.setDefaultTargetDataSource(db1DataSource);//设置RoutingDataSource的数据源列表routingDataSource.setTargetDataSources(dataSourceMap);return routingDataSource;}/*** 类似db1和db2的配置唯一不同的是这里采用routingDataSource* param builder* param routingDataSource entityManager依赖routingDataSource* return*/Bean(name routingEntityManagerFactory)public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, Qualifier(routingDataSource) DataSource routingDataSource) {return builder.dataSource(routingDataSource).packages(com.example.jpa.example1) //数据routing的实体所在的路径这里我们覆盖db1和db2的路径.persistenceUnit(db-routing)// persistenceUnit的名字采用db-routing.build();}/*** 配置数据的事务管理者命名为routingTransactionManager依赖routtingEntityManagerFactory** param routingEntityManagerFactory* return*/Bean(name routingTransactionManager)public PlatformTransactionManager transactionManager(Qualifier(routingEntityManagerFactory) EntityManagerFactory routingEntityManagerFactory) {return new JpaTransactionManager(routingEntityManagerFactory);}
}路由数据源配置与 DataSource1Config 和 DataSource2Config 有相互覆盖关系这里我们直接覆盖 db1 和 db2 的包路径以便于我们的动态数据源生效。
第四步写一个 MVC 拦截器用来指定请求分别采用什么数据源。
新建一个类 DataSourceInterceptor用来在请求前后指定数据源请看代码
复制代码
/*** 动态路由的实现逻辑我们通过请求里面的db-routing来指定此请求采用什么数据源*/
Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {/*** 请求处理之前更改线程里面的数据源*/Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {String dbRouting request.getHeader(db-routing);DataSourceRoutingHolder.setBranchContext(RoutingDataSourceEnum.findByCode(dbRouting));return super.preHandle(request, response, handler);}/*** 请求结束之后清理线程里面的数据源*/Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {super.afterCompletion(request, response, handler, ex);DataSourceRoutingHolder.clearBranchContext();}
}同时我们需要在实现 WebMvcConfigurer 的配置里面把我们自定义拦截器 dataSourceInterceptor 加载进去代码如下
复制代码
/*** 实现WebMvcConfigurer*/
Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {Autowiredprivate DataSourceInterceptor dataSourceInterceptor;//添加自定义拦截器Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(dataSourceInterceptor).addPathPatterns(/**);WebMvcConfigurer.super.addInterceptors(registry);}
...//其他不变的代码省略}此处我们采用的是 MVC 的拦截器机制动态改变的数据配置你也可以使用自己的 AOP 任意的拦截器如事务拦截器、Service 的拦截器等都可以实现。需要注意的是要在开启事务之前配置完毕。
第五步启动测试。
我们在 Http 请求头里面加上 db-routingDB2那么本次请求就会采用数据源 2 进行处理请求代码如下
复制代码
POST /user/info HTTP/1.1
Host: 127.0.0.1:8089
Content-Type: application/json
db-routing: DB2
Cache-Control: no-cache
Postman-Token: 56d8dc02-7f3e-7b95-7ff1-572a4bb7d102
{ages:10}通过上面五个步骤我们可以利用 AbstractRoutingDataSource 实现动态数据源实际工作中可能会比我讲述的要复杂有的需要考虑多线程、线程安全等问题你要多加注意。 在实际应用场景中对于多数据源的问题我还有一些思考下面分享给你。
微服务下多数据源的思考还需要这样用吗
通过上面的两种方式我们分别可以实现同一个 application 应用的多数据源配置那么有什么注意事项呢我简单总结如下几点建议。
多数据源实战注意事项
此种方式利用了当前线程事务不变的原理所以要注意异步线程的处理方式此种方式利用了 DataSource 的原理动态地返回不同的 db 连接一般需要在开启事务之前使用需要注意事务的生命周期比较适合读写操作分开的业务场景多数据的情况下避免一个事务里面采用不同的数据源这样会有意想不到的情况发生比如死锁现象学会通过日志检查我们开启请求的方法和开启的数据源是否正确可以通过 Debug 断点来观察数据源是否选择的正确如下图所示 微服务下的实战建议
在实际工作中为了便捷省事更多开发者喜欢配置多个数据源但是我强烈建议不要在对用户直接提供的 API 服务上面配置多数据源否则将出现令人措手不及的 Bug。
如果你是做后台管理界面供公司内部员工使用的那么这种 API 可以为了方便而使用多数据源。
微服务的大环境下服务越小内聚越高低耦合服务越健壮所以一般跨库之间一定是是通过 REST 的 API 协议进行内部服务之间的调用这是最稳妥的方式原因有如下几点
REST 的 API 协议更容易监控更容易实现事务的原子性db 之间解耦使业务领域代码职责更清晰更容易各自处理各种问题只读和读写的 API 更容易分离和管理。