shardingjdbc配置多数据源,解决shardingjdbc对部分写法的不支持

问题: 使用过shardingjdbc的一般都知道,shardingjdbc有一些限制,并不支持一些sql,具体可登陆sharding官网查看

DataSource接口

  • 不支持timeout相关操作

Connection接口

  • 不支持存储过程,函数,游标的操作
  • 不支持执行native的SQL
  • 不支持savepoint相关操作
  • 不支持Schema/Catalog的操作
  • 不支持自定义类型映射

Statement和PreparedStatement接口

  • 不支持返回多结果集的语句(即存储过程,非SELECT多条数据)
  • 不支持国际化字符的操作
  • 对于ResultSet接口
  • 不支持对于结果集指针位置判断
  • 不支持通过非next方法改变结果指针位置
  • 不支持修改结果集内容
  • 不支持获取国际化字符
  • 不支持获取Array

JDBC 4.1

  • 不支持JDBC 4.1接口新功能

查询所有未支持方法,请阅读io.shardingsphere.core.jdbc.unsupported包。

那么当我们的系统中真的不可避免的出现了这个问题,改如何解决呢?

我们可以通过配置多数据源的方式来解决,当需要使用分库分表时,选择shardDataSource,其余情况选择默认数据源即可;

那么我们先说说shardingjdbc的原理,他是如何帮助我们进行分库分表的;
我们根据添加的debug断点跟随程序执行步骤可以看到
首先我们定位到
org.mybatis.spring.transaction.SpringManagedTransaction类中的openConnection()方法,在该方法中,我们通过当前的dataSource获取对应的Connection;
在往上走,定位到org.apache.ibatis.executor.SimpleExecutor类中的prepareStatement()方法,通过我们获取到的Connection来获取对应类型的Statement;
接下来,我们定位到org.apache.ibatis.executor.doQuery()方法,然后执行handler.query(stmt, resultHandler)该行代码,该代码调用org.apache.ibatis.executor.statement.PreparedStatementHandler
类下面的query()方法,通过我们上面获取到的Statement来调用不同的execute()方法,如果我们的statement类型为ShardingPreparedStatement,则会调用shardingjdbc的逻辑,经过解析、路由、合并等步骤获取结果并返回,
如果Statement的类型是DruidPooledPreparedStatement或者是其他数据库连接池定义的PreparedStatement,则会走各自的查询逻辑;

所以我们需要做的是:
设置当我们走分库分表逻辑时,返回ShardingConnection,当我们不走分库分表逻辑时,返回DruidPooledConnection;

所以我们需要做的是:
设置当我们走分库分表逻辑时,返回ShardingConnection,当我们不走分库分表逻辑时,返回DruidPooledConnection;

但是如何做呢,这个时候就体现了Spring的强大了,Spring预留了AbstractRoutingDataSource接口,可以让用户根据自身的需求选择不同的数据源,
在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源;
接下来我们将代码贴一下;

1.在ShardingSphereConfiguration.class(自己创建的类)中配置数据源
@Configuration
public class ShardingSphereConfiguration {

    @Value("${db.encrypted}")
    private boolean encrypted;

    @Value("${db.username}")
    private String username;

    @Value("${db.password}")
    private String password;

    @Value("${spring.datasource.url}")
    private String jdbcUrl;

    @Value("${spring.datasource.driver-class-name}")
    private String driverClass;

    @Value("${shardingjdbc.sql.show:true}")
    private String sqlShow;

    @Value("${mybatis.mapper-locations:classpath:mapper/**/*.xml}")
    private String mapperLocations;

    @Value("${shardingjdbc.behavior-table-count:300}")
    public Integer tableCount;

    @Value("${behavior-class-table-num:3}")
    public Integer classTableNum;

    @Value("${shardingjdbc.undetection_behavior-table-count:100}")
    public Integer undetectionTableCount;

/**
 * 配置sharding-jdbc的DataSource,给上层应用使用,
 * 这个DataSource包含所有的逻辑库和逻辑表,应用增删改查时,修改对应sql
 * 然后选择合适的数据库继续操作。
 *
 * @return
 * @throws SQLException
 */
@Bean(name = "shardingDataSource")
@Qualifier("shardingDataSource")
public DataSource shardingDataSource(@Qualifier("dataSource0") DataSource dataSource0) throws SQLException {
    ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
    //用户表配置,可以添加多个配置
    shardingRuleConfig.getTableRuleConfigs().add(getUserTableRuleConfiguration());
    List<String> dataSourceNames = new ArrayList<>();
    dataSourceNames.add("fcs");
    Map<String, Object> configMap = new HashMap<>();
    //打印SQL
    Properties props = new Properties();
    props.put("sql.show", sqlShow);
    return new ShardingDataSource(createDataSourceMap(dataSource0), new ShardingRule(shardingRuleConfig, dataSourceNames), configMap, props);
}

/**
 * 创建分表规则
 *
 *  2020-8-10 分表规则修改:以多个分片字段来分表以同时满足通过班级id查询和学生id查询
 * @return
 */
@Bean
TableRuleConfiguration getUserTableRuleConfiguration() {
    TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
    orderTableRuleConfig.setLogicTable(Constants.STUDENT_BEHAVIOR_TABLE_PREFIX);
    orderTableRuleConfig.setLogicIndex(Constants.STUDENT_ROUTE_COLUMN);
    orderTableRuleConfig.setTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration(Constants.STUDENT_ROUTE_COLUMN, new MultipleKeyComplexShardingAlgorithm(tableCount, classTableNum)));
    return orderTableRuleConfig;
}

/**
 * 获取数据源,即包含有多少个数据库,读入到系统中存放于map中
 */
private Map<String, DataSource> createDataSourceMap(DataSource dataSource0) {
    Map<String, DataSource> result = new HashMap<>(4);
    result.put("fcs", dataSource0);
    return result;
}

@Bean
@Primary
public DynamicDataSource dataSource(
        @Qualifier("shardingDataSource")DataSource shardingDataSource,
        @Qualifier("druidDataSource")DataSource druidDataSource) {
    Map<Object, Object> targetDataSource=new HashMap<>();
    targetDataSource.put(DatabaseType.SHARDING_DATA_SOURCE.getSource(),shardingDataSource);
    targetDataSource.put(DatabaseType.DRUID_DATA_SOURCE.getSource(),druidDataSource);

    DynamicDataSource dynamicDataSource=new DynamicDataSource();
    dynamicDataSource.setTargetDataSources(targetDataSource);
    dynamicDataSource.setDefaultTargetDataSource(druidDataSource);

    return dynamicDataSource;
}

/**
     * 设置数据源
     *
     * @return
     */
    @Bean(name = "dataSource0")
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource createDataSource() {
        if (encrypted) {
            username = EncryptionUtils.getDecryptGdprStr(username);
            password = EncryptionUtils.getDecryptGdprStr(password);
        }
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClass);
        druidDataSource.setUrl(jdbcUrl);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    @Bean("sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
        return sqlSessionFactoryBean.getObject();
    }

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

    /**
     * 需要手动配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactitonManager(DataSource shardingDataSource) {
        return new DataSourceTransactionManager(shardingDataSource);
    }

}
UseDataSource.class
package com.dahua.dss.bas.jdbc;
import java.lang.annotation.*;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
DatabaseType source();
}
DatabaseContextHolder.class
package com.dahua.dss.bas.jdbc;
public class DatabaseContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static void setDatabaseType(String databaseType){
        contextHolder.set(databaseType);
    }
    public static String getDatabaseType(){
        return contextHolder.get();
    }
    public static void clearDataSource() {
        contextHolder.remove();
    }
}
DatabaseType .class
package com.dahua.dss.bas.jdbc;
public enum DatabaseType {

    SHARDING_DATA_SOURCE("shardingDataSource","sharding-jdbc数据源"),
    DRUID_DATA_SOURCE("druidDataSource","druid数据源");
    private String source;
    private String desc;
    DatabaseType(String source,String desc){
        this.source=source;
        this.desc=desc;
    }
    public String getSource() {
        return source;
    }
    public void setSource(String source) {
        this.source = source;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
}
DynamicDataSource .class
package com.dahua.dss.bas.jdbc;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }
}
UseDataSourceAspect.class
package com.dahua.dss.bas.jdbc;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE-1)
public class UseDataSourceAspect {
    @Pointcut("@annotation(UseDataSource)")
    public void useDataSource() {
    }
    @Around("useDataSource() && @annotation(anno)")
    public Object dataSourceSwitcher(ProceedingJoinPoint joinPoint, UseDataSource anno) throws Throwable {
        DatabaseContextHolder.setDatabaseType(anno.source().getSource());
        try {
            Object result = joinPoint.proceed();
            return result;
        }catch (Exception e){
            throw e;
        }finally {
            DatabaseContextHolder.clearDataSource();
        }
    }

}

好了,以上就是今天要分享的代码;有问题欢迎给我留言;

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
好的,下面是使用Ruoyi框架实现Sharding-JDBC分表的步骤: 1. 引入Sharding-JDBC依赖 在pom.xml文件中添加Sharding-JDBC的依赖: ```xml <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-core</artifactId> <version>4.1.1</version> </dependency> ``` 2. 配置数据源 在application.yml中配置数据源信息,如下所示: ```yaml spring: datasource: # 主库 master: url: jdbc:mysql://localhost:3306/db_master?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # 从库 slave: url: jdbc:mysql://localhost:3306/db_slave?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver shardingsphere: datasource: names: master,slave # 配置主从库 master-slave-rules: ms: master-data-source-name: master slave-data-source-names: slave load-balance-algorithm-type: round_robin # 配置数据分片规则 sharding: tables: user_info: # 分库键 database-strategy: inline: sharding-column: user_id algorithm-expression: ds${user_id % 2} # 分表键 table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_${order_id % 2} actual-data-nodes: master.user_info_$->{0..1} key-generator: column: id type: SNOWFLAKE ``` 3. 创建数据表 创建两个数据库db_master和db_slave,每个数据库中创建user_info表,表结构如下: ```sql CREATE TABLE `user_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `order_id` bigint(20) NOT NULL COMMENT '订单ID', `name` varchar(50) NOT NULL COMMENT '姓名', `age` int(11) NOT NULL COMMENT '年龄', `address` varchar(200) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表'; ``` 4. 创建Mapper接口 在Ruoyi框架中,通常使用MyBatis-Plus进行数据库操作,因此需要创建对应的Mapper接口,如下所示: ```java public interface UserInfoMapper extends BaseMapper<UserInfo> { } ``` 5. 测试分库分表 在代码中使用Sharding-JDBC进行数据库操作,如下所示: ```java @Service public class UserServiceImpl implements IUserService { @Autowired private UserInfoMapper userInfoMapper; @Override public void addUser(UserInfo userInfo) { userInfoMapper.insert(userInfo); } @Override public List<UserInfo> getUserList() { return userInfoMapper.selectList(null); } } ``` 以上就是使用Ruoyi框架实现Sharding-JDBC分表的步骤。需要注意的是,Sharding-JDBC支持使用MyBatis-Plus的自动填充功能,因此需要手动设置分表键的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值