项目已上传github:https://github.com/tihomcode/tihom-finance
SpringBoot+Spring Data JPA+JsonRpc+缓存等等实现的金融系统(一)——管理端的实现
SpringBoot+Spring Data JPA+JsonRpc+缓存等等实现的金融系统(二)——销售端的实现(JsonRpc和缓存)
SpringBoot+Spring Data JPA+JsonRpc+缓存等等实现的金融系统(三)——RSA签名、对账、定时任务实现
SpringBoot+Spring Data JPA+JsonRpc+缓存等等实现的金融系统(五)——TYK、HTTPS
JPA多数据源
JPA多数据源运行原理及源码查看
主备、读写分离(对账就可能是在备份库和读库执行的,下单操作就是在主库上执行的)
springboot自动配置过程
Spring Data JPA的文档 查看 Annotation-based Configuration这个块的内容
发现代码中这三个关键的bean
- 数据源
- 实体管理工厂(找到代码的实体类与数据库的表对应起来)
- 事务管理工厂
Spring Boot官方文档 查看Data Access的内容,不过文档的内容相对比较简单很难看出如何实现
查看源码
按照这个浏览顺序查看
@Configuration @ConditionalOnBean({DataSource.class}) @ConditionalOnClass({JpaRepository.class}) @ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class}) @ConditionalOnProperty( prefix = "spring.data.jpa.repositories", name = {"enabled"}, havingValue = "true", matchIfMissing = true ) @Import({JpaRepositoriesAutoConfigureRegistrar.class}) //这里表示配置要在HibernateJpaAutoConfiguration之后进行配置 @AutoConfigureAfter({HibernateJpaAutoConfiguration.class}) public class JpaRepositoriesAutoConfiguration { public JpaRepositoriesAutoConfiguration() { } }
进入JpaRepositoriesAutoConfigureRegistrar上的@EnableJpaRepositories注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({JpaRepositoriesRegistrar.class}) public @interface EnableJpaRepositories { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Filter[] includeFilters() default {}; Filter[] excludeFilters() default {}; String repositoryImplementationPostfix() default "Impl"; String namedQueriesLocation() default ""; Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class; Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class; //发现这两个关键的bean,然后我们查找这两个bean在哪里被注册 String entityManagerFactoryRef() default "entityManagerFactory"; String transactionManagerRef() default "transactionManager"; boolean considerNestedRepositories() default false; boolean enableDefaultTransactions() default true; }
回头看看HibernateJpaAutoConfiguration,发现代码也没有什么相关,查看父类JpaBaseConfiguration,发现了这里出现了我们要找的那两个关键的bean
@Bean @ConditionalOnMissingBean({PlatformTransactionManager.class}) public PlatformTransactionManager transactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(); if (this.transactionManagerCustomizers != null) { this.transactionManagerCustomizers.customize(transactionManager); } return transactionManager; } @Bean @ConditionalOnMissingBean public JpaVendorAdapter jpaVendorAdapter() { AbstractJpaVendorAdapter adapter = this.createJpaVendorAdapter(); adapter.setShowSql(this.properties.isShowSql()); adapter.setDatabase(this.properties.determineDatabase(this.dataSource)); adapter.setDatabasePlatform(this.properties.getDatabasePlatform()); adapter.setGenerateDdl(this.properties.isGenerateDdl()); return adapter; } //构建entityManagerFactory @Bean @ConditionalOnMissingBean public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, ObjectProvider<PersistenceUnitManager> persistenceUnitManager) { EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, this.properties.getProperties(), (PersistenceUnitManager)persistenceUnitManager.getIfAvailable()); builder.setCallback(this.getVendorCallback()); return builder; } @Bean @Primary @ConditionalOnMissingBean({LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class}) public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) { Map<String, Object> vendorProperties = this.getVendorProperties(); this.customizeVendorProperties(vendorProperties); return factoryBuilder.dataSource(this.dataSource).packages(this.getPackagesToScan()).properties(vendorProperties).jta(this.isJta()).build(); }
大致流程应该
Hibernate帮我们绑定数据源以及实体类、事务管理的Bean注册到容器中,然后自动配置使用这个bean
实际操作配置多数据源
新建一个备份数据库seller_backup,然后把seller数据库中的表复制到其中,删除主库上对账的表,删除所有记录
数据库同步方面保持数据一致性的方法
- mysql配置主从复制
- 阿里开源的框架alibaba/otter
修改application.yml的内容,配置两个数据源
spring: datasource: primary: url: jdbc:mysql://localhost:3306/seller?useUnicode=true&useSSL=false&characterEncoding=utf-8 username: root password: xxxxxx(你的密码) backup: url: jdbc:mysql://localhost:3306/seller_backup?useUnicode=true&useSSL=false&characterEncoding=utf-8 username: root password: xxxxxx
定义一个DataAccessConfiguration数据库相关操作配置类
查看文档中的操作并且复制代码到类中进行修改
/** * 主数据源 * @return */ @Bean @Primary //限定注解 @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } /** * 备份数据源 * @return */ @Bean @ConfigurationProperties("spring.datasource.backup") public DataSource backupDataSource() { return DataSourceBuilder.create().build(); } /** * 实体管理 * @param builder * @param dataSource * @return */ @Bean @Primary public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory( EntityManagerFactoryBuilder builder,@Qualifier("primaryDataSource") DataSource dataSource) { //使用限定词注入 return builder .dataSource(dataSource) //这里类型注入的时候因为两个bean的类型都一致,所以会报错 .packages(Order.class) .persistenceUnit("primary") .build(); } /** * 实体管理 * @param builder * @param dataSource * @return */ @Bean public LocalContainerEntityManagerFactoryBean backupEntityManagerFactory( EntityManagerFactoryBuilder builder,@Qualifier("backupDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages(Order.class) .persistenceUnit("backup") .build(); }
查看源代码,发现这里还需要一个properties配置
实现类方法如下
然后复制到我们的自定义配置类中进行修改
/** * 数据库相关操作配置 * @author TiHom * create at 2018/8/5 0005. */ @Configuration public class DataAccessConfiguration { @Autowired private JpaProperties properties; /** * 主数据源 * @return */ @Bean @Primary //限定注解 @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } /** * 备份数据源 * @return */ @Bean @ConfigurationProperties("spring.datasource.backup") public DataSource backupDataSource() { return DataSourceBuilder.create().build(); } /** * 实体管理 * @param builder * @param dataSource * @return */ @Bean @Primary public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory( EntityManagerFactoryBuilder builder,@Qualifier("primaryDataSource") DataSource dataSource) { //使用限定词注入 return builder .dataSource(dataSource) //这里类型注入的时候因为两个bean的类型都一致,所以会报错 .packages(Order.class) .properties(getVendorProperties(dataSource)) .persistenceUnit("primary") .build(); } /** * 实体管理 * @param builder * @param dataSource * @return */ @Bean public LocalContainerEntityManagerFactoryBean backupEntityManagerFactory( EntityManagerFactoryBuilder builder,@Qualifier("backupDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages(Order.class) .properties(getVendorProperties(dataSource)) .persistenceUnit("backup") .build(); } protected Map<String, Object> getVendorProperties(DataSource dataSource) { Map<String, Object> vendorProperties = new LinkedHashMap<String, Object>(); vendorProperties.putAll(properties.getHibernateProperties(dataSource)); return vendorProperties; } @Bean @Primary public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(primaryEntityManagerFactory.getObject()); return transactionManager; } @Bean public PlatformTransactionManager backupTransactionManager(@Qualifier("backupEntityManagerFactory") LocalContainerEntityManagerFactoryBean backupEntityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(backupEntityManagerFactory.getObject()); return transactionManager; } // repository 扫描的时候,并不确定哪个先扫描,查看源代码进行判断 @EnableJpaRepositories(basePackageClasses = OrderRepository.class, entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "primaryTransactionManager") public class PrimaryConfiguration { } @EnableJpaRepositories(basePackageClasses = VerifyRepository.class, entityManagerFactoryRef = "backupEntityManagerFactory",transactionManagerRef = "backupTransactionManager") public class backupConfiguration { } }
下面解析这些类!
解析JPA的三个关键bean
- EntityManagerFactoryBean
- DataSource
- TransactionManager
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "primaryTransactionManager")
public class PrimaryConfiguration {
}
@EnableJpaRepositories(basePackageClasses = VerifyRepository.class,
entityManagerFactoryRef = "backUpEntityManagerFactory",transactionManagerRef = "backUpTransactionManager")
public class BackUpConfiguration {
}
这里主备需要实现的repository接口不同,但是basePackageClasses的扫描机制是扫描对应类所在的包然后找到那个包下面的所有类,这里OrderRepository和VerifyRepository是在同一个包下的,这两个配置扫描的包是同一个,所以会把配置配置两份,这就出现问题了,产生了冲突只会生效一个。还有我们不知道PrimaryConfiguration先配置还是BackUpConfiguration先配置,所以需要查看源码来判断。
结果发现,BackUpConfiguration先注入且先注入OrderRepository再注入VerifyRepository,然后PrimaryConfiguration再注入且先注入VerifyRepository后注入OrderRepository
官方也没有提供合适的解决方法,所以最简单的做法就是将主库与备份库操作的repository分别放在repository和repositorybackup中,这样就不会混淆了。
一些问题的解释
- @Primary:如果我们一个容器当中添加了两个相同类型但是名称不相同的bean,我们如果想通过名称注入这些bean,那么这个@Primary无需使用。但是如果我们想直接通过类型,不用名称限定直接注入的话,spring容器就会报错,我们需要一个类型,但是这个类型对应两个bean,spring不知道哪个才是我们需要的,这个时候使用@Primary在一个bean上面让它成为主bean,spring就会自动识别
- MySql主从复制、alibaba/otter
- 不同数据源分包
查看源码的步骤
- 先看官方文档
- 尝试按自己的思维去实现功能
- 暴力搜索Ctrl+Shift+F(注意会跟搜狗输入法中的快捷键冲突,两个软件二选一进行修改)
JPA读写分离
读写分离的作用(what?why?when?how?)
参考于https://blog.csdn.net/u013421629/article/details/78793966
不同数据源相同repositories
添加额外接口,继承
使用OrderRepository读出来的数据和backupOrderRepository读出来的数据是不一样的
public interface backupOrderRepoistory extends OrderRepository { }
修改源码
找到注册过程的代码,找到这个类发现这里是生成bean名称的地方
然后复制这个类的全部代码
这个 RepositoryConfigurationDelegate与源码完全相同,然后我们自己进行一些修改
为什么这样可行呢?因为当前应用下有对应类时不会使用依赖包内的,优先使用当前应用下包内的。这是修改源码的核心!!!
- 声明一个注解@RepositoryBeanNamePrefix
/**
* repository bean 名称的前缀
* @author TiHom
* create at 2018/8/6 0006.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RepositoryBeanNamePrefix {
String value();
}
修改RepositoryConfigurationDelegate类
while(var5.hasNext()) { RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration = (RepositoryConfiguration)var5.next(); BeanDefinitionBuilder definitionBuilder = builder.build(configuration); extension.postProcess(definitionBuilder, this.configurationSource); if (this.isXml) { extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource)this.configurationSource); } else { extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource)this.configurationSource); } AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition(); String beanName = this.beanNameGenerator.generateBeanName(beanDefinition, registry); ---- 开始修改 ---- 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; } ---- 结束修改 ---- if (LOGGER.isDebugEnabled()) { LOGGER.debug("Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}", new Object[]{extension.getModuleName(), beanName, configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName()}); } beanDefinition.setAttribute("factoryBeanObjectType", configuration.getRepositoryInterface()); registry.registerBeanDefinition(beanName, beanDefinition); definitions.add(new BeanComponentDefinition(beanDefinition, beanName)); }
修改DataAccessConfiguration类
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "primaryTransactionManager")
@Primary
public class PrimaryConfiguration {
}
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "backupEntityManagerFactory")
@RepositoryBeanNamePrefix("read")
public class ReadConfiguration {
}
@EnableJpaRepositories(basePackageClasses = VerifyRepository.class,
entityManagerFactoryRef = "backupEntityManagerFactory",transactionManagerRef = "backupTransactionManager")
public class BackupConfiguration {
}
总结
- 接口继承,在不同包下面,并且维护只需要维护被继承的类
- 同一个Repoistory,不需要创建多一个继承,直接拦截注册过程,创建两个同类型但是不同名称的bean到spring容器中,主库的加上@Primary注解,读库的使用自定义的@RepositoryBeanNamePrefix注解