问题: 使用过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();
}
}
}
好了,以上就是今天要分享的代码;有问题欢迎给我留言;