springboot jpa多Repository(数据源)及读写分离
为了解决数据库瓶颈,分散数据库压力,读写分离经常被使用到。接下来我们就来谈一谈,在spring boot 中如何使用jpa进行读写分离。本文提供示例源码。
在只有一个数据源的时候,我们可以很简单的使用有关JPA的自动配置来完成数据库操作。但是读写分离的时候显然我们至少要两个DataSource了,那么这些都是需要我们手动配置了,因为自动配置代码都是使用了条件注解的,我们手动配置之后就不再帮我们自动配置了。这个可以通过查看源码发现。
多Repository配置我们通过springboot,spring data的官网及spring boot源码,可以看到Repository的自动配置过程及配置关键点。有三个比较关键的Bean就是DataSource/EntityManager/TransactionManager,再有就是Repository的接口类所在的包。这方面的配置大家可以在网上很轻松的搜索到,也可以查看文档及源码自己完成,参见github上对应源码.
关键代码:
@Bean
@Primary
@ConfigurationProperties("spring.datasource.write")
public DataSourceProperties writeDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.write")
public DataSource writeDataSource() {
return writeDataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean writeEntityManagerFactory(
EntityManagerFactoryBuilder builder, @Qualifier("writeDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(User.class)
.properties(getVendorProperties(dataSource))
.persistenceUnit("write")
.build();
}
@Bean
@Primary
public PlatformTransactionManager writeTransactionManager(@Qualifier("writeEntityManagerFactory") LocalContainerEntityManagerFactoryBean writeEntityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager(writeEntityManagerFactory.getObject());
return transactionManager;
}
第一种方案
根据JPA多Repository的原理,我们可以知道,我们是可以通过配置扫描不同Repository接口类所在的包达到配置多Repository的目的。
这个这一点,第一种方案就很自然的可以想到。那就是通过接口继承的方式添加一个子接口,但是我们子接口里什么都不用操作,只维护原接口就可以了。这样我们让读的Repository扫描另外的Repository所在的包就可以轻松实现读写分离了。这种方案简单易实现,唯一的步骤就是多添加了一个接口。
如果是已经完成的项目,添加接口也有可能是非常庞大的一个工程。那么我们就可以利用第二种方案了。这是通过修改注册Bean时候的源码,让两次扫描到的Repository生成不同的名称注册到Spring容器当中,把原来的标记为Primary。通过源码我们可以发现生成Bean名称的类是org.springframework.data.repository.config.RepositoryConfigurationDelegate
.我们就拦截这个类中生成Bean名称的过程,把不同配置类扫描到的Repository使用不同的名称,虽然它们类型是一样的,我们可以通过名称限定来注入相应的Bean。我们可以添加一个自己的注解,用来给我们的配置类添加元信息。
自定义注解:
/**
* repository bean 名称的前缀
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RepositoryBeanNamePrefix {
String value();
}
修改RepositoryConfigurationDelegate,添加对应的逻辑:
String beanName = configurationSource.generateBeanName(beanDefinition);
AnnotationMetadata metadata = (AnnotationMetadata) configurationSource.getSource();
//判断配置类是否使用primary进行了标注,如果有,就设为primary
if(metadata.hasAnnotation(Primary.class.getName())){
beanDefinition.setPrimary(true);
}else if(metadata.hasAnnotation(RepositoryBeanNamePrefix.class.getName())){
// 再判断是否使用了RepositoryBeanNamePrefix进行了标注,如果有,添加名称前缀
Map<String,Object> prefixData = metadata.getAnnotationAttributes(RepositoryBeanNamePrefix.class.getName());
String prefix = (String) prefixData.get("value");
beanName = prefix + beanName;
}
配置多个Repository扫描的配置类:
@EnableJpaRepositories(basePackageClasses = UserRepository.class,
entityManagerFactoryRef = "writeEntityManagerFactory", transactionManagerRef = "writeTransactionManager")
@Primary
public class WriteConfiguration {
}
@EnableJpaRepositories(basePackageClasses = UserRepository.class,
entityManagerFactoryRef = "readEntityManagerFactory", transactionManagerRef = "readTransactionManager")
@RepositoryBeanNamePrefix("second")
public class SameRepositoryWriteConfiguration {
}
使用时我们可以通过名称注入的方式,使用读Repository:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private UserReadRepository readRepository;
@Autowired
@Qualifier("seconduserRepository")
private UserRepository seconduserRepository;
}
这样就达到我们读写分离的目的了。
总结本文提供了Jpa多Repository连接多数据源的实现方式,及两种读写分离方案。以上列出的只是关键代码,很难通过语言完整详细的描述整个过程。大家如果有什么疑问,欢迎讨论,最好还是下载示例代码自己看看代码也许会清楚很多。
示例代码 下载完后可以直接运行。因为数据库使用的时内存数据库h2,可以换成自己使用的数据库。
共同学习,写下你的评论
评论加载中...
作者其他优质文章