最近在用 Mybatis-Plus,嗯,是真他妈香!!!今天就来说说 Mybatis-Plus 的那些使用技巧
创建user表
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO user (id, name, age, email) VALUES
(1, 'glj1', 18, 'test1@199604.com'),
(2, 'glj1', 20, 'test2@199604.com'),
(3, 'glj1', 28, 'test3@199604.com'),
(4, 'glj1', 21, 'test4@199604.com'),
(5, 'glj1', 24, 'test5@199604.com');
注意:-- 真实开发中往往都会有这四个字段,version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)
导入依赖
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发,并非官方的!-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
创建application.yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/txds?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
# driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
业务代码
实体类
package com.glj.securitydemo.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* Created with IntelliJ IDEA.
*
* @Author: GuoLiangJun
* @Date: 2021/3/26
* @Time: 17:26
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
mapper接口
package com.glj.securitydemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.glj.securitydemo.pojo.User;
import org.springframework.stereotype.Repository;
/**
* @author jiomer
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
}
注意点,我们需要在主启动类上去扫描我们的mapper包下的所有接口 @MapperScan(value = "com.glj.securitydemo.mapper")
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SecuritydemoApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
配置日志
application.yml文件添加日志配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Mybatis-plus的CRUD
插入操作
@Test
public void testInsertUser(){
User user = new User();
user.setName("glj1");
user.setAge(24);
user.setEmail("admin@199604.com");
int result = userMapper.insert(user);
System.out.println("result:"+result);
System.out.println("user:"+user);
}
看到id会自动填充。数据库插入的id的默认值为:全局的唯一id
主键生成策略
1)主键自增 1、实体类字段上 @TableId(type = IdType.AUTO)
@Tableid
- 描述:主键注解
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 主键类型 |
IdType
值 | 描述 |
---|---|
AUTO | 数据库ID自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert前自行set主键值 |
ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认default方法) |
ID_WORKER | 分布式全局唯一ID 长整型类型(please use ASSIGN_ID ) |
UUID | 32位UUID字符串(please use ASSIGN_UUID ) |
ID_WORKER_STR | 分布式全局唯一ID 字符串类型(please use ASSIGN_ID ) |
具体注解到:https://mp.baomidou.com/guide/annotation.html#tablename
2、数据库id字段设置为自增!
3、再次测试(可以看到id值比上次插入的大1)id的生成策略源码解释
更新操作
@Test
public void testUpdateUser(){
User user = new User();
user.setName("111mybatis");
user.setAge(24);
user.setId(1L);
int i = userMapper.updateById(user);
System.out.println("i:"+i);
}
自动填充
创建时间、修改时间!这两个字段操作都是自动化完成的,我们不希望手动更新!阿里巴巴开发手册:所有的数据库表都要配置上gmt_create、gmt_modified!而且需要自动化!
1、在表中新增字段 gmt_create, gmt_modified
2、实体类字段属性上需要增加注解
/**
* 字段添加填充内容
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime gmtModified;
3.编写处理器来处理这个注解
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// MP3.3.0版本之前的配置类
// this.setFieldValByName("gmtCreate",new Date(),metaObject);
// this.setFieldValByName("gmtModified",new Date(),metaObject);
// MP3.3之后的配置类
this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// MP3.3.0版本之前的配置类
// this.setFieldValByName("gmtModified", new Date(),metaObject);
// MP3.3之后的配置类
this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
}
}
乐观锁
乐观锁 : 顾名思义,十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题, 再次更新值测试 悲观锁:顾名思义,十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
乐观锁实现方式:
取出记录时,获取当前version 更新时,带上这个version 执行更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败
乐观锁测试:
1.给数据库中增加version字段
2.实体类加对应的字段
@Version
private Integer version;
3.配置类
@MapperScan("com.glj.securitydemo.mapper")
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {
/**
* 注册乐观锁插件
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
4.测试
@Test
public void testOptimisticLocker(){
// 1、查询用户信息
User user = userMapper.selectById(1L);
// 2、修改用户信息
user.setName("test");
user.setEmail("123456@qq.com");
// 3、执行更新操作
userMapper.updateById(user);
}
查询操作
普通查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1);
System.out.println("user:"+user);
}
// 测试批量查询!
@Test
public void testSelectByBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
// 按条件查询之一使用map操作
@Test
public void testSelectByBatchIds(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","glj");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
条件查询(QueryWrapper)
如果说,我们需要查询的 SQL 语句如下:
SELECT * FROM user WHERE age = 24;
那么对应的代码可以为:
@Test
public void testQueryWrapper1(){
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("age",24);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
条件查询(QueryWrapper lambda)
@Test
public void testQueryWrapper2(){
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(User::getAge,24);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
普通分页查询
1、配置拦截器组件
/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2、直接使用Page对象
@Test
public void testSelectByPage(){
// 分页对象
Page<User> queryPage = new Page<>(0, 1);
// 分页查询
IPage<User> iPage = userMapper.selectPage(queryPage , null);
// 数据总数
long total = iPage.getTotal();
// 集合数据
List<User> list = iPage.getRecords();
System.out.println("total:"+total);
list.forEach(System.out::println);
}
分页查询(联表)
当我们需要关联表格分页查询时,普通分页查询已经满足不了我们的需求了,那么我们需要进行联表分页查询
假设我们需要的 SQL 语句如下:
select a.*,b.name as sex_text from user a
left join user_sex b
on a.id = b.id
where a.age > 20
那么我们需要进行如下操作
1、新建 UserVO.java
@Data
public class UserVO extends User{
private String sexText;
}
2.UserMapper.java 中新增:
IPage<UserVO> list(Page<UserVO> page, @Param(Constants.WRAPPER) Wrapper<UserVO> queryWrapper);
3.编写代码
@Test
public void testQueryWrapper3(){
QueryWrapper<UserVO> queryWrapper = new QueryWrapper();
queryWrapper.eq("age",24);
// 分页对象
Page<UserVO> queryPage = new Page<>(0, 10);
// 分页查询
IPage<UserVO> iPage = userMapper.list(queryPage , queryWrapper);
// 数据总数
Long total = iPage.getTotal();
// 集合数据
List<UserVO> list = iPage.getRecords();
list.forEach(System.out::println);
}
以上就是分页查询(联表)时的操作,这个应该是用的比较多的
AND 和 OR深入
queryWrapper 默认是按照 and 来连接的,但是在我们业务需求中,肯定会用到 or 来写 SQL
1.刚使用
假设我们需要的 SQL 语句如下:
SELECT
a.*
FROM
user a
WHERE
1 = 1
AND a.id <> 1
AND ( a.name = 'glj_test' OR a.email = 'admin@199604.com' )
那么我们可以这样写
@Test
public void testQueryWrapper4(){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// AND a.id <> 1
queryWrapper.ne(User::getId,1);
// AND ( a.`name` = 'glj_test' OR a.email = 'admin@199604.com' )
queryWrapper.and(i -> i.eq(User::getName,"glj_test").or().eq(User::getEmail,"admin@199604.com"));
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
以上就完成了初级的 and 和 or 联用的情况
2.咱们开始进阶了~
假设我们需要的 SQL 语句如下:
SELECT
a.*
FROM
user a
WHERE
1 = 1
AND a.id <> 1
AND ( (a.name = 'glj' AND a.age = 24) OR (a.email = 'admin@199604.com' or a.age = 24))
那么我们可以这样写:
@Test
public void testQueryWrapper5(){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// AND a.id <> 1
queryWrapper.ne(User::getId,1);
queryWrapper.and(i -> (i.and(j -> j.eq(User::getName,"glj").eq(User::getAge,24)))
.or(j -> j.eq(User::getEmail,"admin@199604.com").or().eq(User::getAge,24)));
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
以上就完成了复杂的 and 和 or 联用的情况
那么以后遇见更复杂的,可以按此规则继续进行拼接 SQL
具体查看官网API:https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
指定查询字段(select)
当我们只需要查询表格中的某几个字段,如果表格数据很大的话,我们不应该查询表格的所有字段,假如,我们需要的 SQL 如下:
SELECT
id,
`name`,
age
FROM
user
WHERE
1 = 1
AND age = 24
我们只需要查询年龄等于24的用户的 id、name、age,所以,可以这样写:
@Test
public void testQueryWrapper6(){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// 只查询 id,name,age
queryWrapper.select(User::getId, User::getName, User::getAge);
// 查询条件为:age = 24
queryWrapper.eq(User::getAge, 24);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
删除操作
物理删除
//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(5L);
}
// 通过id批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1L,3L));
}
// 通过map删除
@Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","glj");
userMapper.deleteByMap(map);
}
逻辑删除
物理删除 :从数据库中直接移除
逻辑删除 :在数据库中没有被移除,而是通过一个变量来让它失效!
deleted = 0 => deleted = 1 管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
1、在数据表中增加一个 deleted 字段
2、实体类中增加属性
@TableLogic
private Integer deleted;
3、配置文件:
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
4、测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(2L);
}
------------------------日志
JDBC Connection [HikariProxyConnection@1551760389 wrapping com.mysql.cj.jdbc.ConnectionImpl@345d053b] will not be managed by Spring
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 2(Long)
<== Updates: 1
注意:自己在 xml 文件中写的 SQL 不会自动加上逻辑删除条件
最后更多技巧请参考官网:https://mp.baomidou.com/