精华内容
下载资源
问答
  • spring动态数据源整合mybatis实现的分库分表操作。 分库分表对业务代码是透明的,只需要在配置文件分配分库模版即可动态的实现分库分表的增删改查操作。
  • SpringBoot+JPA多数据源动态数据源以及分库分表的实现 前言:本来想使用sharding-jdbc来实现这个,但是又不想不太熟悉新的框架而存在太多的坑而导致出现无法预测的问题或者及时的解决问题。因此按照实际开发习惯...

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

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

    jpa官网链接:jpa官网链接

    1、使用前须知:

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

    2、使用方法:

    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";
    }

    多数据源切面类AOP:

    /**
     * @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;
        }
    
    }

    CenterInfo对象

    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

     

    展开全文
  • // 关闭数据源自动配置 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class DemoApplication {}
  • dangdang的扩展 sharding-jdbc实现动态数据源分库分表分页查询dangdang的分库分表扩展 sharding-jdbc封装的DBUtilModuloDatabaseShardingAlgorithmModuloTableShardingAlgorithm 原文地址 dangdang的分库分表扩展 ...


    原文地址

    dangdang的分库分表扩展 sharding-jdbc

    这段时间应公司需求需要分库查询数据,还好不是分表
    研究了一下sharding-jdbc
    我这里需求有点复杂,说是数据源不固定,随时添加一个数据源我就想到不能配置到项目文件中,就在业务库中新建一个表用来存储数据源的信息
    https://trhsy.github.io/img/data1.png

    用最原始的jdbc连接数据库的方式实现分库查询

    封装的DBUtil

    import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
    import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
    import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
    import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
    import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
    import com.dangdang.ddframe.rdb.sharding.jdbc.core.datasource.ShardingDataSource;
    import com.maven.znly.entity.NewDataSource;
    import org.apache.commons.dbcp.BasicDataSource;
    
    import javax.sql.DataSource;
    import java.sql.*;
    import java.util.*;
    
    /**
     * @ClassName DBUtil
     * @Description 为了方便建立数据库链接 只为查询封装
     * @Author asus
     * @Date Created by asus on 2018/11/1917:22
     * @Version 1.0
     **/
    public class DBUtil {
        public static  Page getPage(List<NewDataSource> dataSourceList, String sql,PageRequest pageRequest){
            ResultSet rs=null;
            Connection conn=null;
            List<Map<String, Object>> list = new ArrayList<>();
            Integer pageNo=pageRequest.getPageNumber();//第几页
            Integer pageSize=pageRequest.getPageSize();//一页几条
            Integer count=0;
            try {
                Map<String, DataSource> dataSourceMap = createDataSourceMap(dataSourceList);
                DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap);
                TableRule[] tableRules=new TableRule[dataSourceList.size()];
                for (int i = 0; i <dataSourceList.size() ; i++) {
                    TableRule tableRule = TableRule.builder("device_log").actualTables(Arrays.asList("device_log")).dataSourceRule(dataSourceRule).build();
                    tableRules[i]=tableRule;
                }
                List<TableRule> newTableRule=Arrays.asList(tableRules);
                //TableRule orderTableRule = TableRule.builder("device_log").actualTables(Arrays.asList("device_log")).dataSourceRule(dataSourceRule).build();
                ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(newTableRule)
                        .databaseShardingStrategy(new DatabaseShardingStrategy("create_time", new ModuloDatabaseShardingAlgorithm()))
                        .tableShardingStrategy(new TableShardingStrategy("create_time", new ModuloTableShardingAlgorithm())).build();
                DataSource dataSource = new ShardingDataSource(shardingRule);
                //2.获得数据库的连接
                conn=dataSource.getConnection();
                //构造一个statement对象来执行sql语句:主要有Statement,PreparedStatement,CallableStatement三种实例来实现
    //            stmt=conn.createStatement();
                //获得总个数
                String countSql = "select count(*) totalCount from (" + sql + " ) cout";
                PreparedStatement pstmt = conn.prepareStatement(countSql);
                rs=pstmt.executeQuery();
                count=rs.getInt("totalCount");
                StringBuilder sb = new StringBuilder("SELECT * FROM ");
                sb.append("( ");
                sb.append(sql);
                sb.append(") A limit " + ( pageNo* pageSize) + "," +  pageSize + "");
                PreparedStatement pstmts = conn.prepareStatement(sb.toString());
                rs=pstmts.executeQuery();
                //PreparedStatement pstmt = conn.prepareStatement(sql);
                //执行sql并返还结束 ;
    
                ResultSetMetaData md = rs.getMetaData(); //获得结果集结构信息,元数据
                int columnCount = md.getColumnCount();   //获得列数
                while (rs.next()) {
                    Map<String,Object> rowData = new HashMap<>();
                    for (int i = 1; i <= columnCount; i++) {
                        rowData.put(md.getColumnName(i), rs.getObject(i));
                    }
                    list.add(rowData);
                }
                if(rs !=null){//11.关闭记录集
                    try{
                        rs.close();
                    } catch (SQLException e){
                        e.printStackTrace();
                    }
                }
                if(conn !=null){//13.关闭连接 (记住一定要先关闭前面的11.12.然后在关闭连接,就像关门一样,先关里面的,最后关最外面的)
                    try{
                        conn.close();
                    }catch(SQLException e){
                        e.printStackTrace();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return new MyPage(pageRequest, list, count);
        }
        //将获得的数据库与java的链接返回(返回的类型为Connection)
        public static List<Map<String,Object>> getConnection(List<NewDataSource> dataSourceList, String sql){
            ResultSet rs=null;
            Connection conn=null;
            List<Map<String, Object>> list = new ArrayList<>();
            Integer count=0;
            try {
                Map<String, DataSource> dataSourceMap = createDataSourceMap(dataSourceList);
                DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap);
                TableRule[] tableRules=new TableRule[dataSourceList.size()];
                for (int i = 0; i <dataSourceList.size() ; i++) {
                    TableRule tableRule = TableRule.builder("device_log").actualTables(Arrays.asList("device_log")).dataSourceRule(dataSourceRule).build();
                    tableRules[i]=tableRule;
                }
                List<TableRule> newTableRule=Arrays.asList(tableRules);
                //TableRule orderTableRule = TableRule.builder("device_log").actualTables(Arrays.asList("device_log")).dataSourceRule(dataSourceRule).build();
                ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(newTableRule)
                        .databaseShardingStrategy(new DatabaseShardingStrategy("create_time", new ModuloDatabaseShardingAlgorithm()))
                        .tableShardingStrategy(new TableShardingStrategy("create_time", new ModuloTableShardingAlgorithm())).build();
                DataSource dataSource = new ShardingDataSource(shardingRule);
                //2.获得数据库的连接
                conn=dataSource.getConnection();
                PreparedStatement pstmt = conn.prepareStatement(sql);
                rs=pstmt.executeQuery();
                ResultSetMetaData md = rs.getMetaData(); //获得结果集结构信息,元数据
                int columnCount = md.getColumnCount();   //获得列数
                while (rs.next()) {
                    Map<String,Object> rowData = new HashMap<>();
                    for (int i = 1; i <= columnCount; i++) {
                        rowData.put(md.getColumnName(i), rs.getObject(i));
                    }
                    list.add(rowData);
                }
                if(rs !=null){//11.关闭记录集
                    try{
                        rs.close();
                    } catch (SQLException e){
                        e.printStackTrace();
                    }
                }
                if(conn !=null){//13.关闭连接 (记住一定要先关闭前面的11.12.然后在关闭连接,就像关门一样,先关里面的,最后关最外面的)
                    try{
                        conn.close();
                    }catch(SQLException e){
                        e.printStackTrace();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return list;
        }
        private static DataSource createDataSource(NewDataSource dataSource) {
            BasicDataSource result = new BasicDataSource();
            result.setDriverClassName("com.mysql.jdbc.Driver");
            String URL="jdbc:mysql://"+dataSource.getDataIp()+":"+dataSource.getDataCode()+"/"+dataSource.getDataDbName()+"?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8";
            result.setUrl(URL);
            result.setUsername(dataSource.getDataUserName());
            result.setPassword(dataSource.getDataUserPassword());
            return result;
        }
        private static Map<String, DataSource> createDataSourceMap(List<NewDataSource> dataSourceList) {
            Map<String, DataSource> mapList = new HashMap<>();
            for (int i = 0; i < dataSourceList.size(); i++) {
                NewDataSource dataSource=dataSourceList.get(i);
                DataSource dataSource1=createDataSource(dataSource);
                mapList.put(dataSource.getDataDbName(),dataSource1);
            }
            return mapList;
        }
        static class MyPage implements Page {
            private PageRequest pageRequest;
            private List<Map<String, Object>> objectList;
    
            private int total;
    
            public MyPage(PageRequest pageRequest, List<Map<String,Object>> objectList, int total) {
                this.pageRequest = pageRequest;
                this.objectList = objectList;
                this.total = total;
            }
    
            @Override
            public int getNumber() {
                return pageRequest.getPageNumber();
            }
    
            @Override
            public int getSize() {
                return pageRequest.getPageSize();
            }
    
            @Override
            public int getTotalPages() {
    
                if (total % pageRequest.getPageSize() == 0) {
                    return total/pageRequest.getPageSize();
                } else {
                    return total/pageRequest.getPageSize()+1;
                }
    
    
            }
    
            @Override
            public int getNumberOfElements() {
                return 0;
            }
    
            @Override
            public long getTotalElements() {
                return this.total;
            }
    
            @Override
            public Page map(Converter converter) {
                return null;
            }
    
    
            @Override
            public Iterator<Map<String,Object>> iterator() {
                return objectList.iterator();
            }
    
            @Override
            public List<Map<String,Object>> getContent() {
                return objectList;
            }
    
    
    
            @Override
            public boolean hasContent() {
                return objectList!=null&&objectList.size()>0;
            }
    
            @Override
            public Sort getSort() {
                return null;
            }
    
            @Override
            public boolean isFirst() {
                return pageRequest.getPageNumber() == 0;
            }
    
            @Override
            public boolean isLast() {
                return pageRequest.getPageNumber()==getTotalPages()-1;
            }
    
            @Override
            public boolean hasNext() {
                return pageRequest.getPageNumber()<getTotalPages()-1;
            }
    
            @Override
            public boolean hasPrevious() {
                return pageRequest.getPageNumber() >0;
            }
    
            @Override
            public Pageable nextPageable() {
                return null;
            }
    
            @Override
            public Pageable previousPageable() {
                return null;
            }
        }
    
    }
    

    因为我需要分页所以这里就再次封装一个

    ModuloDatabaseShardingAlgorithm

    import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
    import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
    import com.google.common.collect.Range;
    
    import java.util.Collection;
    import java.util.LinkedHashSet;
    
    /**
     * @ClassName ModuloDatabaseShardingAlgorithm
     * @Description TODO
     * @Author asus
     * @Date Created by asus on 2018/12/1012:10
     * @Version 1.0
     **/
    public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer>{
        @Override
        public String doEqualSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            for (String each : collection) {
                if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                    return each;
                }
            }
            throw new IllegalArgumentException();
        }
    
        @Override
        public Collection<String> doInSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
            for (Integer value : shardingValue.getValues()) {
                for (String tableName : collection) {
                    if (tableName.endsWith(value % 2 + "")) {
                        result.add(tableName);
                    }
                }
            }
            return result;
    
        }
    
        @Override
        public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
            Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
            for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
                for (String each : collection) {
                    if (each.endsWith(i % 2 + "")) {
                        result.add(each);
                    }
                }
            }
            return result;
    
        }
    }
    
    

    ModuloTableShardingAlgorithm

    import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
    import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
    import com.google.common.collect.Range;
    
    import java.util.Collection;
    import java.util.LinkedHashSet;
    
    /**
     * @ClassName ModuloTableShardingAlgorithm
     * @Description TODO
     * @Author asus
     * @Date Created by asus on 2018/12/1012:13
     * @Version 1.0
     **/
    public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
        @Override
        public String doEqualSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            for (String each : collection) {
                if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                    return each;
                }
            }
            throw new IllegalArgumentException();
        }
    
        @Override
        public Collection<String> doInSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
            for (Integer value : shardingValue.getValues()) {
                for (String tableName : collection) {
                    if (tableName.endsWith(value % 2 + "")) {
                        result.add(tableName);
                    }
                }
            }
            return result;
        }
    
        @Override
        public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<Integer> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
            Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
            for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
                for (String each : collection) {
                    if (each.endsWith(i % 2 + "")) {
                        result.add(each);
                    }
                }
            }
            return result;
        }
    }
    
    

    我把table的也给贴出来了
    引用的时候
    先查出动态的数据源信息 dataSourceList和编写好sql

    List<Map<String, Object>> mapList=DBUtil.getConnection(dataSourceList,sql);
    

    就可以得到查出的map集合,下面是我封装的分页
    pageRequest就是这样来的
    在这里插入图片描述
    分页调用

    Page<Map<String,Object>> pageInfo = DBUtil.getPage(dataSourceList,sql,pageRequest);
    

    到此动态数据源分库分页查询就结束了

    展开全文
  • 业务侧分库分表 根据账户切换不同数据库,多个账户在同一个库。例如0001-0005的客户在一个库。 根据账户分表,账户名_表名 其实使用mongodb不用业务侧做这种分库分表,历史原因,还是实现了。 分库 springboot ...

    springboot+mongodb动态数据源

    背景

    业务侧分库分表
    根据账户切换不同数据库,多个账户在同一个库。例如0001-0005的客户在一个库。
    根据账户分表,账户名_表名
    其实使用mongodb不用业务侧做这种分库分表,历史原因,还是实现了。

    分库

    springboot mongodb 的 MongoTemplate 默认是一个database。我们需要动态切换。所以需要改造。

    通过跟踪MongoTemplate构造我们发现,有一个关键的类MongoDatabaseFactory database工厂。
    在这里插入图片描述

    这个接口定义了一个方法getMongoDatabase()。而且他有一个简单的实现SimpleMongoClientDatabaseFactory。其中getMongoDatabase()获取的就是我们配置文件中的数据库名.

    // 默认的实现,getDefaultDatabaseName() 就是配置文件中的 database
    public MongoDatabase getMongoDatabase() throws DataAccessException {
    	return getMongoDatabase(getDefaultDatabaseName());
    }
    

    按照SimpleMongoClientDatabaseFactory自己实现一个Factory, 其他不变,重写getMongoDatabase()

    
    /**
     * 动态的 DynamicMongoDbFactory
     *
     * @author fengyi
     * @version 1.0
     * @date 2021/3/13 18:41
     */
    public class DynamicMongoDbFactory extends MongoDatabaseFactorySupport<MongoClient>
            implements DisposableBean {
    
        private final static Logger LOG = LoggerFactory.getLogger(DynamicMongoDbFactory.class);
    
        /**
         * Creates a new {@link DynamicMongoDbFactory} instance for the given {@code connectionString}.
         *
         * @param connectionString connection coordinates for a database connection. Must contain a database name and must not
         *                         be {@literal null} or empty.
         * @see <a href="https://docs.mongodb.com/manual/reference/connection-string/">MongoDB Connection String reference</a>
         */
        public DynamicMongoDbFactory(String connectionString) {
            this(new ConnectionString(connectionString));
        }
    
        /**
         * Creates a new {@link DynamicMongoDbFactory} instance from the given {@link MongoClient}.
         *
         * @param connectionString connection coordinates for a database connection. Must contain also a database name and not
         *                         be {@literal null}.
         */
        public DynamicMongoDbFactory(ConnectionString connectionString) {
            this(MongoClients.create(connectionString), connectionString.getDatabase(), true);
        }
    
        /**
         * Creates a new {@link DynamicMongoDbFactory} instance from the given {@link MongoClient}.
         *
         * @param mongoClient  must not be {@literal null}.
         * @param databaseName must not be {@literal null} or empty.
         */
        public DynamicMongoDbFactory(MongoClient mongoClient, String databaseName) {
            this(mongoClient, databaseName, false);
        }
    
        /**
         * Creates a new {@link DynamicMongoDbFactory} instance from the given {@link MongoClient}.
         *
         * @param mongoClient          must not be {@literal null}.
         * @param databaseName         must not be {@literal null} or empty.
         * @param mongoInstanceCreated
         */
        DynamicMongoDbFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) {
            super(mongoClient, databaseName, mongoInstanceCreated, new MongoExceptionTranslator());
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.MongoDbFactory#getSession(com.mongodb.ClientSessionOptions)
         */
        @Override
        public ClientSession getSession(ClientSessionOptions options) {
            return getMongoClient().startSession(options);
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.MongoDbFactoryBase#closeClient()
         */
        @Override
        protected void closeClient() {
            getMongoClient().close();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.MongoDbFactoryBase#doGetMongoDatabase(java.lang.String)
         */
        @Override
        protected MongoDatabase doGetMongoDatabase(String dbName) {
            return getMongoClient().getDatabase(dbName);
        }
    
        @Override
        public MongoDatabase getMongoDatabase() throws DataAccessException {
            String db = DbHolder.get() == null ? getDefaultDatabaseName() : DbHolder.get();
            if (LOG.isDebugEnabled()) {
                LOG.debug("DB Holder: {}", db);
            }
            return super.getMongoDatabase(db);
        }
    }
    

    分库信息上下文 ThreadLocal

    /**
     * 分库信息上下文
     *
     * @author fengyi
     * @version 1.0
     * @date 2021/3/15 13:55
     */
    public class DbHolder {
        private static final ThreadLocal<String> DB_NAME = new InheritableThreadLocal<>();
    
        public static void set(String dbName){
            DB_NAME.set(dbName);
        }
    
        public static String get(){
            return DB_NAME.get();
        }
    
        public static void remove(){
            DB_NAME.remove();
        }
    }
    

    注入bean

        /**
         * 动态分库
         * @param mongoClient
         * @return
         */
        @Primary
        @Bean
        public MongoDatabaseFactory dynamicMongoDbFactory(MongoProperties mongoProperties, MongoClient mongoClient){
            return new DynamicMongoDbFactory(mongoClient, mongoProperties.getDatabase());
        }
    

    除此之外还需要注入MongoClient驱动的配置,否则会启动报错

    @Configuration(
            proxyBeanMethods = false
    )
    @ConditionalOnClass({MongoClient.class})
    public class MongoClientConfig {
        public MongoClientConfig() {
        }
    
        @Bean
        @ConditionalOnMissingBean(MongoClient.class)
        public MongoClient mongo(MongoProperties properties, Environment environment,
                                 ObjectProvider<MongoClientSettingsBuilderCustomizer> builderCustomizers,
                                 ObjectProvider<MongoClientSettings> settings) {
            return new MongoClientFactory(properties, environment,
                    builderCustomizers.orderedStream().collect(Collectors.toList()))
                    .createMongoClient(settings.getIfAvailable());
        }
    }
    

    分表

    关键类BasicMongoPersistentEntity, 重写获取集合的方法。直接上代码 ,不废话了

    重写 MongoMappingContext 的 typeInformation方法

    /**
     * 重写MongoMappingContext
     *
     * @author fengyi
     * @version 1.0
     * @date 2021/3/15 14:11
     */
    public class MyMongoMappingContext extends MongoMappingContext {
    
    
        @Override
        protected <T> DynamicMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
            return new DynamicMongoPersistentEntity<T>(typeInformation);
        }
    }
    

    DynamicMongoPersistentEntity 继承BasicMongoPersistentEntity,重写getCollection()方法

    /**
     * 动态的 表名
     *
     * @author fengyi
     * @version 1.0
     * @date 2021/3/15 13:49
     */
    public class DynamicMongoPersistentEntity<T> extends BasicMongoPersistentEntity<T> {
    
        private final static Logger LOG = LoggerFactory.getLogger(DynamicMongoPersistentEntity.class);
    
        private final static String UNDERLINE = "_";
    
        /**
         * Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
         * collection name to the entities simple type name.
         *
         * @param typeInformation must not be {@literal null}.
         */
        public DynamicMongoPersistentEntity(TypeInformation<T> typeInformation) {
            super(typeInformation);
        }
    
        @Override
        public String getCollection() {
            String collection = super.getCollection();
            collection = AccountHolder.get() == null ? collection : AccountHolder.get().concat(UNDERLINE).concat(collection);
            if (LOG.isDebugEnabled()) {
                LOG.debug("Collection Holder: {}", collection);
            }
            return collection;
        }
    
    }
    

    AccountHolder 账号信息 ThreadLocal

    
    
    /**
     * 账号信息上下文
     *
     * @author fengyi
     * @version 1.0
     * @date 2021/3/15 13:55
     */
    public class AccountHolder {
        private static final ThreadLocal<String> ACCOUNT = new InheritableThreadLocal<>();
    
        public static void set(String account){
            ACCOUNT.set(account);
        }
    
        public static String get(){
            return ACCOUNT.get();
        }
    
        public static void remove(){
            ACCOUNT.remove();
        }
    }
    
    

    注入bean

        /**
         * 动态分表
         * @param applicationContext
         * @param properties
         * @param conversions
         * @return
         * @throws ClassNotFoundException
         */
        @Primary
        @Bean
        MyMongoMappingContext mongoMappingContext(ApplicationContext applicationContext, MongoProperties properties, MongoCustomConversions conversions) throws ClassNotFoundException {
    
            MyMongoMappingContext context = new MyMongoMappingContext();
            context.setInitialEntitySet((new EntityScanner(applicationContext)).scan(new Class[]{Document.class, Persistent.class}));
            Class<?> strategyClass = properties.getFieldNamingStrategy();
            if (strategyClass != null) {
                context.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
            }
    
            context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
            return context;
        }
    

    测试结果

    在这里插入图片描述

    在这里插入图片描述

    结束语

    文章只是描述了核心原理,具体设置账户和分库的逻辑,要根据实际业务场景来实现。比如从http header中获取,设置到AccountHolder。而分库信息DbHolder的设置,则应该有一张路由表,用来查找这个账户在那个库。有问题可以留言。

    展开全文
  • 但是最近因为项目要用到分库分表,所以让我研究一下看怎么实现。我想着上一篇博客讲了多环境的配置,不同的环境调用不同的数据库,那接下来就将一个环境用到多个库也就讲了。所以才有了这篇文章。我们先来看一下今天...

    前言

    说实话,这章本来不打算讲的,因为配置多数据源的网上有很多类似的教程。但是最近因为项目要用到分库分表,所以让我研究一下看怎么实现。我想着上一篇博客讲了多环境的配置,不同的环境调用不同的数据库,那接下来就将一个环境用到多个库也就讲了。所以才有了这篇文章。
    我们先来看一下今天项目的项目结构,在上篇博客的基础上进行了一定的增改,主要是增加了一个 config 文件,在dao 中分了两个子包mapper1 和mapper2 将原先的UserMapper 移入到了 mapper1 中。好了,开始正文
    file

    多数据源配置

    背景

    在这之前,还是先说一下为什么会存在多数据源。如果项目小的话,当然是所有的数据以及逻辑处理都操作同一个库。但是当业务量大的话,就会考虑到分库了。比我会将也日志入库数据存放到单独的数据库。或者用户权限信息单独的一个库存放。这种如果只是简单的分库,一个项目中就用到2~4 个数据库的话,这种多数据源配置就有意义啦。在配置文件中配置好这几个数据源,都有唯一标识。项目在启动加载的时候都进行初始化,然后在调用的时候,想用哪个库就哪个数据源的连接实例就好了。

    如果不整合 mybatis 的话,直接使用使用spring 自带的jdbcTemplate ,那配置多数据源,以及使用都比较简单,但是整合 mybatis 的话,就相对复杂点。我们一步一步来将讲解。

    修改配置文件

    打开application-dev.yml 文件,添加数据源。

    #开发环境
    spring:
      # 数据源配置
      datasource:
        one:
          driver-class-name: com.mysql.jdbc.Driver
          jdbc-url: jdbc:mysql://192.168.252.53:3306/zlflovemm?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
          username: root
          password: 123456
          max-idle: 10
          max-wait: 10000
          min-idle: 5
          initial-size: 5
        two:
          driver-class-name: com.mysql.jdbc.Driver
          jdbc-url: jdbc:mysql://192.168.252.53:3306/zlfdb?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
          username: root
          password: 123456
          max-idle: 10
          max-wait: 10000
          min-idle: 5
          initial-size: 5
    

    这里需要注意的是如果使用的是springboot 2.0 以上的,那么注意是 driver-class-name 和
    jdbc-url 而不是driverClassName和url.这里是一个坑,提醒大家一下。

    配置数据源

    接下来就需要我们手动的加载什么什么数据源了,我们在config中创建 DataSourcesConfig 类

    @Configuration
    public class DataSourcesConfig {
    
        @Bean(name="dbOne")
        @ConfigurationProperties(prefix = "spring.datasource.one")
        @Primary
        DataSource dbOne(){
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name="dbTwo")
        @ConfigurationProperties(prefix = "spring.datasource.two")
        DataSource dbTwo(){
            return DataSourceBuilder.create().build();
        }
    
    }
    

    这里定义了两个数据源的DataSource。分别是我们在配置文件中配置的one 和two 。注解@Primary 表示默认使用的数据源。

    MyBatisConfigOne 类

    @Configuration
    @MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
    public class MyBatisConfigOne {
        @Resource(name = "dbOne")
        DataSource dbOne;
    
        @Bean
        @Primary
        SqlSessionFactory sqlSessionFactory1()throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dbOne);
            return bean.getObject();
        }
        @Bean
        @Primary
        SqlSessionTemplate sqlSessionTemplate1() throws Exception{
            return new SqlSessionTemplate(sqlSessionFactory1());
        }
    }
    

    MyBatisConfigTwo 类

    @Configuration
    @MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
    public class MyBatisConfigTwo {
        @Resource(name = "dbTwo")
        DataSource dbTwo;
    
        @Bean
        SqlSessionFactory sqlSessionFactory2()throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dbTwo);
            return bean.getObject();
        }
        @Bean
        SqlSessionTemplate sqlSessionTemplate2()throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory2());
        }
    }
    

    注意连个文件的区别:
    file

    dao 层

    在dao 层创建了两个包mapper1 和mapper2 .包里面的UserMapper类的内容是完全一样,放在不同的包中只是区分使用哪个数据源。和昨天是一样的。

    public interface UserMapper {
    
        @Select("select id,username as userName,password,email,role_code as roleCode,gmt_create as gmtCreate,gmt_update as gmtUpdate,nickname as nickName,user_create as userCreate from sys_user")
        List<UserEntry> findUserList();
    
    
        @Insert({"insert into sys_user(username,password,email) values('${user.userName}','${user.password}','${user.email}')"})
        int add(@Param("user") UserEntry user);
    
        @Delete("delete from sys_user where id = #{id}")
        int delete(int id);
    }
    

    service 层

    UserService接口

    public interface UserService {
    
        List<UserEntry> findUserList();
    
        int addUser(String userName,String password,String email);
    
        int deleteUser(int id);
    
        List<UserEntry> findUserList2();
    
        int addUser2(String userName,String password,String email);
    
        int deleteUser2(int id);
    }
    
    

    UserServiceImpl类:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        protected UserMapper userMapper;
    
        @Autowired
        protected UserMapper2 userMapper2;
    
    
        @Override
        public List<UserEntry> findUserList() {
            return userMapper.findUserList();
        }
    
        @Override
        public int addUser(String userName, String password, String email) {
            UserEntry user=new UserEntry();
            user.setUserName(userName);
            user.setPassword(password);
            user.setEmail(email);
            return userMapper.add(user);
        }
    
        @Override
        public int deleteUser(int id) {
            return userMapper.delete(id);
        }
    
        @Override
        public List<UserEntry> findUserList2() {
            return userMapper2.findUserList();
        }
    
        @Override
        public int addUser2(String userName, String password, String email) {
            UserEntry user=new UserEntry();
            user.setUserName(userName);
            user.setPassword(password);
            user.setEmail(email);
            return userMapper2.add(user);
        }
    
        @Override
        public int deleteUser2(int id) {
            return userMapper2.delete(id);
        }
    }
    

    controller 层

    userController

    @Slf4j
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @RequestMapping(value = "/list",method = RequestMethod.GET)
        public List<UserEntry> findUserList(){
            return userService.findUserList();
        }
    
        @RequestMapping(value = "/add",method = RequestMethod.GET)
        public String addUser(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
            int falg=userService.addUser(uaserName,password,email);
            if(falg>0){
                return "success";
            }
            return "error";
        }
    		
        @RequestMapping(value = "/delete",method = RequestMethod.GET)
        public String deleteUser(@RequestParam(value = "id")int id){
            if(userService.deleteUser(id)>0){
                return "success";
            }
            return "error";
        }
    
        @RequestMapping(value = "/list2",method = RequestMethod.GET)
        public List<UserEntry> findUserList2(){
            return userService.findUserList2();
        }
    
        @RequestMapping(value = "/add2",method = RequestMethod.GET)
        public String addUser2(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
            int falg= userService.addUser2(uaserName,password,email);
            if(falg>0){
                return "success";
            }
            return "error";
        }
    
        @RequestMapping(value = "/delete2",method = RequestMethod.GET)
        public String deleteUser2(@RequestParam(value = "id")int id){
            if(userService.deleteUser2(id)>0){
                return "success";
            }
            return "error";
        }
    }
    

    测试

    file
    file
    可以看到是从不同的库中调出来的。这样就说明我们springboot配置多数据源整合mybatis 已经成功了。其实最主要就是config 包下的那三个配置类。其他的都是常见的业务逻辑,所以后面我就没有怎么讲了,代码会同步到github 上,想要实践的可以拿源码下来实践。

    到此我们springboot整合mybatis 多数据源已经配置好了,但是我们配置下来可以发现,我们如果想要配置几个数据源就得在 dao 层创建多少个子包用来区分。那如果我们数据量足够大,要分库分表而不是几个库呢?

    分库分表

    背景

    其实分库分表和多数据源是一样的,只不过是数据源更多了,多到在配置中配置所有的连接显得很臃肿,所以不得不另觅它法。分库分表就是 在项目中配置连接主库的连接,从主库中读取各个分库的连接,然后进行动态的加载,那个接口想调用那个分库就加载这个分库的连接。
    我现在项目做的由于不用整合mybatis 直接使用jdbcTemplate ,所以实现起来不是很麻烦。

    思路

    主要就两个类;
    GetDynamicJdbcTemplate类:手动的创建连接。

    /**
     * @ClassName GetDynamicJdbcTemplate
     * @Description 获取动态的jdbcTemplate
     * @Author zhulinfeng
     * @Date 2019/9/20 14:35
     * @Version 1.0
     */
    public class GetDynamicJdbcTemplate {
    
        private  String driverClassName;
        private  String url;
        private  String dbUsername;
        private  String dbPassword;
        private JdbcTemplate jdbcTemplate;
    
        public JdbcTemplate getJdbcTemplate() {
            return jdbcTemplate;
        }
    
        public GetDynamicJdbcTemplate(String driverClassName, String url, String dbUsername, String dbPassword){
            this.driverClassName=driverClassName;
            this.url=url;
            this.dbUsername=dbUsername;
            this.dbPassword=dbPassword;
            this.jdbcTemplate=new JdbcTemplate(getDataSource());
        }
    
        public DriverManagerDataSource getDataSource() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            dataSource.setUsername(dbUsername);
            dataSource.setPassword(dbPassword);
            return dataSource;
        }
    }
    

    GetJdbcTemplateMap类在项目启动的时候,会读取主库中的配置,将所有分库的连接都创建好放到map中。我们是按照地市分表的,接口在调用的时候根据前端传过来的地市就可以知道使用哪个数据库连接了。

    
    @Component
    @Slf4j
        public class GetJdbcTemplateMap implements ApplicationRunner {
    
        @Autowired
        @Qualifier("baseTemplate")
        private JdbcTemplate jdbcTemplate;
    
        public static Map<String,JdbcTemplate> JdbcTemplateMap=new HashMap<>();
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            String sql="CALL proc_baseinfo_cfg_dbsetting_query()";
            List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
            if(list!=null && !list.isEmpty()){
                insertMap(list);
            }
        }
    
        private void insertMap(List<Map<String, Object>> list){
            for(Map<String, Object> map :list){
                String url="jdbc:mysql://" map.get("serverip") ":" map.get("dbport") "/" map.get("dbname") "?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
                log.info(url);
                String  dbUsername=  map.get("user").toString();
                String  dbPassword=  map.get("password").toString();
                GetDynamicJdbcTemplate getDynamicJdbcTemplate=new GetDynamicJdbcTemplate(ConstantClass.DRIVERCLASSNAME,url,dbUsername,dbPassword);
                JdbcTemplate jdbcTemplate=getDynamicJdbcTemplate.getJdbcTemplate();
                JdbcTemplateMap.put(map.get("cityid").toString(),jdbcTemplate);
            }
        }
    }
    

    file

    在接口中调用也很方便。
    file

    但是上面讲的只适合我们自己特有的业务,并且也没有整合mybatis ,所以我就没有写在我自己的项目中,这里提供出来是给大家一个思路。

    番外

    也算是写完了这篇,感觉写的不是很好,但是有不知道怎么修改,暂时就先这样吧,后续有思路了再进行修改。又问问我为什么不先整合Thymeleaf 弄出页面来。之所以没有弄,是因为我想后期做前后端分离都是以接口的形式调用。所以想先将后端的部分都搭建好,再来整合前端的。
    好了,就说这么多啦,今天项目的代码也同步到github 上啦。
    github地址:https://github.com/QuellanAn/zlflovemm

    后续加油♡

    欢迎大家关注个人公众号 “程序员爱酸奶”

    分享各种学习资料,包含java,linux,大数据等。资料包含视频文档以及源码,同时分享本人及投递的优质技术博文。

    如果大家喜欢记得关注和分享哟❤
    file

    展开全文
  • 有这个的使用场景调研,遇到MySQL数据库的数据量大,希望分表,希望系统能自动处理分表数据,还有希望实现读写分离的业务需求,又不想使用Mycat类的中间件,希望直接能通过代码实现,那么现在属于Apache的顶级项目的...
  • 但是最近因为项目要用到分库分表,所以让我研究一下看怎么实现。我想着上一篇博客讲了多环境的配置,不同的环境调用不同的数据库,那接下来就将一个环境用到多个库也就讲了。所以才有了这篇文章。 我们先来看一下...
  • 分布式数据访问层中间件,旨在为供一个通用数据访问层服务,支持MySQL动态数据源、读写分离、分布式唯一主键生成器、分库分表动态化配置等功能,并且支持从客户端角度对数据源的各方面(比如连接池、SQL等)进行...
  • 来自阿里的用于支持数据库读写分离、分表分库的分布式中间件。 官网:http://www.mycat.io/ 1.2、MyCat原理 主要是通过对SQL的拦截,然后经过一定规则(如分片解析、路由分析、读写分离分析、缓存分析等),将SQL...
  • 通过使用sharding-jdbc分库分表,以及多数据源的切换
  • 分布式数据访问层中间件,旨在为供一个通用数据访问层服务,支持MySQL动态数据源、读写分离、分布式唯一主键生成器、分库分表动态化配置等功能,并且支持从客户端角度对数据源的各方面(比如连接池、SQL等)进行...
  • spring动态数据源方式来实现分库分表,主要依靠数据库标志表标志参数化,根据标志策略得到对应的不同的库,不同的表,然后依赖动态数据源加入事务管理, 数据库的映射关系实现库表的对应操作。 ...
  • 分库分表是为了支持高并发,数据量大两个问题的。 1.分表 单表数据量太大,会极大影响sql执行的性能,到了后面sql可能就跑的很慢了。 分表就是把一个表的数据放到多个表中,然后查询的时候就查一个表。比如按照用户...
  • spring+mybatis+druid数据源+sharding-jdbc分库分表
  • cuntaosccDataSource:分库分表数据源,appName数据库代号从dba获得,appRuleFile分库分表配置文件,useLocalConfig使用本地配置。 sqlMapClientPartitionsqlMapClientTemplatePartition:创建SqlMapCli
  • 数据库分库分表策略的具体实现方案 https://mp.weixin.qq.com/s?__biz=MzUxOTAxODc2Mg==&amp;mid=2247483791&amp;idx=4&amp;sn=d96a8696003bf193f2250d9fe266106a&amp;chksm=f98141eacef6c8fccb3b01...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,908
精华内容 7,163
关键字:

动态数据源和分库分表