赞
踩
前言:本来想使用sharding-jdbc来实现这个,但是又不想不太熟悉新的框架而存在太多的坑而导致出现无法预测的问题或者及时的解决问题。因此按照实际开发习惯,使用原来简单项目,不引入新的框架,对JPA进行封装来实现。
jpa官网链接:jpa官网链接
1、controller层中
CenterFactory.setCenterInfoByTypeAndValue(CenterUtil.ORDER_ID, custOrderId);
这一句是为了设置当前动态数据源归属,如ORDER分为四个库,根据客户订单编号规则获取到究竟是属于哪一个库的最终定位到类似ORDER2
- @RestController
- public class indexController {
- @Autowired
- private IUserSV userSV;
-
- @RequestMapping("/test")
- public List<User> test(Long custOrderId) {
- //这一句是为了设置当前动态数据源归属,如ORDER分为四个库,根据客户订单编号规则获取到究竟是属于哪一个库的最终定位到类似ORDER2
- CenterFactory.setCenterInfoByTypeAndValue(CenterUtil.ORDER_ID, custOrderId);
- return userSV.findByCustOrderId(custOrderId);
- }
- }
2、在SV层中
在service方法中添加自定义注解,@DataSource(source = "ORDER{CENTER}") source的值为数据库yml配置的值,{CENTER}为分库替换变量。
- @Service
- public class UserSVImpl implements IUserSV {
-
- @Autowired
- private IUserDAO dao;
-
- @DataSource(source = "ORDER{CENTER}")
- @Override
- public List<User> findByCustOrderId(Long custOrderId) {
- return dao.findByCustOrderId(custOrderId);
- }
- }
3、在pojo对象中
{REGION}为分表,具体分表规则自定义,当然如果不闲麻烦的话,仍然@Query可以通过入参对表名进行传递。
- @Entity(name = "user_{REGION}")
- public class User {
- @Column
- public Long custOrderId;
- @Column
- public String remark;
-
- public Long getCustOrderId() {
- return custOrderId;
- }
-
- public void setCustOrderId(Long custOrderId) {
- this.custOrderId = custOrderId;
- }
-
- public String getRemark() {
- return remark;
- }
-
- public void setRemark(String remark) {
- this.remark = remark;
- }
- }
其实使用起来就是两个核心,一个是在通过对注解的拦截进行设置数据源,一个是对sql的拦截进行分表设置。
需要依赖的包除非就是springboot以及Jpa
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid-spring-boot-starter</artifactId>
- <version>1.1.16</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.21</version>
- </dependency>
- <dependency>
- <groupId>com.oracle</groupId>
- <artifactId>ojdbc6</artifactId>
- <version>11.2.0.3</version>
- </dependency>
- server:
- port: 12305
-
- spring:
- #默认的数据源
- datasource:
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.1:1521/test1
- rname: tempquery
- password: tempquery
- validationQuery: SELECT 1 FROM DUAL
- main:
- allow-bean-definition-overriding: true
-
- jpa:
- database: oracle
- show-sql: true
- properties:
- #不加此配置,获取不到当前currentsession
- hibernate:
- current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
- #拦截sql,进行分表
- session_factory:
- statement_inspector: com.order.config.source.aop.JpaInterceptor
- open-in-view: false
-
- #配置日志
- logging:
- #配置日志文件路径
- path: log
- level:
- xatu.zsl: debug #不同目录下的日志可配置不同级别
- org.springfromework.web: info
-
- mydatasources:
- #主动开启多数据源
- multiDatasourceOpen: true
- datasource[0]:
- dbName: ORDER1
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test2
- username: ORDER1
- password: ORDER1
- datasource[1]:
- dbName: ORDER2
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test3
- username: ORDER2
- password: ORDER2
- datasource[2]:
- dbName: ORDER3
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test4
- username: ORDER3
- password: ORDER3
- datasource[3]:
- dbName: ORDER4
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test5
- username: ORDER4
- password: ORDER4
- datasource[4]:
- dbName: CFG1
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test6
- username: CFG1
- password: CFG1234
- datasource[5]:
- dbName: CFG2
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test7
- username: CFG2
- password: CFG1234
- datasource[6]:
- dbName: CFG3
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test8
- username: CFG3
- password: CFG1234
- datasource[7]:
- dbName: CFG4
- driverClassName: oracle.jdbc.driver.OracleDriver
- url: jdbc:oracle:thin:@127.0.0.0.1:1521/test9
- username: CFG4
- password: CFG1234
-
- @Inherited
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD,ElementType.TYPE})
- public @interface DataSource {
- String source() default "ORD_CFG";
- }
yml
配置类- public class DruidProperties {
- private final static Logger log = LoggerFactory.getLogger(DruidProperties.class);
-
- public DruidProperties() {
- log.info("default 数据源加载");
- }
-
- /**
- * 数据源名称
- */
- private String dbName = "CFG";
-
- private String url;
-
- private String username;
-
- private String password;
- /**
- * 默认为 oracle 配置
- */
- private String driverClassName = "oracle.jdbc.driver.OracleDriver";
-
- private Integer initialSize = 10;
-
- private Integer minIdle = 3;
-
- private Integer maxActive = 60;
-
- private Integer maxWait = 60000;
-
- private Boolean removeAbandoned = true;
-
- private Integer removeAbandonedTimeout = 180;
-
- private Integer timeBetweenEvictionRunsMillis = 60000;
-
- private Integer minEvictableIdleTimeMillis = 300000;
-
- //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
- private String validationQuery = "SELECT 1 FROM DUAL";
-
- //建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
- //如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
- private Boolean testWhileIdle = true;
-
- //申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
- private Boolean testOnBorrow = false;
-
- //归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
- private Boolean testOnReturn = false;
-
- //PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭
- private Boolean poolPreparedStatements = true;
-
- private Integer maxPoolPreparedStatementPerConnectionSize = 50;
-
- //属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
- //监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
- private String filters = "stat";
- }
- /**
- * @ClassName: MultiDataSourceAop.java
- * @Description: 分库,动态切换数据源
- * @version: v1.0.0
- * @author: yulang
- * @date: 2019/12/5 15:41
- * <p>
- * Modification History:
- * Date Author Version Description
- * ------------------------------------------------------------
- * 2019/12/5 yulang v1.0.0 第一次创建
- */
- @Aspect
- @Component
- @ConditionalOnProperty(prefix = "mydatasources", name = "multiDatasourceOpen", havingValue = "true")
- public class MultiDataSourceAop implements Ordered {
- private Logger log = LoggerFactory.getLogger(this.getClass());
-
- public MultiDataSourceAop() {
- log.info("多数据源初始化 AOP ");
- }
-
- @Pointcut(value = "@annotation(org.yulang.config.annotation.DataSource)")
- private void cut() {
- }
-
- @Around("cut()")
- public Object around(ProceedingJoinPoint point) throws Throwable {
- Signature signature = point.getSignature();
- MethodSignature methodSignature = (MethodSignature) signature;
- //获取当点方法的注解
- Object target = point.getTarget();
- Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
- DataSource datasource = currentMethod.getAnnotation(DataSource.class);
- if (datasource != null) {
- DynamicDataSource.setDataSourceDbName(getDataSource(datasource));
- } else {
- // 获取类上的注解
- datasource = point.getTarget().getClass().getAnnotation(DataSource.class);
- DynamicDataSource.setDataSourceDbName(getDataSource(datasource));
- if (datasource ==null){
- DynamicDataSource.setDataSourceDbName("CFG");
- log.info("设置数据源为:默认 --> CFG");
- }
- }
- try {
- return point.proceed();
- } finally {
- log.info("清空数据源信息!");
- DynamicDataSource.clearDataSourceDbName();
- }
- }
-
- private String getDataSource(DataSource datasource){
- String source = datasource.source();
- if (source.contains("{CENTER}")){
- CenterInfo centerInfo = CenterFactory.getCenterInfo();
- source = source.replace("{CENTER}",centerInfo.getCenter());
- }
- log.info("设置数据源为:" + source);
- return source;
- }
-
- /**
- * aop的顺序要早于spring的事务
- */
- @Override
- public int getOrder() {
- return 1;
- }
- }
- @Component
- public class MultiSourceConfig {
- private final static Logger log = LoggerFactory.getLogger(MultiSourceConfig.class);
-
- @Autowired
- private DruidProperties druidProperties;
-
- @Autowired
- private MultiDataSource multiDataSource;
-
-
- /**
- * 单数据源连接池配置
- */
- @Bean
- @ConditionalOnProperty(name = "mydatasources.multiDatasourceOpen", havingValue = "false")
- public DruidDataSource singleDatasource() {
- log.error("singleDatasource");
- return druidProperties.config(new DruidDataSource());
- }
-
- /**
- * 多数据源连接池配置
- */
- @Bean
- @ConditionalOnProperty(name = "mydatasources.multiDatasourceOpen", havingValue = "true")
- public DynamicDataSource mutiDataSource() {
- log.error("mutiDataSource");
-
- //存储数据源别名与数据源的映射
- HashMap<Object, Object> dbNameMap = new HashMap<>();
- // 核心数据源
- DruidDataSource mainDataSource = druidProperties.config();
- // 这里添加 主要数据库,其它数据库挂了,默认使用主数据库
- dbNameMap.put("ORD_CFG", mainDataSource);
- // 其它数据源
- // 当前多数据源是否存在
- if (multiDataSource.getDatasource() != null) {
- //过滤掉没有添加 dbName 的数据源,先加载全局配置,再次加载当前配置
- List<DruidDataSource> multiDataSourceList = multiDataSource.getDatasource().stream()
- .filter(dp -> !"".equals(Optional.ofNullable(dp.getDbName()).orElse("")))
- .map(dp -> {
- DruidDataSource druidDataSource = dp.config(druidProperties.config());
- dbNameMap.put(dp.getDbName(), druidDataSource);
- return druidDataSource;
- })
- .collect(Collectors.toList());
-
- // 测试所有的数据源
- /* try {
- mainDataSource.init();
- for (DruidDataSource druidDataSource : multiDataSourceList) {
- druidDataSource.init();
- }
- } catch (SQLException sql) {
- log.error("======================= 多数据源配置错误 ==========================");
- sql.printStackTrace();
- }*/
- }
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- dynamicDataSource.setTargetDataSources(dbNameMap);
- dynamicDataSource.setDefaultTargetDataSource(mainDataSource);
- return dynamicDataSource;
- }
-
- }
其中initMap方法为对应分库的配置方法。
具体可以根据数据的哪一列进行分库分表 如下客户订单编号前三位为100,200将会路由到数据1中心,
当然如果知道当前数据处于哪个分区(REGION),则可以直接根据REGION设置分区
- /**
- * @ClassName: com.order.common.center
- * @Description: 设置中心,简易版
- * @version: v1.0.0
- * @author: yulang
- * @date: 2019/12/5 12:09
- * <p>
- * Modification History:
- * Date Author Version Description
- * ------------------------------------------------------------
- * 2019/12/5 yulang v1.1.0 第一次创建
- */
- public class CenterFactory {
- //设中心
- private static Map<String, String> mapCenter;
- private static final ThreadLocal CENTER_INFO = new ThreadLocal();
- static {
- if (mapCenter == null) {
- initMap();
- }
- }
-
- public static CenterInfo getCenterInfo() {
- if (CENTER_INFO.get() == null) {
- throw new RuntimeException("没有设置中心!");
- } else {
- return (CenterInfo)CENTER_INFO.get();
- }
- }
-
- public static void setCenterInfoByTypeAndValue(String type, String region) {
- CENTER_INFO.set(new CenterInfo(region,mapCenter.get(region)));
- }
-
- public static void setCenterInfoByTypeAndValue(String type, Long value) {
- String region = value.toString();
- if (type.equals(CenterUtil.ORDER_ID)){
- region = region.substring(0, 3);
- }
- setCenterInfoByTypeAndValue(CenterUtil.REGION_ID,region);
- }
-
-
- public static Map<String, String> initMap() {
- mapCenter = new HashMap<>();
- //todo 具体可以根据数据的哪一列进行分库分表 如下客户订单编号前三位为100,200将会路由到数据1中心,
- // 当然如果知道分区,可以直接根据REGION设置分区
- mapCenter.put("100", "1");
- mapCenter.put("200", "1");
- mapCenter.put("300", "2");
- mapCenter.put("400", "2");
- mapCenter.put("500", "3");
- mapCenter.put("600", "3");
- mapCenter.put("700", "4");
- mapCenter.put("800", "4");
- mapCenter.put("900", "4");
- return mapCenter;
- }
-
- }
- public class CenterInfo implements Serializable {
- private String center = null;//划分出多少个库
- private String regionId = null;//一个库中划分出多少分表
- private String jvmid = null;
- private Date date = null;//查询指定年月日
- }
当然会存在多数据源情况下的事物问题,我们可以抽取一层sv层专注事物控制。
最简单的sv层不做事物,只做表的增删改查不做业务逻辑以及事物提交。
附录:
github源码地址:https://github.com/yulangde/coll/tree/master/mds
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。