为了解决数据库瓶颈,分散数据库压力,读写分离经常被使用到。接下来我们就来谈一谈,在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@Inheritedpublic @interface RepositoryBeanNamePrefix { String value();}
修改RepositoryConfigurationDelegate,添加对应的逻辑:
String beanName = configurationSource.generateBeanName(beanDefinition);AnnotationMetadata metadata = (AnnotationMetadata) configurationSource.getSource();//判断配置类是否使用primary进行了标注,如果有,就设为primaryif(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扫描的配置类:
使用时我们可以通过名称注入的方式,使用读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 { }
@RestController@RequestMapping("/user")public class UserController { @Autowired private UserRepository userRepository; @Autowired private UserReadRepository readRepository; @Autowired @Qualifier("seconduserRepository") private UserRepository seconduserRepository;}
这样就达到我们读写分离的目的了。
总结
本文提供了Jpa多Repository连接多数据源的实现方式,及两种读写分离方案。以上列出的只是关键代码,很难通过语言完整详细的描述整个过程。大家如果有什么疑问,欢迎讨论,最好还是下载示例代码自己看看代码也许会清楚很多。