springboot實現(xiàn)以代碼的方式配置sharding-jdbc水平分表
多數(shù)項目可能是已經(jīng)運行了一段時間,才開始使用sharding-jdbc。
本教程就如何配置sharding-jdbc,才能使代碼改動最少,對功能影響最少(如果已經(jīng)做了垂直分表,只有一部分子項目需要水平分表)給出一個簡單方案。
關(guān)于依賴
shardingsphere-jdbc-core-spring-boot-starter
官方給出了Spring Boot Starter配置
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
但是基于已有項目,添加shardingsphere自動配置是很惡心的事
為什么配置了某個數(shù)據(jù)連接池的spring-boot-starter(比如druid)和 shardingsphere-jdbc-spring-boot-starter 時,系統(tǒng)啟動會報錯?
回答:
1. 因為數(shù)據(jù)連接池的starter(比如druid)可能會先加載并且其創(chuàng)建一個默認數(shù)據(jù)源,這將會使得 ShardingSphere‐JDBC 創(chuàng)建數(shù)據(jù)源時發(fā)生沖突。
2. 解決辦法為,去掉數(shù)據(jù)連接池的starter 即可,sharing‐jdbc 自己會創(chuàng)建數(shù)據(jù)連接池。
一般項目已經(jīng)有自己的DataSource了,如果使用shardingsphere-jdbc的自動配置,就必須舍棄原有的DataSource。
shardingsphere-jdbc-core
為了不放棄原有的DataSource配置,我們只引入shardingsphere-jdbc-core依賴
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-core</artifactId> <version>4.1.1</version> </dependency>
如果只水平分表,只支持mysql,可以排除一些無用的依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.1.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-sql-parser-postgresql</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-sql-parser-oracle</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-sql-parser-sqlserver</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>encrypt-core-rewrite</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shadow-core-rewrite</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>encrypt-core-merge</artifactId>
</exclusion>
<exclusion>
<!-- 數(shù)據(jù)庫連接池,一般原有項目已引入其他的連接池 -->
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
<exclusion>
<!-- 也是數(shù)據(jù)庫連接池,一般原有項目已引入其他的連接池 -->
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</exclusion>
<exclusion>
<!-- 對象池,可以不排除 -->
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</exclusion>
<exclusion>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</exclusion>
<exclusion>
<!-- mysql驅(qū)動,原項目已引入,為了避免改變原有版本號,排除了吧 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
<exclusion>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</exclusion>
<exclusion>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
數(shù)據(jù)源DataSource
原DataSource
以Druid為例,原配置為
package com.xxx.common.autoConfiguration;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
/**
* @ClassName: DruidConfiguration
* @Description: Druid連接池配置
*/
@Configuration
@Slf4j
public class DruidConfiguration {
@Value("${spring.datasource.driver-class-name}")
private String driver;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${datasource.druid.initialsize}")
private Integer druid_initialsize = 0;
@Value("${datasource.druid.maxactive}")
private Integer druid_maxactive = 20;
@Value("${datasource.druid.minidle}")
private Integer druid_minidle = 0;
@Value("${datasource.druid.maxwait}")
private Integer druid_maxwait = 30000;
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("loginUsername", "root");
reg.addInitParameter("loginPassword", "root!@#");
//reg.addInitParameter("logSlowSql", "");
return reg;
}
/**
*
* @Title: druidDataSource
* @Description: 數(shù)據(jù)庫源Bean
* @param @return 參數(shù)說明
* @return DataSource 返回類型
* @throws
*/
@Bean
public DataSource druidDataSource() {
// 數(shù)據(jù)源
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver); // 驅(qū)動
druidDataSource.setUrl(url); // 數(shù)據(jù)庫連接地址
druidDataSource.setUsername(username); // 數(shù)據(jù)庫用戶名
druidDataSource.setPassword(password); // 數(shù)據(jù)庫密碼
druidDataSource.setInitialSize(druid_initialsize);// 初始化連接大小
druidDataSource.setMaxActive(druid_maxactive); // 連接池最大使用連接數(shù)量
druidDataSource.setMinIdle(druid_minidle); // 連接池最小空閑
druidDataSource.setMaxWait(druid_maxwait); // 獲取連接最大等待時間
// 打開PSCache,并且指定每個連接上PSCache的大小
druidDataSource.setPoolPreparedStatements(false);
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(33);
//druidDataSource.setValidationQuery("SELECT 1"); // 用來檢測連接是否有效的sql
druidDataSource.setTestOnBorrow(false); // 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。
druidDataSource.setTestOnReturn(false); // 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能
druidDataSource.setTestWhileIdle(false); // 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效
druidDataSource.setTimeBetweenLogStatsMillis(60000); // 配置間隔多久才進行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒
druidDataSource.setMinEvictableIdleTimeMillis(1800000); // 配置一個連接在池中最小生存的時間,單位是毫秒
// 當程序存在缺陷時,申請的連接忘記關(guān)閉,這時候,就存在連接泄漏
// 配置removeAbandoned對性能會有一些影響,建議懷疑存在泄漏之后再打開。在上面的配置中,如果連接超過30分鐘未關(guān)閉,就會被強行回收,并且日志記錄連接申請時的調(diào)用堆棧。
druidDataSource.setRemoveAbandoned(false); // 打開removeAbandoned功能
druidDataSource.setRemoveAbandonedTimeout(1800); // 1800秒,也就是30分鐘
druidDataSource.setLogAbandoned(false); // 關(guān)閉abanded連接時輸出錯誤日志
// 過濾器
List<Filter> filters = new ArrayList<Filter>();
filters.add(this.getStatFilter()); // 監(jiān)控
//filters.add(this.getSlf4jLogFilter()); // 日志
filters.add(this.getWallFilter()); // 防火墻
druidDataSource.setProxyFilters(filters);
log.info("連接池配置信息:"+druidDataSource.getUrl());
return druidDataSource;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
WebStatFilter webStatFilter = new WebStatFilter();
filterRegistrationBean.setFilter(webStatFilter);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
/**
*
* @Title: getStatFilter
* @Description: 監(jiān)控過濾器
* @param @return 參數(shù)說明
* @return StatFilter 返回類型
* @throws
*/
public StatFilter getStatFilter(){
StatFilter sFilter = new StatFilter();
//sFilter.setSlowSqlMillis(2000); // 慢sql,毫秒時間
sFilter.setLogSlowSql(false); // 慢sql日志
sFilter.setMergeSql(true); // sql合并優(yōu)化處理
return sFilter;
}
/**
*
* @Title: getSlf4jLogFilter
* @Description: 監(jiān)控日志過濾器
* @param @return 參數(shù)說明
* @return Slf4jLogFilter 返回類型
* @throws
*/
public Slf4jLogFilter getSlf4jLogFilter(){
Slf4jLogFilter slFilter = new Slf4jLogFilter();
slFilter.setResultSetLogEnabled(false);
slFilter.setStatementExecutableSqlLogEnable(false);
return slFilter;
}
/**
*
* @Title: getWallFilter
* @Description: 防火墻過濾器
* @param @return 參數(shù)說明
* @return WallFilter 返回類型
* @throws
*/
public WallFilter getWallFilter(){
WallFilter wFilter = new WallFilter();
wFilter.setDbType("mysql");
wFilter.setConfig(this.getWallConfig());
wFilter.setLogViolation(true); // 對被認為是攻擊的SQL進行LOG.error輸出
wFilter.setThrowException(true); // 對被認為是攻擊的SQL拋出SQLExcepton
return wFilter;
}
/**
*
* @Title: getWallConfig
* @Description: 數(shù)據(jù)防火墻配置
* @param @return 參數(shù)說明
* @return WallConfig 返回類型
* @throws
*/
public WallConfig getWallConfig(){
WallConfig wConfig = new WallConfig();
wConfig.setDir("META-INF/druid/wall/mysql"); // 指定配置裝載的目錄
// 攔截配置-語句
wConfig.setTruncateAllow(false); // truncate語句是危險,缺省打開,若需要自行關(guān)閉
wConfig.setCreateTableAllow(true); // 是否允許創(chuàng)建表
wConfig.setAlterTableAllow(false); // 是否允許執(zhí)行Alter Table語句
wConfig.setDropTableAllow(false); // 是否允許修改表
// 其他攔截配置
wConfig.setStrictSyntaxCheck(true); // 是否進行嚴格的語法檢測,Druid SQL Parser在某些場景不能覆蓋所有的SQL語法,出現(xiàn)解析SQL出錯,可以臨時把這個選項設(shè)置為false,同時把SQL反饋給Druid的開發(fā)者
wConfig.setConditionOpBitwseAllow(true); // 查詢條件中是否允許有"&"、"~"、"|"、"^"運算符。
wConfig.setMinusAllow(true); // 是否允許SELECT * FROM A MINUS SELECT * FROM B這樣的語句
wConfig.setIntersectAllow(true); // 是否允許SELECT * FROM A INTERSECT SELECT * FROM B這樣的語句
//wConfig.setMetadataAllow(false); // 是否允許調(diào)用Connection.getMetadata方法,這個方法調(diào)用會暴露數(shù)據(jù)庫的表信息
return wConfig;
}
}
可見,如果用自動配置的方式放棄這些原有的配置風險有多大
怎么改呢?
ShardingJdbcDataSource
第一步,創(chuàng)建一個interface,用以加載自定義的分表策略
可以在各個子項目中創(chuàng)建bean,實現(xiàn)此接口
public interface ShardingRuleSupport {
void configRule(ShardingRuleConfiguration shardingRuleConfig);
}
第二步,在DruidConfiguration.class中注入所有的ShardingRuleSupport
@Autowired(required = false) private List<ShardingRuleSupport> shardingRuleSupport;
第三步,創(chuàng)建sharding-jdbc分表數(shù)據(jù)源
//包裝Druid數(shù)據(jù)源
Map<String, DataSource> dataSourceMap = new HashMap<>();
//自定義一個名稱為ds0的數(shù)據(jù)源名稱,包裝原有的Druid數(shù)據(jù)源,還可以再定義多個數(shù)據(jù)源
//因為只分表不分庫,所有定義一個數(shù)據(jù)源就夠了
dataSourceMap.put("ds0", druidDataSource);
//加載分表配置
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
//要加載所有的ShardingRuleSupport實現(xiàn)bean,所以用for循環(huán)加載
for (ShardingRuleSupport support : shardingRuleSupport) {
support.configRule(shardingRuleConfig);
}
//加載其他配置
Properties properties = new Properties();
//由于未使用starter的自動裝配,所以手動設(shè)置,是否顯示分表sql
properties.put("sql.show", sqlShow);
//返回ShardingDataSource包裝的數(shù)據(jù)源
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
完整的ShardingJdbcDataSource配置
package com.xxx.common.autoConfiguration;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
/**
* @ClassName: DruidConfiguration
* @Description: Druid連接池配置
*/
@Configuration
@Slf4j
public class DruidConfiguration {
@Value("${spring.datasource.driver-class-name}")
private String driver;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${datasource.druid.initialsize}")
private Integer druid_initialsize = 0;
@Value("${datasource.druid.maxactive}")
private Integer druid_maxactive = 20;
@Value("${datasource.druid.minidle}")
private Integer druid_minidle = 0;
@Value("${datasource.druid.maxwait}")
private Integer druid_maxwait = 30000;
/**
* 默認不顯示分表SQL
*/
@Value("${spring.shardingsphere.props.sql.show:false}")
private boolean sqlShow;
@Autowired(required = false)
private List<ShardingRuleSupport> shardingRuleSupport;
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("loginUsername", "root");
reg.addInitParameter("loginPassword", "root!@#");
//reg.addInitParameter("logSlowSql", "");
return reg;
}
/**
*
* @Title: druidDataSource
* @Description: 數(shù)據(jù)庫源Bean
* @param @return 參數(shù)說明
* @return DataSource 返回類型
* @throws
*/
@Bean
public DataSource druidDataSource() {
// 數(shù)據(jù)源
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver); // 驅(qū)動
druidDataSource.setUrl(url); // 數(shù)據(jù)庫連接地址
druidDataSource.setUsername(username); // 數(shù)據(jù)庫用戶名
druidDataSource.setPassword(password); // 數(shù)據(jù)庫密碼
druidDataSource.setInitialSize(druid_initialsize);// 初始化連接大小
druidDataSource.setMaxActive(druid_maxactive); // 連接池最大使用連接數(shù)量
druidDataSource.setMinIdle(druid_minidle); // 連接池最小空閑
druidDataSource.setMaxWait(druid_maxwait); // 獲取連接最大等待時間
// 打開PSCache,并且指定每個連接上PSCache的大小
druidDataSource.setPoolPreparedStatements(false);
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(33);
//druidDataSource.setValidationQuery("SELECT 1"); // 用來檢測連接是否有效的sql
druidDataSource.setTestOnBorrow(false); // 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。
druidDataSource.setTestOnReturn(false); // 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能
druidDataSource.setTestWhileIdle(false); // 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效
druidDataSource.setTimeBetweenLogStatsMillis(60000); // 配置間隔多久才進行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒
druidDataSource.setMinEvictableIdleTimeMillis(1800000); // 配置一個連接在池中最小生存的時間,單位是毫秒
// 當程序存在缺陷時,申請的連接忘記關(guān)閉,這時候,就存在連接泄漏
// 配置removeAbandoned對性能會有一些影響,建議懷疑存在泄漏之后再打開。在上面的配置中,如果連接超過30分鐘未關(guān)閉,就會被強行回收,并且日志記錄連接申請時的調(diào)用堆棧。
druidDataSource.setRemoveAbandoned(false); // 打開removeAbandoned功能
druidDataSource.setRemoveAbandonedTimeout(1800); // 1800秒,也就是30分鐘
druidDataSource.setLogAbandoned(false); // 關(guān)閉abanded連接時輸出錯誤日志
// 過濾器
List<Filter> filters = new ArrayList<Filter>();
filters.add(this.getStatFilter()); // 監(jiān)控
//filters.add(this.getSlf4jLogFilter()); // 日志
filters.add(this.getWallFilter()); // 防火墻
druidDataSource.setProxyFilters(filters);
log.info("連接池配置信息:"+druidDataSource.getUrl());
if (shardingRuleSupport == null || shardingRuleSupport.isEmpty()) {
log.info("............分表配置為空,使用默認的數(shù)據(jù)源............");
return druidDataSource;
}
log.info("++++++++++++加載sharding jdbc配置++++++++++++");
//包裝Druid數(shù)據(jù)源
Map<String, DataSource> dataSourceMap = new HashMap<>();
//自定義一個名稱為ds0的數(shù)據(jù)源名稱,包裝原有的Druid數(shù)據(jù)源,還可以再定義多個數(shù)據(jù)源
//因為只分表不分庫,所有定義一個數(shù)據(jù)源就夠了
dataSourceMap.put("ds0", druidDataSource);
//加載分表配置
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
//要加載所有的ShardingRuleSupport實現(xiàn)bean,所以用for循環(huán)加載
for (ShardingRuleSupport support : shardingRuleSupport) {
support.configRule(shardingRuleConfig);
}
//加載其他配置
Properties properties = new Properties();
//由于未使用starter的自動裝配,所以手動設(shè)置,是否顯示分表sql
properties.put("sql.show", sqlShow);
//返回ShardingDataSource包裝的數(shù)據(jù)源
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
WebStatFilter webStatFilter = new WebStatFilter();
filterRegistrationBean.setFilter(webStatFilter);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
/**
*
* @Title: getStatFilter
* @Description: 監(jiān)控過濾器
* @param @return 參數(shù)說明
* @return StatFilter 返回類型
* @throws
*/
public StatFilter getStatFilter(){
StatFilter sFilter = new StatFilter();
//sFilter.setSlowSqlMillis(2000); // 慢sql,毫秒時間
sFilter.setLogSlowSql(false); // 慢sql日志
sFilter.setMergeSql(true); // sql合并優(yōu)化處理
return sFilter;
}
/**
*
* @Title: getSlf4jLogFilter
* @Description: 監(jiān)控日志過濾器
* @param @return 參數(shù)說明
* @return Slf4jLogFilter 返回類型
* @throws
*/
public Slf4jLogFilter getSlf4jLogFilter(){
Slf4jLogFilter slFilter = new Slf4jLogFilter();
slFilter.setResultSetLogEnabled(false);
slFilter.setStatementExecutableSqlLogEnable(false);
return slFilter;
}
/**
*
* @Title: getWallFilter
* @Description: 防火墻過濾器
* @param @return 參數(shù)說明
* @return WallFilter 返回類型
* @throws
*/
public WallFilter getWallFilter(){
WallFilter wFilter = new WallFilter();
wFilter.setDbType("mysql");
wFilter.setConfig(this.getWallConfig());
wFilter.setLogViolation(true); // 對被認為是攻擊的SQL進行LOG.error輸出
wFilter.setThrowException(true); // 對被認為是攻擊的SQL拋出SQLExcepton
return wFilter;
}
/**
*
* @Title: getWallConfig
* @Description: 數(shù)據(jù)防火墻配置
* @param @return 參數(shù)說明
* @return WallConfig 返回類型
* @throws
*/
public WallConfig getWallConfig(){
WallConfig wConfig = new WallConfig();
wConfig.setDir("META-INF/druid/wall/mysql"); // 指定配置裝載的目錄
// 攔截配置-語句
wConfig.setTruncateAllow(false); // truncate語句是危險,缺省打開,若需要自行關(guān)閉
wConfig.setCreateTableAllow(true); // 是否允許創(chuàng)建表
wConfig.setAlterTableAllow(false); // 是否允許執(zhí)行Alter Table語句
wConfig.setDropTableAllow(false); // 是否允許修改表
// 其他攔截配置
wConfig.setStrictSyntaxCheck(true); // 是否進行嚴格的語法檢測,Druid SQL Parser在某些場景不能覆蓋所有的SQL語法,出現(xiàn)解析SQL出錯,可以臨時把這個選項設(shè)置為false,同時把SQL反饋給Druid的開發(fā)者
wConfig.setConditionOpBitwseAllow(true); // 查詢條件中是否允許有"&"、"~"、"|"、"^"運算符。
wConfig.setMinusAllow(true); // 是否允許SELECT * FROM A MINUS SELECT * FROM B這樣的語句
wConfig.setIntersectAllow(true); // 是否允許SELECT * FROM A INTERSECT SELECT * FROM B這樣的語句
//wConfig.setMetadataAllow(false); // 是否允許調(diào)用Connection.getMetadata方法,這個方法調(diào)用會暴露數(shù)據(jù)庫的表信息
return wConfig;
}
}
分表策略
主要的類
創(chuàng)建幾個ShardingRuleSupport接口的實現(xiàn)Bean
@Component
public class DefaultShardingRuleAdapter implements ShardingRuleSupport {
@Override
public void configRule(ShardingRuleConfiguration shardingRuleConfiguration) {
Collection<TableRuleConfiguration> tableRuleConfigs = shardingRuleConfiguration.getTableRuleConfigs();
TableRuleConfiguration ruleConfig1 = new TableRuleConfiguration("table_one", "ds0.table_one_$->{0..9}");
ComplexShardingStrategyConfiguration strategyConfig1 = new ComplexShardingStrategyConfiguration("column_id", new MyDefaultShardingAlgorithm());
ruleConfig1.setTableShardingStrategyConfig(strategyConfig1);
tableRuleConfigs.add(ruleConfig1);
TableRuleConfiguration ruleConfig2 = new TableRuleConfiguration("table_two", "ds0.table_two_$->{0..9}");
ComplexShardingStrategyConfiguration strategyConfig2 = new ComplexShardingStrategyConfiguration("column_id", new MyDefaultShardingAlgorithm());
ruleConfig2.setTableShardingStrategyConfig(strategyConfig2);
tableRuleConfigs.add(ruleConfig2);
}
}
@Component
public class CustomShardingRuleAdapter implements ShardingRuleSupport {
@Override
public void configRule(ShardingRuleConfiguration shardingRuleConfiguration) {
Collection<TableRuleConfiguration> tableRuleConfigs = shardingRuleConfiguration.getTableRuleConfigs();
TableRuleConfiguration ruleConfig1 = new TableRuleConfiguration(MyCustomShardingUtil.LOGIC_TABLE_NAME, MyCustomShardingUtil.ACTUAL_DATA_NODES);
ComplexShardingStrategyConfiguration strategyConfig1 = new ComplexShardingStrategyConfiguration(MyCustomShardingUtil.SHARDING_COLUMNS, new MyCustomShardingAlgorithm());
ruleConfig1.setTableShardingStrategyConfig(strategyConfig1);
tableRuleConfigs.add(ruleConfig1);
}
}
其他的分表配置類
public class MyDefaultShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {
public String getShardingKey () {
return "column_id";
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
Collection<String> col = new ArrayList<>();
String logicTableName = shardingValue.getLogicTableName() + "_";
Map<String, String> availableTargetNameMap = new HashMap<>();
for (String targetName : availableTargetNameMap) {
String endStr = StringUtils.substringAfter(targetName, logicTableName);
availableTargetNameMap.put(endStr, targetName);
}
int size = availableTargetNames.size();
//=,in
Collection<String> shardingColumnValues = shardingValue.getColumnNameAndShardingValuesMap().get(this.getShardingKey());
if (shardingColumnValues != null) {
for (String shardingColumnValue : shardingColumnValues) {
String modStr = Integer.toString(Math.abs(shardingColumnValue .hashCode()) % size);
String actualTableName = availableTargetNameMap.get(modStr);
if (StringUtils.isNotEmpty(actualTableName)) {
col.add(actualTableName);
}
}
}
//between and
//shardingValue.getColumnNameAndRangeValuesMap().get(this.getShardingKey());
... ...
//如果分表列不是有序的,則between and無意義,沒有必要實現(xiàn)
return col;
}
}
public class MyCustomShardingAlgorithm extends MyDefaultShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {
@Override
public String getShardingKey () {
return MyCustomShardingUtil.SHARDING_COLUMNS;
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
Collection<String> col = new ArrayList<>();
String logicTableName = shardingValue.getLogicTableName() + "_";
Map<String, String> availableTargetNameMap = new HashMap<>();
for (String targetName : availableTargetNameMap) {
String endStr = StringUtils.substringAfter(targetName, logicTableName);
availableTargetNameMap.put(endStr, targetName);
}
Map<String, String> specialActualTableNameMap = MyCustomShardingUtil.getSpecialActualTableNameMap();
int count = (int) specialActualTableNameMap.values().stream().distinct().count();
int size = availableTargetNames.size() - count;
//=,in
Collection<String> shardingColumnValues = shardingValue.getColumnNameAndShardingValuesMap().get(this.getShardingKey());
if (shardingColumnValues != null) {
for (String shardingColumnValue : shardingColumnValues) {
String specialActualTableName = specialActualTableNameMap.get(shardingColumnValue);
if (StringUtils.isNotEmpty(specialActualTableName)) {
col.add(specialActualTableName);
continue;
}
String modStr = Integer.toString(Math.abs(shardingColumnValue .hashCode()) % size);
String actualTableName = availableTargetNameMap.get(modStr);
if (StringUtils.isNotEmpty(actualTableName)) {
col.add(actualTableName);
}
}
}
//between and
//shardingValue.getColumnNameAndRangeValuesMap().get(this.getShardingKey());
... ...
//如果分表列不是有序的,則between and無意義,沒有必要實現(xiàn)
return col;
}
}
@Component
public class MyCustomShardingUtil {
/**
* 邏輯表名
*/
public static final String LOGIC_TABLE_NAME = "table_three";
/**
* 分片字段
*/
public static final String SHARDING_COLUMNS = "column_name";
/**
* 添加指定分片表的后綴
*/
private static final String[] SPECIAL_NODES = new String[]{"0sp", "1sp"};
// ds0.table_three_$->{((0..9).collect{t -> t.toString()} << ['0sp','1sp']).flatten()}
public static final String ACTUAL_DATA_NODES = "ds0." + LOGIC_TABLE_NAME + "_$->{((0..9).collect{t -> t.toString()} << "
+ "['" + SPECIAL_NODES[0] + "','" + SPECIAL_NODES[1] + "']"
+ ").flatten()}";
private static final List<String> specialList0 = new ArrayList<>();
@Value("${special.table_three.sp0.ids:null}")
private void setSpecialList0(String ids) {
if (StringUtils.isBlank(ids)) {
return;
}
String[] idSplit = StringUtils.split(ids, ",");
for (String id : idSplit) {
String trimId = StringUtils.trim(id);
if (StringUtils.isEmpty(trimId)) {
continue;
}
specialList0.add(trimId);
}
}
private static final List<String> specialList1 = new ArrayList<>();
@Value("${special.table_three.sp1.ids:null}")
private void setSpecialList1(String ids) {
if (StringUtils.isBlank(ids)) {
return;
}
String[] idSplit = StringUtils.split(ids, ",");
for (String id : idSplit) {
String trimId = StringUtils.trim(id);
if (StringUtils.isEmpty(trimId)) {
continue;
}
specialList1.add(trimId);
}
}
private static class SpecialActualTableNameHolder {
private static volatile Map<String, String> specialActualTableNameMap = new HashMap<>();
static {
for (String specialId : specialList0) {
specialActualTableNameMap.put(specialId, LOGIC_TABLE_NAME + "_" + SPECIAL_NODES[0]);
}
for (String specialId : specialList1) {
specialActualTableNameMap.put(specialId, LOGIC_TABLE_NAME + "_" + SPECIAL_NODES[1]);
}
}
}
/**
* @return 指定ID的表名映射
*/
public static Map<String, String> getSpecialActualTableNameMap() {
return SpecialActualTableNameHolder.specialActualTableNameMap;
}
}
ShardingAlgorithm接口的子接口除了ComplexKeysShardingAlgorithm,還有HintShardingAlgorithm,PreciseShardingAlgorithm,RangeShardingAlgorithm;本教程使用了更通用的ComplexKeysShardingAlgorithm接口。
配置TableRuleConfiguration類時,使用了兩個參數(shù)的構(gòu)造器
public TableRuleConfiguration(String logicTable, String actualDataNodes) {}
TableRuleConfiguration類還有一個參數(shù)的的構(gòu)造器,沒有實際數(shù)據(jù)節(jié)點,是給廣播表用的
public TableRuleConfiguration(String logicTable) {}
groovy行表達式說明
ds0.table_three_$->{((0…9).collect{t -> t.toString()} << [‘0sp',‘1sp']).flatten()}
sharding-jdbc的groovy行表達式支持$->{…}或${…},為了避免與spring的占位符混淆,官方推薦使用$->{…}
(0..9) 獲得0到9的集合
(0..9).collect{t -> t.toString()} 數(shù)值0到9的集合轉(zhuǎn)換成字符串0到9的數(shù)組
(0..9).collect{t -> t.toString()} << ['0sp','1sp'] 字符串0到9的數(shù)組合并['0sp','1sp']數(shù)組,結(jié)果為 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ['0sp','1sp']]
flatten() 扁平化數(shù)組,結(jié)果為 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0sp', '1sp']
properties配置
#是否顯示分表SQL,默認為false spring.shardingsphere.props.sql.show=true #指定哪些列值入指定的分片表,多個列值以“,”分隔 #column_name為9997,9998,9999的記錄存入表table_three_0sp中 #column_name為1111,2222,3333,4444,5555的記錄存入表table_three_1sp中 #其余的值哈希取模后,存入對應(yīng)的table_three_模數(shù)表中 special.table_three.sp0.ids=9997,9998,9999 special.table_three.sp1.ids=1111,2222,3333,4444,5555
Sharding-jdbc的坑
任何SQL,只要select子句中包含動態(tài)參數(shù),則拋出類型強轉(zhuǎn)異常
禁止修改分片鍵,如果update的set子句中存在分片鍵,則不能執(zhí)行sql
結(jié)語
至此,簡單的單表分表策略就配置完成了
代碼沒有好壞,合適的就是最好的
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- spring boot使用sharding jdbc的配置方式
- 詳解springboot采用多數(shù)據(jù)源對JdbcTemplate配置的方法
- springboot2.0.0配置多數(shù)據(jù)源出現(xiàn)jdbcUrl is required with driverClassName的錯誤
- SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis)
- 詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源
- springboot+springJdbc+postgresql 實現(xiàn)多數(shù)據(jù)源的配置
- SpringBoot3+ShardingJDBC5.5.0 讀寫分離配置的實現(xiàn)
- SpringBoot?配置多個JdbcTemplate的實現(xiàn)步驟
- SpringBoot+MybatisPlus+jdbc連接池配置多數(shù)據(jù)源的實現(xiàn)
- Spring?JDBC配置與使用的實現(xiàn)
相關(guān)文章
spring-boot實現(xiàn)增加自定義filter(新)
本篇文章主要介紹了spring-boot實現(xiàn)增加自定義filter(新),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
Shiro + JWT + SpringBoot應(yīng)用示例代碼詳解
這篇文章主要介紹了Shiro (Shiro + JWT + SpringBoot應(yīng)用),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Spring?security?oauth2以redis作為tokenstore及jackson序列化失敗問題
這篇文章主要介紹了Spring?security?oauth2以redis作為tokenstore及jackson序列化失敗問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>2024-04-04
詳解Spring依賴注入:@Autowired,@Resource和@Inject區(qū)別與實現(xiàn)原理
這篇文章主要介紹了詳解Spring依賴注入:@Autowired,@Resource和@Inject區(qū)別與實現(xiàn)原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-06-06
java多線程批量拆分List導入數(shù)據(jù)庫的實現(xiàn)過程
這篇文章主要給大家介紹了關(guān)于java多線程批量拆分List導入數(shù)據(jù)庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2021-10-10
Springboot之@ConfigurationProperties注解解讀
在Spring Boot中,@EnableConfigurationProperties注解的主要作用是激活@ConfigurationProperties注解的配置屬性類,從而讓配置屬性類能被Spring容器管理,這樣的話,我們就可以在屬性類中輕松地使用@ConfigurationProperties來綁定配置文件中的屬性2024-10-10
關(guān)于Java的Condition接口最佳理解方式
這篇文章主要介紹了關(guān)于Java的Condition接口最佳理解方式,Condition就是實現(xiàn)了管程里面的條件變量,Java?語言內(nèi)置的管程里只有一個條件變量,而Lock&Condition實現(xiàn)的管程支持多個條件變量,需要的朋友可以參考下2023-05-05
dubbo服務(wù)引用之創(chuàng)建Invoker流程詳解
這篇文章主要為大家介紹了dubbo服務(wù)引用二之創(chuàng)建Invoker流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
SpringBoot中的@ConfigurationProperties注解解析
這篇文章主要介紹了SpringBoot中的@ConfigurationProperties注解解析,Spring源碼中大量使用了ConfigurationProperties注解,通過與其他注解配合使用,能夠?qū)崿F(xiàn)Bean的按需配置,該注解可以放在類上,也可以放在方法上,需要的朋友可以參考下2023-11-11

