https://blog.csdn.net/Anbang713/article/details/83240353
在上篇博客《MySQL-主从复制之同步主从数据》中,我们实现了读库和写库的数据同步。今天,我们继续学习SpringBoot集成JPA如何实现数据读写分离。废话不多话直接上代码。
一、配置数据源
1. # 数据源 2. spring.datasource.druid.write.url=jdbc:mysql://localhost:3380/test 3. spring.datasource.druid.write.username=root 4. spring.datasource.druid.write.password=Anbang713 5. spring.datasource.druid.write.driver-class-name=com.mysql.jdbc.Driver 6. 7. spring.datasource.druid.read.url=jdbc:mysql://localhost:3381/test 8. spring.datasource.druid.read.username=root 9. spring.datasource.druid.read.password=Anbang713 10. spring.datasource.druid.read.driver-class-name=com.mysql.jdbc.Driver 11. 12. # JPA 13. spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 14. spring.jpa.database=mysql 15. spring.jpa.generate-ddl=false 16. spring.jpa.hibernate.ddl-auto=none 17. spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy 18. spring.jpa.show-sql=false
二、数据源配置类
1. /**
2. * 数据源配置
3. *
4. * @author Administrator
5. *
6. */
7. @Configuration
8. public class DataSourceConfig {
9.
10. public final static String WRITE_DATASOURCE_KEY = "writeDruidDataSource";
11. public final static String READ_DATASOURCE_KEY = "readDruidDataSource";
12.
13. @ConfigurationProperties(prefix = "spring.datasource.druid.read")
14. @Bean(name = READ_DATASOURCE_KEY)
15. public DataSource readDruidDataSource() {
16. return new DruidDataSource();
17. }
18.
19. @ConfigurationProperties(prefix = "spring.datasource.druid.write")
20. @Bean(name = WRITE_DATASOURCE_KEY)
21. @Primary
22. public DataSource writeDruidDataSource() {
23. return new DruidDataSource();
24. }
25.
26. /**
27. * 注入AbstractRoutingDataSource
28. *
29. * @param readDruidDataSource
30. * @param writeDruidDataSource
31. * @return
32. * @throws Exception
33. */
34. @Bean
35. public AbstractRoutingDataSource routingDataSource(
36. @Qualifier(READ_DATASOURCE_KEY) DataSource readDruidDataSource,
37. @Qualifier(WRITE_DATASOURCE_KEY) DataSource writeDruidDataSource) throws Exception {
38. DynamicDataSource dataSource = new DynamicDataSource();
39. Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
40. targetDataSources.put(WRITE_DATASOURCE_KEY, writeDruidDataSource);
41. targetDataSources.put(READ_DATASOURCE_KEY, readDruidDataSource);
42. dataSource.setTargetDataSources(targetDataSources);// 配置数据源
43. dataSource.setDefaultTargetDataSource(writeDruidDataSource);// 默认为主库用于写数据
44. return dataSource;
45. }
46. }三、使用ThreadLocal使数据源与线程绑定
1. public class DynamicDataSourceHolder {
2. // 使用ThreadLocal把数据源与当前线程绑定
3. private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
4.
5. public static void setDataSource(String dataSourceName) {
6. dataSources.set(dataSourceName);
7. }
8.
9. public static String getDataSource() {
10. return (String) dataSources.get();
11. }
12.
13. public static void clearDataSource() {
14. dataSources.remove();
15. }
16. }四、动态数据源配置
1. public class DynamicDataSource extends AbstractRoutingDataSource {
2.
3. @Override
4. protected Object determineCurrentLookupKey() {
5. // 可以做一个简单的负载均衡策略
6. String lookupKey = DynamicDataSourceHolder.getDataSource();
7. System.out.println("------------lookupKey---------" + lookupKey);
8. return lookupKey;
9. }
10. }五、写数据源配置类
1. @Configuration
2. @EnableJpaRepositories(value = "com.study.mysql.jpa.dao",
3. entityManagerFactoryRef = "writeEntityManagerFactory",
4. transactionManagerRef = "writeTransactionManager")
5. public class WriteDataSourceConfig {
6.
7. @Autowired
8. JpaProperties jpaProperties;
9. @Autowired
10. @Qualifier("writeDruidDataSource")
11. private DataSource writeDruidDataSource;
12.
13. /**
14. * 我们通过LocalContainerEntityManagerFactoryBean来获取EntityManagerFactory实例
15. *
16. * @return
17. */
18. @Bean(name = "writeEntityManagerFactoryBean")
19. public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(
20. EntityManagerFactoryBuilder builder) {
21. return builder.dataSource(writeDruidDataSource).properties(jpaProperties.getProperties())
22. .packages("com.study.mysql.jpa.api") // 设置实体类所在位置
23. .persistenceUnit("writePersistenceUnit").build();
24. }
25.
26. /**
27. * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
28. * 总之在执行操作之前我们总要获取一个EntityManager,这就类似于Hibernate的Session,mybatis的sqlSession。
29. *
30. * @param builder
31. * @return
32. */
33. @Bean(name = "writeEntityManagerFactory")
34. @Primary
35. public EntityManagerFactory writeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
36. return this.writeEntityManagerFactoryBean(builder).getObject();
37. }
38.
39. /**
40. * 配置事物管理器
41. *
42. * @return
43. */
44. @Bean(name = "writeTransactionManager")
45. @Primary
46. public PlatformTransactionManager writeTransactionManager(EntityManagerFactoryBuilder builder) {
47. return new JpaTransactionManager(writeEntityManagerFactory(builder));
48. }
49. }六、自定义注解
1. @Target({
2. ElementType.METHOD, ElementType.TYPE })
3. @Retention(RetentionPolicy.RUNTIME)
4. @Documented
5. public @interface TargetDateSource {
6. String dataSource() default "";// 数据源
7. }七、定义切面,实现数据源切换
1. @Aspect
2. @Component
3. public class DynamicDataSourceAspect {
4.
5. @Around("execution(public * com.study.mysql.jpa.core..*.*(..))")
6. public Object around(ProceedingJoinPoint pjp) throws Throwable {
7. MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
8. Method targetMethod = methodSignature.getMethod();
9. if (targetMethod.isAnnotationPresent(TargetDateSource.class)) {
10. String targetDataSource = targetMethod.getAnnotation(TargetDateSource.class).dataSource();
11. System.out.println("----------数据源是:" + targetDataSource + "------");
12. DynamicDataSourceHolder.setDataSource(targetDataSource);
13. }
14. // 执行方法
15. Object result = pjp.proceed();
16. DynamicDataSourceHolder.clearDataSource();
17. return result;
18. }
19. }在完成上面的相关配置后,我们写个简单的学生增删改查接口做测试。至此,我们的项目结构是这样的:
当然在这里,我们有必要看一下业务层实现类的代码,通过注解@TargetDataSource注解实现读写分离。
1. @Service
2. public class StudentServiceImpl implements StudentService {
3.
4. @Autowired
5. private StudentDao studentDao;
6.
7. @Override
8. @TargetDateSource(dataSource = DataSourceConfig.READ_DATASOURCE_KEY)
9. public List<Student> findAll() {
10. return studentDao.findAll();
11. }
12.
13. @Override
14. @TargetDateSource(dataSource = DataSourceConfig.READ_DATASOURCE_KEY)
15. public Student findById(Integer id) {
16. Optional<Student> students = studentDao.findById(id);
17. if (students.isPresent() && students.get() != null) {
18. return students.get();
19. }
20. return null;
21. }
22.
23. @Override
24. @Transactional
25. @TargetDateSource(dataSource = DataSourceConfig.WRITE_DATASOURCE_KEY)
26. public Integer save(Student entity) throws Exception {
27. if (entity.getId() != null) {
28. Student perz = studentDao.saveAndFlush(entity);
29. return perz.getId();
30. }
31. Student perz = studentDao.save(entity);
32. return perz.getId();
33. }
34.
35. }八、测试
启动SpringBoot启动类,并通过http://localhost:8080/swagger-ui.html访问我们的学生类接口。在测试之前,我们现在看下数据库的数据。可以看到我们的主从数据库数据是一样的。(MySQL5.6-3380为主数据库,用于写数据;MySQL5.6-3381为从数据库,用于读数据)
那么我们现在往数据库插入一条数据,执行save接口:
首先可以看到,在切面类中打印的日志,已经实现数据源的自动切换了。
然后我们看下数据库的数据,可以看到两边的数据是一模一样的。
最后,我们测试一下读的时候是从哪个数据源读的。
可以看到,在读请求的时候,是从从数据库读的数据。至此,我们使用SpringBoot集成JPA实现读写分离的目的已经达到。