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) 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 34. @Primary 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. @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实现读写分离的目的已经达到。