当前位置:   article > 正文

使用 SpringBoot+JPA多数据源,动态数据源以及分库分表的实现_springboot jpa 分表

springboot jpa 分表

 SpringBoot+JPA多数据源,动态数据源以及分库分表的实现

前言:本来想使用sharding-jdbc来实现这个,但是又不想不太熟悉新的框架而存在太多的坑而导致出现无法预测的问题或者及时的解决问题。因此按照实际开发习惯,使用原来简单项目,不引入新的框架,对JPA进行封装来实现。

jpa官网链接:jpa官网链接

1、使用前须知:

  • 采用的数据库连接池:Druid
  • 分库(多数据源实现采用):注解 + AOP 的方式实现(数据源注解可以在方法上,也可以在类上,当然规则可以自己在aop代码中进行更改,注解的值可以使用动态的替换值)
  • 如果多个数据源的表结构大不相同,则不推荐使用,但是如果字段相同,只有表名不同依旧可以使用。
  • 分表实现采用拦截jpa底层生成sql,对表名进行替换

2、使用方法:

1、controller层中

CenterFactory.setCenterInfoByTypeAndValue(CenterUtil.ORDER_ID, custOrderId);

这一句是为了设置当前动态数据源归属,如ORDER分为四个库,根据客户订单编号规则获取到究竟是属于哪一个库的最终定位到类似ORDER2

  1. @RestController
  2. public class indexController {
  3. @Autowired
  4. private IUserSV userSV;
  5. @RequestMapping("/test")
  6. public List<User> test(Long custOrderId) {
  7. //这一句是为了设置当前动态数据源归属,如ORDER分为四个库,根据客户订单编号规则获取到究竟是属于哪一个库的最终定位到类似ORDER2
  8. CenterFactory.setCenterInfoByTypeAndValue(CenterUtil.ORDER_ID, custOrderId);
  9. return userSV.findByCustOrderId(custOrderId);
  10. }
  11. }

2、在SV层中

在service方法中添加自定义注解,@DataSource(source = "ORDER{CENTER}") source的值为数据库yml配置的值,{CENTER}为分库替换变量。

  1. @Service
  2. public class UserSVImpl implements IUserSV {
  3. @Autowired
  4. private IUserDAO dao;
  5. @DataSource(source = "ORDER{CENTER}")
  6. @Override
  7. public List<User> findByCustOrderId(Long custOrderId) {
  8. return dao.findByCustOrderId(custOrderId);
  9. }
  10. }

3、在pojo对象中

{REGION}为分表,具体分表规则自定义,当然如果不闲麻烦的话,仍然@Query可以通过入参对表名进行传递。

  1. @Entity(name = "user_{REGION}")
  2. public class User {
  3. @Column
  4. public Long custOrderId;
  5. @Column
  6. public String remark;
  7. public Long getCustOrderId() {
  8. return custOrderId;
  9. }
  10. public void setCustOrderId(Long custOrderId) {
  11. this.custOrderId = custOrderId;
  12. }
  13. public String getRemark() {
  14. return remark;
  15. }
  16. public void setRemark(String remark) {
  17. this.remark = remark;
  18. }
  19. }

 

以下即是对框架的核心类进行解析

其实使用起来就是两个核心,一个是在通过对注解的拦截进行设置数据源,一个是对sql的拦截进行分表设置。

依赖

需要依赖的包除非就是springboot以及Jpa

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-jpa</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba</groupId>
  7. <artifactId>druid-spring-boot-starter</artifactId>
  8. <version>1.1.16</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>mysql</groupId>
  12. <artifactId>mysql-connector-java</artifactId>
  13. <version>5.1.21</version>
  14. </dependency>
  15. <dependency>
  16. <groupId>com.oracle</groupId>
  17. <artifactId>ojdbc6</artifactId>
  18. <version>11.2.0.3</version>
  19. </dependency>

配置文件

  1. server:
  2. port: 12305
  3. spring:
  4. #默认的数据源
  5. datasource:
  6. driverClassName: oracle.jdbc.driver.OracleDriver
  7. url: jdbc:oracle:thin:@127.0.0.1:1521/test1
  8. rname: tempquery
  9. password: tempquery
  10. validationQuery: SELECT 1 FROM DUAL
  11. main:
  12. allow-bean-definition-overriding: true
  13. jpa:
  14. database: oracle
  15. show-sql: true
  16. properties:
  17. #不加此配置,获取不到当前currentsession
  18. hibernate:
  19. current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
  20. #拦截sql,进行分表
  21. session_factory:
  22. statement_inspector: com.order.config.source.aop.JpaInterceptor
  23. open-in-view: false
  24. #配置日志
  25. logging:
  26. #配置日志文件路径
  27. path: log
  28. level:
  29. xatu.zsl: debug #不同目录下的日志可配置不同级别
  30. org.springfromework.web: info
  31. mydatasources:
  32. #主动开启多数据源
  33. multiDatasourceOpen: true
  34. datasource[0]:
  35. dbName: ORDER1
  36. driverClassName: oracle.jdbc.driver.OracleDriver
  37. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test2
  38. username: ORDER1
  39. password: ORDER1
  40. datasource[1]:
  41. dbName: ORDER2
  42. driverClassName: oracle.jdbc.driver.OracleDriver
  43. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test3
  44. username: ORDER2
  45. password: ORDER2
  46. datasource[2]:
  47. dbName: ORDER3
  48. driverClassName: oracle.jdbc.driver.OracleDriver
  49. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test4
  50. username: ORDER3
  51. password: ORDER3
  52. datasource[3]:
  53. dbName: ORDER4
  54. driverClassName: oracle.jdbc.driver.OracleDriver
  55. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test5
  56. username: ORDER4
  57. password: ORDER4
  58. datasource[4]:
  59. dbName: CFG1
  60. driverClassName: oracle.jdbc.driver.OracleDriver
  61. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test6
  62. username: CFG1
  63. password: CFG1234
  64. datasource[5]:
  65. dbName: CFG2
  66. driverClassName: oracle.jdbc.driver.OracleDriver
  67. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test7
  68. username: CFG2
  69. password: CFG1234
  70. datasource[6]:
  71. dbName: CFG3
  72. driverClassName: oracle.jdbc.driver.OracleDriver
  73. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test8
  74. username: CFG3
  75. password: CFG1234
  76. datasource[7]:
  77. dbName: CFG4
  78. driverClassName: oracle.jdbc.driver.OracleDriver
  79. url: jdbc:oracle:thin:@127.0.0.0.1:1521/test9
  80. username: CFG4
  81. password: CFG1234

添加注解类

  1. @Inherited
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target({ElementType.METHOD,ElementType.TYPE})
  4. public @interface DataSource {
  5. String source() default "ORD_CFG";
  6. }

数据源配置映射 yml配置类

  1. public class DruidProperties {
  2. private final static Logger log = LoggerFactory.getLogger(DruidProperties.class);
  3. public DruidProperties() {
  4. log.info("default 数据源加载");
  5. }
  6. /**
  7. * 数据源名称
  8. */
  9. private String dbName = "CFG";
  10. private String url;
  11. private String username;
  12. private String password;
  13. /**
  14. * 默认为 oracle 配置
  15. */
  16. private String driverClassName = "oracle.jdbc.driver.OracleDriver";
  17. private Integer initialSize = 10;
  18. private Integer minIdle = 3;
  19. private Integer maxActive = 60;
  20. private Integer maxWait = 60000;
  21. private Boolean removeAbandoned = true;
  22. private Integer removeAbandonedTimeout = 180;
  23. private Integer timeBetweenEvictionRunsMillis = 60000;
  24. private Integer minEvictableIdleTimeMillis = 300000;
  25. //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
  26. private String validationQuery = "SELECT 1 FROM DUAL";
  27. //建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
  28. //如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
  29. private Boolean testWhileIdle = true;
  30. //申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
  31. private Boolean testOnBorrow = false;
  32. //归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
  33. private Boolean testOnReturn = false;
  34. //PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭
  35. private Boolean poolPreparedStatements = true;
  36. private Integer maxPoolPreparedStatementPerConnectionSize = 50;
  37. //属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
  38. //监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
  39. private String filters = "stat";
  40. }

多数据源切面类AOP:

  1. /**
  2. * @ClassName: MultiDataSourceAop.java
  3. * @Description: 分库,动态切换数据源
  4. * @version: v1.0.0
  5. * @author: yulang
  6. * @date: 2019/12/5 15:41
  7. * <p>
  8. * Modification History:
  9. * Date Author Version Description
  10. * ------------------------------------------------------------
  11. * 2019/12/5 yulang v1.0.0 第一次创建
  12. */
  13. @Aspect
  14. @Component
  15. @ConditionalOnProperty(prefix = "mydatasources", name = "multiDatasourceOpen", havingValue = "true")
  16. public class MultiDataSourceAop implements Ordered {
  17. private Logger log = LoggerFactory.getLogger(this.getClass());
  18. public MultiDataSourceAop() {
  19. log.info("多数据源初始化 AOP ");
  20. }
  21. @Pointcut(value = "@annotation(org.yulang.config.annotation.DataSource)")
  22. private void cut() {
  23. }
  24. @Around("cut()")
  25. public Object around(ProceedingJoinPoint point) throws Throwable {
  26. Signature signature = point.getSignature();
  27. MethodSignature methodSignature = (MethodSignature) signature;
  28. //获取当点方法的注解
  29. Object target = point.getTarget();
  30. Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
  31. DataSource datasource = currentMethod.getAnnotation(DataSource.class);
  32. if (datasource != null) {
  33. DynamicDataSource.setDataSourceDbName(getDataSource(datasource));
  34. } else {
  35. // 获取类上的注解
  36. datasource = point.getTarget().getClass().getAnnotation(DataSource.class);
  37. DynamicDataSource.setDataSourceDbName(getDataSource(datasource));
  38. if (datasource ==null){
  39. DynamicDataSource.setDataSourceDbName("CFG");
  40. log.info("设置数据源为:默认 --> CFG");
  41. }
  42. }
  43. try {
  44. return point.proceed();
  45. } finally {
  46. log.info("清空数据源信息!");
  47. DynamicDataSource.clearDataSourceDbName();
  48. }
  49. }
  50. private String getDataSource(DataSource datasource){
  51. String source = datasource.source();
  52. if (source.contains("{CENTER}")){
  53. CenterInfo centerInfo = CenterFactory.getCenterInfo();
  54. source = source.replace("{CENTER}",centerInfo.getCenter());
  55. }
  56. log.info("设置数据源为:" + source);
  57. return source;
  58. }
  59. /**
  60. * aop的顺序要早于spring的事务
  61. */
  62. @Override
  63. public int getOrder() {
  64. return 1;
  65. }
  66. }

多数据源配置类:

  1. @Component
  2. public class MultiSourceConfig {
  3. private final static Logger log = LoggerFactory.getLogger(MultiSourceConfig.class);
  4. @Autowired
  5. private DruidProperties druidProperties;
  6. @Autowired
  7. private MultiDataSource multiDataSource;
  8. /**
  9. * 单数据源连接池配置
  10. */
  11. @Bean
  12. @ConditionalOnProperty(name = "mydatasources.multiDatasourceOpen", havingValue = "false")
  13. public DruidDataSource singleDatasource() {
  14. log.error("singleDatasource");
  15. return druidProperties.config(new DruidDataSource());
  16. }
  17. /**
  18. * 多数据源连接池配置
  19. */
  20. @Bean
  21. @ConditionalOnProperty(name = "mydatasources.multiDatasourceOpen", havingValue = "true")
  22. public DynamicDataSource mutiDataSource() {
  23. log.error("mutiDataSource");
  24. //存储数据源别名与数据源的映射
  25. HashMap<Object, Object> dbNameMap = new HashMap<>();
  26. // 核心数据源
  27. DruidDataSource mainDataSource = druidProperties.config();
  28. // 这里添加 主要数据库,其它数据库挂了,默认使用主数据库
  29. dbNameMap.put("ORD_CFG", mainDataSource);
  30. // 其它数据源
  31. // 当前多数据源是否存在
  32. if (multiDataSource.getDatasource() != null) {
  33. //过滤掉没有添加 dbName 的数据源,先加载全局配置,再次加载当前配置
  34. List<DruidDataSource> multiDataSourceList = multiDataSource.getDatasource().stream()
  35. .filter(dp -> !"".equals(Optional.ofNullable(dp.getDbName()).orElse("")))
  36. .map(dp -> {
  37. DruidDataSource druidDataSource = dp.config(druidProperties.config());
  38. dbNameMap.put(dp.getDbName(), druidDataSource);
  39. return druidDataSource;
  40. })
  41. .collect(Collectors.toList());
  42. // 测试所有的数据源
  43. /* try {
  44. mainDataSource.init();
  45. for (DruidDataSource druidDataSource : multiDataSourceList) {
  46. druidDataSource.init();
  47. }
  48. } catch (SQLException sql) {
  49. log.error("======================= 多数据源配置错误 ==========================");
  50. sql.printStackTrace();
  51. }*/
  52. }
  53. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  54. dynamicDataSource.setTargetDataSources(dbNameMap);
  55. dynamicDataSource.setDefaultTargetDataSource(mainDataSource);
  56. return dynamicDataSource;
  57. }
  58. }

设置中心类

其中initMap方法为对应分库的配置方法。

具体可以根据数据的哪一列进行分库分表 如下客户订单编号前三位为100,200将会路由到数据1中心,
当然如果知道当前数据处于哪个分区(REGION),则可以直接根据REGION设置分区

  1. /**
  2. * @ClassName: com.order.common.center
  3. * @Description: 设置中心,简易版
  4. * @version: v1.0.0
  5. * @author: yulang
  6. * @date: 2019/12/5 12:09
  7. * <p>
  8. * Modification History:
  9. * Date Author Version Description
  10. * ------------------------------------------------------------
  11. * 2019/12/5 yulang v1.1.0 第一次创建
  12. */
  13. public class CenterFactory {
  14. //设中心
  15. private static Map<String, String> mapCenter;
  16. private static final ThreadLocal CENTER_INFO = new ThreadLocal();
  17. static {
  18. if (mapCenter == null) {
  19. initMap();
  20. }
  21. }
  22. public static CenterInfo getCenterInfo() {
  23. if (CENTER_INFO.get() == null) {
  24. throw new RuntimeException("没有设置中心!");
  25. } else {
  26. return (CenterInfo)CENTER_INFO.get();
  27. }
  28. }
  29. public static void setCenterInfoByTypeAndValue(String type, String region) {
  30. CENTER_INFO.set(new CenterInfo(region,mapCenter.get(region)));
  31. }
  32. public static void setCenterInfoByTypeAndValue(String type, Long value) {
  33. String region = value.toString();
  34. if (type.equals(CenterUtil.ORDER_ID)){
  35. region = region.substring(0, 3);
  36. }
  37. setCenterInfoByTypeAndValue(CenterUtil.REGION_ID,region);
  38. }
  39. public static Map<String, String> initMap() {
  40. mapCenter = new HashMap<>();
  41. //todo 具体可以根据数据的哪一列进行分库分表 如下客户订单编号前三位为100,200将会路由到数据1中心,
  42. // 当然如果知道分区,可以直接根据REGION设置分区
  43. mapCenter.put("100", "1");
  44. mapCenter.put("200", "1");
  45. mapCenter.put("300", "2");
  46. mapCenter.put("400", "2");
  47. mapCenter.put("500", "3");
  48. mapCenter.put("600", "3");
  49. mapCenter.put("700", "4");
  50. mapCenter.put("800", "4");
  51. mapCenter.put("900", "4");
  52. return mapCenter;
  53. }
  54. }

CenterInfo对象

  1. public class CenterInfo implements Serializable {
  2. private String center = null;//划分出多少个库
  3. private String regionId = null;//一个库中划分出多少分表
  4. private String jvmid = null;
  5. private Date date = null;//查询指定年月日
  6. }

当然会存在多数据源情况下的事物问题,我们可以抽取一层sv层专注事物控制。

最简单的sv层不做事物,只做表的增删改查不做业务逻辑以及事物提交。

 

附录:

github源码地址:https://github.com/yulangde/coll/tree/master/mds

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/173764
推荐阅读
相关标签
  

闽ICP备14008679号