最近开始了一个新项目用到了sharding-jdbc之前从来没用过,所以也是学了一段时间,摸爬滚打之后终于搭好了,下面来具体说说怎么使用的
<!-- 这里只是sharding的pom坐标 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.1.1</version>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
我们先开始比较重要的一个sharding的xml配置,这里面主要就是为我们的表和库进行分库分表的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd">
<!--主键生成策略(废弃,改为手动生成)-->
<sharding:key-generator id="defaultKeyGenerator" column="id" type="SNOWFLAKE"/>
<!--这里就是默认的分库策略 sharding-column就是分库的依据字段
precise-algorithm-ref就是分库的实现类 后面我们在讨论这个-->
<sharding:standard-strategy id="defaultDatabaseStrategy" sharding-column="id"
precise-algorithm-ref="preciseDataBaseAlgorithm"/>
<!--默认的分表策略,字段和上面的含义一样-->
<sharding:standard-strategy id="defaultTableStrategy" sharding-column="id"
precise-algorithm-ref="preciseTableAlgorithm"/>
<!-- 指定表的分库策略,跟上面区分开的目的就是防止某张表想要区分的字段值
不同可以用作区分 我这里都是依据id来分库分表所以写不写都行 -->
<sharding:standard-strategy id="tradeNoDatabaseStrategy" sharding-column="id"
precise-algorithm-ref="preciseDataBaseAlgorithm"/>
<!-- 同上 -->
<sharding:standard-strategy id="tradeNoTableStrategy" sharding-column="id"
precise-algorithm-ref="preciseTableAlgorithm"/>
<!-- 这个是针对某些不需要分库分表定义的无策略 -->
<sharding:none-strategy id="noneStrategy"/>
<!-- 源码里面sharding的数据源其实是一个map这里的
shardingDataSource本人理解就是对该map的一个统称
也就是sharding上面注入所有数据源的名称 -->
<sharding:data-source id="shardingDataSource">
<!--配置各个表规则 data-source-names就是我们定义的所有库名
default-database-strategy-ref就是我们对于这些库用的上述定义策略名
default-table-strategy-ref 我们对表使用的上述定义的策略名-->
<sharding:sharding-rule data-source-names="ds0,ds1" default-database-strategy-ref="defaultDatabaseStrategy"
default-table-strategy-ref="defaultTableStrategy">
<sharding:table-rules>
<!--需要分库分表进行分库分表 logic-table逻辑表,比如我们想要对t_order进行分表
分表后表名都是t_order0,t_order1 t_order就是逻辑表不存在的
actual-data-nodes这个就是所有表和库所在的节点
database-strategy-ref和table-strategy-ref就是我们上面定义的策略名
-->
<sharding:table-rule logic-table="t_order" actual-data-nodes="ds$->{0..1}.t_order$->{0..1}"
database-strategy-ref="tradeNoDatabaseStrategy" table-strategy-ref="tradeNoTableStrategy"/>
<!--不分库分表使用无策略 这里不分库分表的就直接指定库.表明 库和表的策略都使用不分库分表
这里着重说一下actual-data-nodes因为这张表不分库分表,所以这里的库名是写死的-->
<sharding:table-rule logic-table="t_no_strategy_table" actual-data-nodes="ds0.t_no_strategy_table"
database-strategy-ref="noneStrategy" table-strategy-ref="noneStrategy"/>
</sharding:table-rules>
</sharding:sharding-rule>
<sharding:props>
<prop key="sql.show">true</prop>
</sharding:props>
</sharding:data-source>
</beans>
接着就是我们的分库和分表的实现类
/**
* @createDate 2022/4/14
* 分库操作
*/
@Component
public class PreciseDataBaseAlgorithm<T extends Comparable<?>> implements PreciseShardingAlgorithm<T> {
/**
* @param availableTargetNames 当前已经定义的库名
* @param shardingValue 当前分库逻辑字段值 就是我们在上面xml定义的相关属性
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<T> shardingValue) {
// 这里通过分库的依据字段值取hash来分库
int hash = shardingValue.hashCode();
hash = hash < 0 ? -hash : hash;
int num = hash % availableTargetNames.size();
for (String availableTargetName : availableTargetNames) {
if (availableTargetName.endsWith(String.valueOf(num))) {
return availableTargetName;
}
}
throw new RuntimeException("分库错误");
}
}
/**
* @createDate 2022/4/14
* 分表策略
*/
@Component
public class PreciseTableAlgorithm<T extends Comparable<?>> implements PreciseShardingAlgorithm<T> {
/**
* @param availableTargetNames 当前分表的所有名字
* @param shardingValue 当前分表的依据字段值
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<T> shardingValue) {
int hashCode = shardingValue.hashCode();
hashCode = shardingValue.hashCode() < 0 ? -shardingValue.hashCode() : shardingValue.hashCode();
// 进行分库操作
int num = hashCode % availableTargetNames.size();
for (String availableTargetName : availableTargetNames) {
if(availableTargetName.endsWith(String.valueOf(num))){
return availableTargetName;
}
}
throw new RuntimeException("分表错误");
}
}
接着就是非常重要的数据源相关配置和注入了
切记:一定把启动类上面里面自动注入数据源给去掉
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
首先我们要把分库的所有库对象注入到ioc
/**
* @createDate 2022/4/14
*/
@Configuration
public class ShardingJdbcConfig implements BeanDefinitionRegistryPostProcessor {
private String pix = "spring.shardingsphere.datasource.";
@SneakyThrows
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 创建所有的sharding库的datasource ResourceBundle可以做直接获取src下面.properties的文件
ResourceBundle properties = ResourceBundle.getBundle("application");
List<String> strings = Splitter.on(",").splitToList(properties.getString("spring.shardingsphere.datasource.names"));
for (String str : strings) {
// 设置要注入的bean的相关属性和参数,这里就是要注入的数据源的url 名字 密码之类的 这里bean统一类型都是DruidDataSource
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(DruidDataSource.class)
.setInitMethodName("init")
.setDestroyMethodName("close")
.addPropertyValue("url", properties.getString(pix + str + ".url"))
.addPropertyValue("driverClassName", "com.mysql.jdbc.Driver")
.addPropertyValue("username", properties.getString(pix + str + ".username"))
.addPropertyValue("password", properties.getString(pix + str + ".password"))
.addPropertyValue("initialSize", "10")
.addPropertyValue("minIdle", "10")
.addPropertyValue("maxActive", "100")
.addPropertyValue("minEvictableIdleTimeMillis", "300000")
.addPropertyValue("timeBetweenEvictionRunsMillis", "60000")
.addPropertyValue("maxWait", "60000")
.addPropertyValue("validationQuery", "select 1")
.addPropertyValue("testOnBorrow", "false")
.addPropertyValue("testOnReturn", "false")
.addPropertyValue("testWhileIdle", "true")
.addPropertyValue("removeAbandonedTimeout", "1800").getBeanDefinition();
// 将对象注入到ioc并且给bean名字
registry.registerBeanDefinition(str,beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
接着定义数据源和区分各个不同的mapper用不同的数据源
@Configuration
/*
该注解导入的xml就是上面定义的分组分表相关策略的规则,也是为了创建ShardingDataSource
这里创建出来的ShardingDataSource通过翻看源码就是一个map,key就是每个数据源的名称
value就是每个库的datasource都是DataSource的实现类
*/
@ImportResource(value = "classpath:data-sharing.xml") // 注入ShardingDataSource
@MapperScan(basePackages = {"mybatis.noStrategy"},sqlSessionFactoryRef = "noStrategySqlSessionFactory")
@MapperScan(basePackages = {"mybatis.order"},sqlSessionFactoryRef = "orderSqlSessionFactory")
public class DataSourceConfig {
@Bean
// 这里的resource可以不加因为@Bean里面有一个Autowire,但是不推荐
@Resource
// 注入shardingDataSource
public DataSource shardingDatasource(DataSource shardingDataSource) {
return shardingDataSource;
}
@Bean
// 这里的primary很重要,因为下面也有相同类型的注入
@Primary
@Resource
public SqlSessionFactory orderSqlSessionFactory(DataSource shardingDataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 指定当前数据源给谁用
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mybatis/order/*.xml"));
sqlSessionFactoryBean.setDataSource(shardingDataSource);
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionFactory noStrategySqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 指定当前数据源给谁哪个包下面的mapper用
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mybatis/noStrategy/*.xml"));
// 获取数据源
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/ds0?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&useAffectedRows=true");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUserName("root");
sqlSessionFactoryBean.setDataSource(druidDataSource);
return sqlSessionFactoryBean.getObject();
}
}
接下来我们测试一下,这里我们的id都是uuid里面有英文很多同学会问,我们是通过取模的方式来进行分库分表的,这里的英文怎么办呢,这个就需要看下上面的策略实现类的具体代码了,我们通过id的值进行hash然后取余的所以这里不存在这类问题.
@RestController
@RequestMapping("/sharding")
public class ShardingController {
@Autowired
private OrderDao orderDao;
@Autowired
private NoStrategyDao noStrategyDao;
@RequestMapping(value = "/insert",produces = MediaType.APPLICATION_JSON_VALUE)
public String insert(){
Order order = new Order();
order.setId(StringUtils.replace(UUID.randomUUID().toString(),"-","").toUpperCase());
order.setName("张三");
int insert = orderDao.insert(order);
return String.valueOf(insert);
}
@RequestMapping(value = "/insertNoSharding",produces = MediaType.APPLICATION_JSON_VALUE)
public String insertNoSharding(){
Order order = new Order();
order.setId(StringUtils.replace(UUID.randomUUID().toString(),"-","").toUpperCase());
order.setName("张三");
int insert = noStrategyDao.insert(order);
return String.valueOf(insert);
}
}
结果:
多次插入可以看到不同库不同表都会有数据进入,而没有进行分库分表的就只会进入上面指定的库表
HintShardingAlgorithm算法使用
上述方式在使用分库分表的时候是精准的分库分表策略,指定了分库和分表的固定字段,如果我们某个业务需要在不同的场景下面需要通过不同的字段进行分库分表那么就可以使用下面的这种方式
xml配置修改
<!-- 指定分库策略 这里使用标签sharding:hint-startegy -->
<sharding:hint-strategy id="tradeNoDatabaseStrategy"
algorithm-ref="hintDataBaseAlgorithm"/>
<!-- 指定分表策略 -->
<sharding:hint-strategy id="tradeNoTableStrategy"
algorithm-ref="hintTableAlgorithm"/>
策略代码变动,这里的策略写的粗糙了点 大家可以根据自己需求进行修改
分表的策略除了名字不一样其他的都一样就不写出来了
@Component
public class HintDataBaseAlgorithm implements HintShardingAlgorithm<String> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
HintShardingValue<String> shardingValue) {
// 拿到后续通过HintManager传递过来的分库字段值进行hashCode
List<String> collect = shardingValue.getValues().stream().filter
(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList());
List<String> shardingData = new ArrayList<>();
for (String availableTargetName : availableTargetNames) {
int code = collect.get(0).hashCode();
if(code < 0){
code = -code;
}
// 之后hashcode模以库的数量 我这里是2 库名字:ds0,ds1
if (availableTargetName.endsWith(String.valueOf(code % 2))) {
shardingData.add(availableTargetName);
}
}
// 这里跟上面精确分库有所不同的这里返回的是Collection
return shardingData;
}
}
// 实际使用
public void testHintSharding(){
// 这里通过name 李四进行分库分表进入最终hash出来应该是一号库一号表
Order order = new Order();
order.setId("123");
order.setName("李四");
HintManager manager = HintManager.getInstance();
manager.addDatabaseShardingValue("t_order",order.getName());
manager.addTableShardingValue("t_order",order.getName());
orderDao.insert(order);
manager.close();
}
ds1 ::: insert into t_order1 values(?, ?) ::: [123, 李四]
public void testHintSharding(){
// 王五hash出来的就是0号库0号表
Order order = new Order();
order.setId("123");
order.setName("赵六");
HintManager manager = HintManager.getInstance();
manager.addDatabaseShardingValue("t_order",order.getName());
manager.addTableShardingValue("t_order",order.getName());
orderDao.insert(order);
manager.close();
}
ds0 ::: insert into t_order0 values(?, ?) ::: [123, 赵六]
public void testHintSharding(){
// 这次分库使用name 分表使用name2 hash出来就是0库1表
Order order = new Order();
order.setId("123");
order.setName("赵六");
order.setName2("李四");
HintManager manager = HintManager.getInstance();
manager.addDatabaseShardingValue("t_order",order.getName());
manager.addTableShardingValue("t_order",order.getName2());
orderDao.insert(order);
manager.close();
}
ds0 ::: insert into t_order1 values(?, ?) ::: [123, 赵六]
注意事项:因为HintManager底层使用的是ThreadLoacl所以切记每次使用结束以后进行close里面就是清除本地缓存的操作.