Mybatis动态数据源-分库分表简单版实现

1、动态数据源【分库】

分库有两种选择方案:

  • 用dao接口注解+切面实现动态数据源
  • 利用表字段分库策略实现

此处采用第一种方案,第二种方案我将在分表是实现;

1.1 环境搭建
  • 引入以下依赖
     <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  • 启动类配置:
// 关闭数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DemoApplication {}

创建两个库, user_master\user_slave库,并在库中分表建立一个表:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int NOT NULL,
  `name` varchar(255) NULL DEFAULT NULL,
  `age` int NOT NULL DEFAULT 0 COMMENT '年龄',
  PRIMARY KEY (`id`),
  INDEX `c_index`(`name`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
1.2 数据准备
  • 配置数据源
spring.datasource.master.jdbc-url = jdbc:mysql://localhost:3306/user_master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.master.username = ***
spring.datasource.master.password = ***
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url = jdbc:mysql://localhost:3306/user_slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.slave.username = ***
spring.datasource.slave.password =***
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
  • 自定义数据源
@Configuration
@MapperScan(basePackages = "com.lonk.demo.template.dao", sqlSessionFactoryRef = "SqlSessionFactory")
public class DynamicDataSourceConfig {

    @Bean
    public  org.apache.ibatis.session.Configuration configuration() {
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setLogImpl(StdOutImpl.class);
        configuration.setUseColumnLabel(true);
        return configuration;
    }


    @Bean(name = "master")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource getDateSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slave")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource getDateSource2() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    public DynamicDataSource DataSource(@Qualifier("master") DataSource masterDataSource,
                                        @Qualifier("slave") DataSource slaveDataSource) {
        //这个地⽅是⽐较核⼼的targetDataSource 集合是我们数据库和名字之间的映射
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.DataBaseType.Master, masterDataSource);
        targetDataSource.put(DataSourceType.DataBaseType.Slave, slaveDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(masterDataSource);
        return dataSource;
    }


    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        // jdbc数据源整合mybatis的SqlSessionFactory
        bean.setDataSource(dynamicDataSource);
        // 设置配置: 类似mybatis-config.xml
        bean.setConfiguration(configuration());
        bean.setTypeAliasesPackage("com.lonk.demo.template.entity");
        // bean.setPlugins();
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource){
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}
public class DataSourceType {

    private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();


    public static void setDataBaseType(DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }
        TYPE.set(dataBaseType);
    }

    public static DataBaseType getDataBaseType() {
        DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Master : TYPE.get();
        return dataBaseType;
    }

    public static void clearDataBaseType() {
        TYPE.remove();
    }


    public enum DataBaseType {
        Master("master"),
        Slave("slave");

        private String name;

        private DataBaseType(String name) {
          this.name = name;
        }

        public String getName() {
          return name;
        }
    }
}
1.3 动态数据源

上面配置数据源默认为master, 如何实现动态切换数据源?
可以实现AbstractRoutingDataSource,确定数据源路由策略:

public class DynamicDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
        return dataBaseType;
    }
}

此处,采用注解+aop实现动态切换,配置不同的数据源调用不同的库:

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SharedDataSource {

    String value() default "master";
}


@Aspect
@Component
public class DataSourceAop {


    @Before("@annotation(dataSource)")
    public void changeDataSource(JoinPoint point, SharedDataSource dataSource) {
        String type = dataSource.value();
        if (DataSourceType.DataBaseType.Master.getName().equals(type)){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Master);
        } else if (DataSourceType.DataBaseType.Slave.getName().equals(type)){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Slave);
        } else {
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Master);
        }
    }

    @After("@annotation(dataSource)")
    public void restoreDataSource(JoinPoint point, SharedDataSource dataSource) {
    	// 注意清除,不影响其他使用默认数据源master
        DataSourceType.clearDataBaseType();
    }

}
1.4 示例
@RestController
@RequestMapping("/v1/api/user")
@Slf4j
public class UserController {

    @Resource
    private UserService userService;


    @GetMapping("/{id}")
    public User GetUser(@PathVariable("id") Integer id){
        User user = userService.getUserById(id);
        log.info("user: {}", user);
        return user;
    }
}

@Repository
public interface UserDao {

	 // 未配置默认为master
     @SharedDataSource("master")
     User getUserById(Integer id);
}
  <select id="getUserById" parameterType="java.lang.Integer" resultMap="UserMap">
        select * from user where id = #{id}
    </select>

在这里插入图片描述

2、分表的简单实现

在1的基础上我们来实现一个简单分表案例, 这里自定义mybatis插件,根据主键id实现分表查询:

2.1 数据表配置

在user_master、user_slave同时创建两张表:

CREATE TABLE `t_user_0`  (
  `id` int NOT NULL,
  `name` varchar(255) NULL DEFAULT NULL,
  `age` int NOT NULL DEFAULT 0 COMMENT '年龄',
  PRIMARY KEY (`id`),
  INDEX `c_index`(`name`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;

CREATE TABLE `t_user_1`  (
  `id` int NOT NULL,
  `name` varchar(255) NULL DEFAULT NULL,
  `age` int NOT NULL DEFAULT 0 COMMENT '年龄',
  PRIMARY KEY (`id`),
  INDEX `c_index`(`name`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
2.2 自定义分表插件

这个我以简单id查询为例,对于对象类型需要使用时需修改:

@Intercepts(@Signature(
            type = StatementHandler.class,
            method = "prepare",
            args = {Connection.class, Integer.class}))
public class TableShardInterceptor implements Interceptor {

    private static final String logicTable = "user";
    private static final List<String> actualTable = new ArrayList();
    static {
        actualTable.add("t_user_0");
        actualTable.add("t_user_1");
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String statementId = mappedStatement.getId();

        String sql = (String) metaObject.getValue("delegate.boundSql.sql");

        ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");;
        // Object parameterObject1 = statementHandler.getBoundSql().getParameterObject();
        Object parameterObject = parameterHandler.getParameterObject();
        sql = replaceSql(logicTable, parameterObject, sql);

        metaObject.setValue("delegate.boundSql.sql", sql);
        return invocation.proceed();
    }

    private String replaceSql(String logic, Object parameterObject, String sql) {
        if (parameterObject instanceof Integer) {
            // 分表策略: 根据ID取模
            Integer index = (Integer) parameterObject % actualTable.size();
            String actual = "t_" + logic + "_" + index ;
            return sql.replaceAll(logic, actual);
        } else {
            // 默认表
            String actual = actualTable.get(0);
            return sql.replaceAll(logic, actual);
        }
    }

    // 复杂参数对象处理
    private void paramProcess() {
        // 配置分表字段
//        String sharedField = "id";
//        String sharedTable = "order";
//        if (parameterObject instanceof MapperMethod.ParamMap) {
//            MapperMethod.ParamMap parameterMap = (MapperMethod.ParamMap) parameterObject;
//            Object valueObject = parameterMap.get(sharedField);
//            if (valueObject == null) {
//                throw new RuntimeException(String.format("入参字段%s无匹配", sharedField));
//            }
//
//            replaceSql(sharedTable, valueObject, sql);
//        } else {
//            if (isBaseType(parameterObject)) {
//
//            }
//            if (parameterObject instanceof Map){
//                Map<String,Object>  parameterMap =  (Map<String,Object>)parameterObject;
//                Object valueObject = parameterMap.get(sharedField);
//
//                replaceSql(sharedTable, valueObject, sql);
//            } else {
//                //非基础类型对象
//                Class<?> parameterObjectClass = parameterObject.getClass();
//                Field declaredField = parameterObjectClass.getDeclaredField(sharedField);
//                declaredField.setAccessible(true);
//                Object valueObject = declaredField.get(parameterObject);
//                //替换sql
//                replaceSql(sharedTable, valueObject, sql);
//            }
//        }
    }

    private boolean isBaseType(Object object) {
        if (object.getClass().isPrimitive()
                || object instanceof String
                || object instanceof Integer
                || object instanceof Double
                || object instanceof Float
                || object instanceof Long
                || object instanceof Boolean
                || object instanceof Byte
                || object instanceof Short) {
            return true;
        } else {
            return false;
        }
    }


    public static Object getValueByReflect(Object model, String paraName) throws Exception {
        Object value = new Object();
        Field[] fields = model.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getName().equals(paraName)) {
                value =  field.get(model);
                break;
            }
        }
        return value;
    }

}

将分表插件加到数据源:

修改DynamicDataSourceConfig的configuration

@Bean
    public  org.apache.ibatis.session.Configuration configuration() {
        TableShardInterceptor pagePlugin = new TableShardInterceptor();
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setLogImpl(StdOutImpl.class);
        configuration.setUseColumnLabel(true);
        configuration.addInterceptor(pagePlugin);
        return configuration;
    }

以上是根据表字段进行分表查询,类似也可实现分库查询,此处不再举例。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是 Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库的实战代码: 1. 添加依赖 在 `pom.xml` 文件中添加以下依赖: ```xml <dependencies> <!-- Sharding-JDBC --> <dependency> <groupId>io.shardingsphere</groupId> <artifactId>sharding-jdbc-core</artifactId> <version>4.1.1</version> </dependency> <!-- Mybatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.24</version> </dependency> </dependencies> ``` 2. 配置数据 在 `application.yml` 文件中配置数据: ```yaml spring: datasource: # 主库 master: url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # 从库 slave: url: jdbc:mysql://localhost:3306/db_slave?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver ``` 3. 配置 Sharding-JDBC 在 `application.yml` 文件中配置 Sharding-JDBC: ```yaml spring: shardingsphere: datasource: names: master, slave # 数据名称 master: type: com.zaxxer.hikari.HikariDataSource slave: type: com.zaxxer.hikari.HikariDataSource config: sharding: tables: user: actualDataNodes: master.user_$->{0..1} # 分规则,user_0 和 user_1 tableStrategy: inline: shardingColumn: id algorithmExpression: user_$->{id % 2} # 分规则,根据 id 取模 databaseStrategy: inline: shardingColumn: id algorithmExpression: master # 分库规则,根据 id 取模 bindingTables: - user # 绑定,即需要进行分库 ``` 4. 配置 Mybatis-Plus 在 `application.yml` 文件中配置 Mybatis-Plus: ```yaml mybatis-plus: configuration: map-underscore-to-camel-case: true # 下划线转驼峰 ``` 5. 编写实体类 创建 `User` 实体类,用于映射数据库中的 `user` : ```java @Data public class User { private Long id; private String name; private Integer age; } ``` 6. 编写 Mapper 接口 创建 `UserMapper` 接口,用于定义操作 `user` 的方法: ```java @Mapper public interface UserMapper extends BaseMapper<User> { } ``` 7. 编写 Service 类 创建 `UserService` 类,用于调用 `UserMapper` 接口中的方法: ```java @Service public class UserService { @Autowired private UserMapper userMapper; public User getById(Long id) { return userMapper.selectById(id); } public boolean save(User user) { return userMapper.insert(user) > 0; } public boolean updateById(User user) { return userMapper.updateById(user) > 0; } public boolean removeById(Long id) { return userMapper.deleteById(id) > 0; } } ``` 8. 测试 在 `UserController` 类中进行测试: ```java @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user") public User getUser(Long id) { return userService.getById(id); } @PostMapping("/user") public boolean addUser(@RequestBody User user) { return userService.save(user); } @PutMapping("/user") public boolean updateUser(@RequestBody User user) { return userService.updateById(user); } @DeleteMapping("/user") public boolean removeUser(Long id) { return userService.removeById(id); } } ``` 启动应用程序,访问 `http://localhost:8080/user?id=1` 可以得到 `id` 为 1 的用户信息。访问 `http://localhost:8080/user` 并传入用户信息,可以添加用户。访问 `http://localhost:8080/user` 并传入更新后的用户信息,可以更新用户信息。访问 `http://localhost:8080/user?id=1` 并使用 DELETE 方法,可以删除用户。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值