背景:
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar,这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目,ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。
数据库要实现读写分离,比较主流使用的有
sharding sphere
和mycat
,sharding
作为一个组件集成在应用内,Sharding
是一个Jar形式,在本地应用层重写Jdbc原生的方法,实现数据库分片形式。而mycat
则作为一个独立的应用需要单独部署,是一个基于第三方应用中间件数据库代理框架,客户端所有的jdbc请求都必须要先交给MyCa
t,再有MyCat
转发到具体的真实服务器中。MyCat
属于服务器端数据库中间件,更倾向于运维层面,而Sharding
是一个本地数据库中间件框架。从设计理念上看,两则确实有一定的相似性。主要流程都是SQL 解析 -> SQL 路由 -> SQL 改写 -> SQL 执行 -> 结果归并。但架构设计上是不同的。
Mycat
是基于Proxy
,它复写了MySQL
协议,将Mycat Server
伪装成一个MySQL
数据库,而Sharding
是基于 JDBC 的扩展,是以 jar 包的形式提供轻量级服务的。
一、单库分表
1.引入pom文件,并编写yml配置
<!-- 依赖shardingsphere -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
spring:
shardingsphere:
datasource:
names: m1 #配置库的名字,随意
m1: #配置目前m1库的数据源信息
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://xxxx:3306/xxxxx?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&rewriteBatchedStatements=true
username: root
password: xxxxx
maxActive: 20
initialSize: 1
maxWait: 60000
poolPreparedStqtements: true
maxPoolPreparedStatementPerConnection: 20
minIdle: 1
timeBetweenAEvictionRunsMills: 60000
minEvictableIdleTimeMills: 300000
validationQuery: slelect 1 from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
filters: stat, wall, log4j
sharding:
tables:
t_student: # 指定的数据库名
actualDataNodes: m1.t_student_$->{1..2}
tableStrategy:
inline: # 指定t_student表的分片策略,分片策略包括分片键和分片算法
shardingColumn: id
algorithmExpression: t_student_$->{id % 2 + 1}
keyGenerator: # 指定t_student表的主键生成策略为SNOWFLAKE
type: SNOWFLAKE #主键生成策略为SNOWFLAKE
column: id #指定主键
# t_student2: # 指定的数据库名2
# actualDataNodes: m1.t_student2_$->{1..2}
# tableStrategy:
# inline: # 指定t_student2表的分片策略,分片策略包括分片键和分片算法
# shardingColumn: id
# algorithmExpression: t_student2_$->{id % 2 + 1}
# keyGenerator: # 指定t_student2表的主键生成策略为SNOWFLAKE
# type: SNOWFLAKE #主键生成策略为SNOWFLAKE
# column: id #指定主键
props:
sql:
show: true
2、编写实体类,以及service进行测试(注意:实体类表名使用逻辑表明)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_student")
@ApiModel(value = "StudentEntity对象", description = "学生表")
public class StudentEntity implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("地址")
private String address;
@ApiModelProperty("年龄")
private Integer age;
@ApiModelProperty("数据创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
}
@EnableTransactionManagement
@Configuration //配置类
public class MybatisPlusConfig {
/**
*在实体类的字段上加上@Version注解
* @return 注册乐观锁插件, 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
分页插件 DbType:数据库类型(根据类型获取应使用的分页方言)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
//逻辑删除
// @Bean
// public ISqlInjector sqlInjector(){
// return new LogicSqlInjector();
// }
// /**
// * SQL执行效率插件
// */
// @Bean
// @Profile({"dev","test"})// 设置 dev test 环境开启
// public PerformanceInterceptor performanceInterceptor() {
// PerformanceInterceptor interceptor = new PerformanceInterceptor();
// //在工作中不允许用户等待
// interceptor.setMaxTime(100);// ms单位毫秒 设置sql可以执行的最大时间,如果超过了这个时间则不执行
// interceptor.setFormat(true);//
// return interceptor;
// }
}
@Slf4j
@Component //把处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
this.strictInsertFill(metaObject, "version", Integer.class, 1);
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill......");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
public interface StudentService extends IService<StudentEntity> {
}
@Service
public class StudentServiceImp extends ServiceImpl<StudentMapper, StudentEntity> implements StudentService {
}
@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {
@Resource
private StudentMapper studentMapper;
@Resource
private StudentService studentService;
@GetMapping("/save")
@ApiOperation("保存学生数据")
public MyResponse save(){
StudentEntity studentEntity = StudentEntity.builder().name("肖战").age(26).address("北京海淀中关村").build();
studentMapper.insert(studentEntity);
return MyResponse.success(studentEntity);
}
@GetMapping("/save1")
@ApiOperation("保存学生数据")
public MyResponse save1(){
StudentEntity studentEntity = StudentEntity.builder().name("肖战1").age(16).address("北京海淀中关村111").build();
studentService.save(studentEntity);
return MyResponse.success(studentEntity);
}
@ApiOperation("Mysql中条件检索学生信息")
@PostMapping("/search")
public MyResponse selectByMysql(@RequestBody StudentEntity studentEntity) throws Exception {
Preconditions.checkNotNull(studentEntity, "分页数据不能为空");
log.info("信息检索参数:{}", JSONUtil.toJsonPrettyStr(JSON.toJSONString(studentEntity)));
try {
//封装分页和排序条件
Page<StudentEntity> page = PageUtil.createPage(0, 10, "create_time");
LambdaQueryWrapper<StudentEntity> wrapper = Wrappers.lambdaQuery(StudentEntity.class)
.like(StringUtils.isNotBlank(studentEntity.getName()), StudentEntity::getName, studentEntity.getName())
.ge((Objects.nonNull(studentEntity.getAge())), StudentEntity::getAge, studentEntity.getAge())
.like(StringUtils.isNotBlank(studentEntity.getAddress()), StudentEntity::getAddress, studentEntity.getAddress());
page = studentMapper.selectPage(page, wrapper);
return MyResponse.success(page);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
二、读写分离
spring:
shardingsphere:
datasource:
# names: master,slave0,slave1
names: master,slave0
# 数据源 主库
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xxxxx:3306/xxxxx?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&rewriteBatchedStatements=true
username: xxxxx
password: xxxxx
# 数据源 从库0
slave0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xxxxx:3306/xxxxx?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&rewriteBatchedStatements=true
username: xxxxx
password: xxxxx
# # 数据源 从库1
# slave1:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.jdbc.Driver
# url: jdbc:mysql://xxxxx/xxxxx?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&rewriteBatchedStatements=true
# username: xxxxx
# password: xxxxx
# 读写分离
masterslave:
load-balance-algorithm-type: round_robin
name: ms
master-data-source-name: master
slave-data-source-names: slave0
# slave-data-source-names: slave0,slave1