一、Insert
1、插入操作
在测试类里面添加:
@Test
public void addUser() {
User user = new User();
user.setName("lucy");
user.setAge(30);
user.setEmail("lucy@qq.com");
int insert = userMapper.insert(user); // 影响的行数
System.out.println("insert:" + insert);
}
2、主键策略
参考博客:https://www.cnblogs.com/haoxinyue/p/5208136.html
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。
(1)数据库自增长序列或字段
最常见的方式。利用数据库,全数据库唯一。
优点:
1)简单,代码方便,性能可以接受。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
3)在性能达不到要求的情况下,比较难于扩展。
4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。
5)分表分库的时候会有麻烦。
优化方案:
1)针对主库单点,如果有多个 Master 库,则每个 Master 库设置的起始数字不一样,步长一样,可以是 Master 的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。
(2)UUID
常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。
优点:
1)简单,代码方便。
2)生成 ID 性能非常好,基本不会有性能问题。
3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读。
(3)Redis生成ID
当使用数据库来生成 ID 性能不够要求的时候,我们可以尝试使用 Redis 来生成 ID。这主要依赖于Redis 是单线程的,所以也可以用生成全局唯一的ID。可以用 Redis 的原子操作 INCR 和 INCRBY 来实现。
可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的 ID。但是步长和初始值一定需要事先需要了。使用 Redis 集群也可以方式单点故障的问题。
另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
(4)snowflake算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
缺点:
1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
3、MP主键设置
在User类中的id变量上面添加注解@TableId,关于IdType的值:
- AUTO: 自动增长
- UUID(过时): 每次随机生成唯一值,推荐使用ASSIGN_UUID
- INPUT: 自己设置id值
- ID_WORKER_STR(过时): mp自带策略,雪花算法,生成19位值,适用于字符串类型,推荐使用ASSIGN_ID
- ID_WORKER(过时): mp自带策略,雪花算法,生成19位值,适用于数字类型,比如Long,推荐使用ASSIGN_ID
@TableId(type = IdType.ASSIGN_ID)
private Long id;
全局主键配置
要想影响所有实体的配置,可以设置全局主键配置
#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
二、Update
1、自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。 我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:
(1)数据表中添加字段
在数据库表中添加 datetime 类型的新字段 create_time、update_time
(2)实体类添加@TableField注解
/*
自动填充数据表中的crete_time、update_time字段
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
(3)实现元对象处理器接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/*
使用mybatis_plus实现添加操作,这个方法会执行
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
/*
使用mybatis_plus实现修改操作,这个方法会执行
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
2、根据id更新
在测试类里面添加:
@Test
public void updateUser() {
User user = new User();
//修改1号用户的年龄
user.setId(1381813686125056001L);
user.setAge(99);
int update = userMapper.updateById(user);
System.out.println("update:" + update);
}
效果如图:
3、乐观锁
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
(1)数据库中添加version字段
ALTER TABLE `user` ADD COLUMN `version` INT;
(2)实体类添加@Version注解
@Version
@TableField(fill = FieldFill.INSERT) // 设置默认值
private Integer version;
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法 - 在
**update(entity, wrapper)**
方法下,**wrapper**
不能复用!!!
(3)元对象处理器接口添加version的insert默认值
/*
使用mybatis_plus实现添加操作,这个方法会执行
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
// 设置版本号默认为1
this.setFieldValByName("version", 1, metaObject);
}
(4)创建配置类,添加乐观锁插件
创建config包和MpConfig配置类,把启动类头上的@MapperScan注解剪切到自定义配置类上面
@Configuration
@MapperScan("com.atguigu.mpdemo.mapper")
public class MpConfig {
/**
* 新版乐观锁插件,推荐使用!
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
/**
* 旧版,不建议使用。
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
(5)测试乐观锁
/*
测试乐观锁
*/
@Test
public void testOptimisticLocker() {
// 查询
User user = userMapper.selectById(1381855104893751298L);
// 修改数据
user.setName("Jackson");
user.setEmail("jackson@163.com");
// 执行更新
userMapper.updateById(user);
}
测试后分析打印的sql语句,将version的数值进行了加1操作
三、Select
根据id查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
通过多个id批量查询
完成了动态 sql 的 foreach 的功能
@Test
public void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
users.forEach(System.out::println);
}
分页查询
MyBatis-Plus 自带分页插件,只要简单的配置即可使用。
(1)配置类添加分页插件
@Configuration
@MapperScan("com.atguigu.mpdemo.mapper")
public class MpConfig {
/**
* 注册插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
/*
添加分页插件
*/
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
// 设置数据库的类型
pageInterceptor.setDbType(DbType.MYSQL);
// 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false
pageInterceptor.setOverflow(false);
// 单页分页条数限制,默认无限制
pageInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(pageInterceptor);
/*
添加乐观锁插件
*/
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
(2)测试使用
@Test
public void testSelectPage() {
// 创建page对象,传入两个参数:当前页和每页显示记录数
Page<User> page = new Page<>(1, 3);
// 调用MP分页查询的方法,分页所有数据封装到page对象里面
userMapper.selectPage(page, null);
// 通过page对象获取分页数据
System.out.println("当前页:" + page.getCurrent());// 1
System.out.println("每页数据list集合:" + page.getRecords());// [User(id=1, name=Jone, age=18, email=...
System.out.println("每页显示记录数:" + page.getSize());// 3
System.out.println("总记录数:" + page.getTotal());// 6
System.out.println("总页数:" + page.getPages());// 2
System.out.println("是否有下一页:" + page.hasNext());// true
System.out.println("是否有上一页:" + page.hasPrevious());// false
}
四、Delete
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据。
- 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。
物理删除
根据id删除
@Test
public void testDelete() {
userMapper.deleteById(1381855104893751298L);
}
通过多个id批量删除
@Test
public void testDelete() {
userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
}
逻辑删除
(1)数据库中添加deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` tinyint(1);
(2)实体类添加@TableLogic注解
@TableLogic
@TableField(fill = FieldFill.INSERT) // 设置默认值
private Integer deleted;
(3)元对象处理器接口添加deleted的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
// 设置版本号默认插入值为1
this.setFieldValByName("version", 1, metaObject);
// 设置deleted默认插入值为0
this.setFieldValByName("deleted", 0, metaObject);
}
(4)修改配置文件
# 此处为默认值
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
(5)测试
@Test
public void testLogicDelete() {
userMapper.deleteById(1381918538914136065L);
}
之后执行查询操作时,会自动查询deleted=0的数据:
五、Wrapper条件构造器
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : Entity 对象封装操作类,不是用 lambda 语法
- UpdateWrapper : Update 条件封装,用于 Entity 对象更新操作
- AbstractLambdaWrapper : Lambda 语法使用 Wrapper 统一处理解析 lambda 获取 column。
- LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
ge/gt/le/lt/eq/ne/isNull/isNotNull
- ge:>=
- gt: >
- le: <=
- lt: <
- eq:=
- ne:<> 或 !=
- isNull: is null
- isNotNull: is not null
@Test public void testWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name", "tom").ge("age", 12).isNotNull("email"); List<User> users = userMapper.selectList(wrapper); System.out.println(users); }
between/notBetween
包含大小边界
@Test
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 80);
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
allEq
同时满足条件
@Test
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "tom");
map.put("age", "60");
wrapper.allEq(map);
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
like/notLike/likeLeft/likeRight
@Test
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeRight("name", "e");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
in/notIn/inSql/notInSql/exists/notExists
@Test
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//wrapper.in("id", 1, 2, 3);
wrapper.inSql("id", "select id from user where age < 60");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
or/and
@Test
public void testUpdate1() { //修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>(); userUpdateWrapper
.like("name", "h")
.or()
.between("age", 20, 30);
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
还可以嵌套or/and
@Test
public void testUpdate2() { //修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>(); userUpdateWrapper
.like("name", "h")
.or(i -> i.eq("name", "李白").ne("age", 20));
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
orderBy/orderByDesc/orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
last
直接拼接到sql语句最后,只能调用一次,多次调用以最后一次为准,有 sql 注入的风险,请谨慎使用。
@Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
指定要查询的列
@Test
public void testSelectListColumn() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name", "age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
set/setSql
@Test
public void testUpdateSet() { //修改值
User user = new User();
user.setAge(99);
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
.setSql(" email = '123@qq.com'");//可以有子查询
int result = userMapper.update(user, userUpdateWrapper);
}