MyBatis-Plus实用篇超完整教程
作者:冬天vs不冷
一、基础组件
简介
- MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的
增强工具
- 在MyBatis 的基础上
只做增强不做改变
,为简化开发、提高效率而生
1、BaseMapper接口API
- BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型
- Mapper继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
- BaseMapper接口,增删改返回影响数据条数的Integer
BaseMapper中提供的CRUD方法
增加:insert
删除:delete
- 修改:update
- update方法:entity实体对象某属性为null,则不会修改此属性
查询:select
- selectObjs方法:只返回第一个字段的值
- selectPage方法:分页需要添加分页插件,否则不生效
2、IService接口API
- 封装IService接口,进一步封装CRUD采用
get查询单行、remove删除、list查询集合、page分页前缀
命名方式 - IService接口,增删改返回是否操作成功的boolean
- 泛型 T 为任意实体对象
IService中提供的CRUD方法
增加:save
删除:remove
修改:update
新增或修改:主键存在则根据主键修改,主键不存在则新增
- 查询:单个get,集合listgetOne方法,多个抛出异常,第二个参数
throwEx设置为false
则获取第一条数据
查询记录数:count
分页查询:page
- 集合查询:listlistObjs方法,可以将查询结果T类型转换成V类型返回List<V>
3、创建Service层操作数据
Mapper接口:创建UserMapper对象并继承BaseMapper
@Mapper public interface UserMapper extends BaseMapper<User> {}
Service接口:创建UserService并继承IService
/** * UserService继承IService模板提供的基础功能 */ public interface UserService extends IService<User> {}
Service实现类:创建UserService的实现类并继承ServiceImpl
/** * ServiceImpl实现了IService,提供了IService中基础功能的实现 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
二、常用注解
1、@TableName
- 在实体类类型上添加@TableName(“t_user”),标识实体类对应的表
- 不加注解,默认驼峰转下划线则是表明,如实体OrderInfo,则默认对应表名order_info
@Data @TableName("t_user") public class User { private Long id; private String name; private Integer age; private String email; }
为实体类所对应的表名设置默认的前缀,那么就不需要在每个实体类上通过@TableName标识实体类对应的表
mybatis-plus: global-config: db-config: # 设置实体类所对应的表的统一前缀 table-prefix: t_
2、@TableId
2.1、value属性
不加注解,默认情况Long id就是主键如果想其他字段作为主键,且实体与数据库字段不一致,则添加@TableId(value = “tid”)
@Data public class User { @TableId(value = "tid") private Long uid; private String name; private Integer age; private String email; }
2.2、type属性
常用主键策略:
枚举值 | 描述 |
---|---|
IdType.AUTO | 数据库ID自增;该类型请确保数据库设置了ID自增,否则无效 |
IdType.INPUT | 用户输入ID;该类型可以通过自己注册自动填充插件进行填充 |
IdType.ASSIGN_ID(默认) | 主键类型为number或string的雪花算法;只有当插入对象ID 为空,才自动填充 |
IdType.ASSIGN_UUID | 主键类型为string,UUID.replace(“-”,“”);只有当插入对象ID 为空,才自动填充 |
IdType.NONE | 没有设置主键类型;跟随全局;全局的主键策略如果没有设置,默认是雪花算法 |
@Data public class User { @TableId(type = IdType.AUTO) private Long uid; private String name; private Integer age; private String email; }
Mybatis-plus中的内置雪花算法
- 在某些情况下,我们想提前获取这个ID,调用
com.baomidou.mybatisplus.core.toolkit.IdWorker.getId()
方法即可
3、@TableField
3.1、value属性
- 解决对象中字段名和数据库不匹配(没有遵循小驼峰或者完全不匹配)
//指定数据库字段名称 @TableField(value = "email") private string mail
解决关键字报错
// 执行的sql语句就变成`关键字`,这样sql就不会报错了 @TableField(value = "`desc`") private string desc
3.2、exist属性
解决对象中的属性字段在表中不存在的问题
@TableField(exist = false) private Boolean checked;
3.3、select属性
在查询操作中某个字段值不想被查询展示出来(比如密码password),可使用此注解
@TableField(select = false) private String password;
3.4、condition属性
预处理where查询条件
// 实体注解-实现模糊查询 @TableField(condition = SqlCondition.LIKE) private String name; // 业务代码 User user = new User(); user.setName("张"); userService.lambdaQuery(user).list(); // 打印sql SELECT id,user_name,age FROM user WHERE name LIKE CONCAT('%',?,'%')
链式查询不生效(没有模糊查询)
userService.lambdaQuery().eq(User::getName,"张").list();
3.5、update属性
- 预处理update set 部分
- 方式一:@TableField(update="%s+1") update 表 set 字段=字段+1 where …
- 方式二:@TableField(update="now()") 使用数据库时间,update 表 set 字段=now() where …
// 方式三: // 实体注解-实现修改字段时候,前后添加“**” 其中 %s 会填充为字段 @TableField(update = "CONCAT('**',%s,'**')") private String name; // 业务代码 User user = new User(); user.setId(1); user.setName("张三"); userService.updateById(user); // 打印sql UPDATE user SET name=CONCAT('**',name,'**') WHERE id=?
链式查询不生效(前后没有填充**)
userService.lambdaQuery().eq(User::getName,"张").list();
3.6、fill属性
解决每个数据库表都有create_time 和 update_time字段,我们可以使用自动填充功能维护这两个字段
// 实体字段注解 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; // 枚举类 public enum FieldFill { /** * 默认不处理 */ DEFAULT, /** * 插入填充字段 */ INSERT, /** * 更新填充字段 */ UPDATE, /** * 插入和更新填充字段 */ INSERT_UPDATE }
创建MyMetaObjectHandler配置类,实现MetaObjectHandler
接口
@Component public class MyMetaObjectHandler implements MetaObjectHandler { Date date = new Date(); @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", Date.class, date); this.strictInsertFill(metaObject, "updateTime", Date.class, date); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", Date.class, date); } }
3.7、insertStrategy、updateStrategy、whereStrategy属性
- insertStrategy:当insert操作时,该字段拼接insert语句时的策略
- updateStrategy:当更新操作时,该字段拼接set语句时的策略
- whereStrategy:表示该字段在拼接where条件时的策略
public enum FieldStrategy { /** * 忽略判断,所有字段都进行更新和插入 * 无论什么值,直接拼接,没有值,则拼接null */ IGNORED, /** * 只更新和插入非NULL值 * 相当于添加if判断,不为null才操作 * <if test="columnProperty != null">column=#{columnProperty}</if> */ NOT_NULL, /** * 只更新和插入非NULL值且非空字符串 * 相当于添加if判断,不为null而且不是空字符串才操作 * <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> */ NOT_EMPTY, /** * 默认NOT_NULL */ DEFAULT, /** * 永远不进行更新和插入 */ NEVER }
3.8、typeHandler属性
- 类型处理器,设置存入数据库的类型
- 设置mysql数据库字段info为json类型,@TableField(typeHandler = JacksonTypeHandler.class)表示将UserInfo对象转为json对象入库
- 此时user出现对象嵌套List<Address>,需要设置resultMap响应对象,@TableName(autoResultMap = true)表示自动映射resultMap
4、@TableLogic
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
实现逻辑删除
数据库中创建逻辑删除状态列,设置默认值为0
实体类中添加逻辑删除属性
测试删除功能,真正执行的是修改
public void testDeleteById(){ int result = userMapper.deleteById(1527472864163348482L); System.out.println(result > 0 ? "删除成功!" : "删除失败!"); System.out.println("受影响的行数为:" + result); }
此时执行查询方法,查询的结果为自动添加条件is_deleted=0
public void testSelectList(){ List<User> users = userMapper.selectList(null); }
三、条件构造器
1、wapper介绍
- Wrapper:条件构造抽象类,最顶端父类
- AbstractWrapper:用于查询条件封装,生成sql的where条件
- QueryWrapper:查询条件封装
- UpdateWrapper:update条件封装
- AbstractLambdaWrapper:使用Lambda语法
- LambdaQueryWrapper:用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper:用于Lambda语法更新Wrapper
- AbstractWrapper:用于查询条件封装,生成sql的where条件
2、构造器常用方法
函数名 | 说明 | 说明/例子 |
---|---|---|
eq | 等于= | 例:eq(“name”,“zhangsan”) —> name = ‘zhangsan’ |
ne | 不等于<> | 例:ne(“name”,“zhangsan”) —> name <> ‘zhangsan’ |
gt | 大于> | 例:gt(“age”,18) —> age > 18 |
ge | 大于等于>= | 例:ge(“age”,18) —> age >= 18 |
lt | 小于< | 例:lt(“age”,18) —> age < 18 |
le | 小于等于<= | 例:le(“age”,18) —> age <= 18 |
between | between 值1 and 值2 | 例:between(“age”,10,20) —> age between 10 and 20 |
notBetween | not between 值1 and 值2 | 例:notBetween(“age”,10,20) —> age not between 10 and 20 |
like | like ‘%值%’ | 例:like(“name”,“强”) —> name like ‘%强%’ |
notLike | not like ‘%值%’ | 例:notLike(“name”,“强”) —> name not like ‘%强%’ |
likeLeft | like ‘%值’ | 例:like(“name”,“飞”) —> name like ‘%强’ |
likeRight | like ‘值%’ | 例:like(“name”,“王”) —> name like ‘王%’ |
isNull | 字段 is null | 例:isNull(“emal”) —> email is null |
isNotNull | 字段 is not null | 例:isNotNull(“emal”) —> email is not null |
in | 字段 in (值1,值2…) | 例:in(“age”,{10,18,30}) —> age in (10,18,30) |
notIn | 字段 not in (值1,值2…) | 例:notIn(“age”,{10,18,30}) —> age not in (10,18,30) |
inSql | 字段 in ( sql语句 ) | inSql(“id”, “select id from table where name like ‘%J%’”) —> id in (select id from table where name like ‘%J%’) |
notInSql | 字段 not in ( sql语句 ) | notInSql(“id”, “select id from table where name like ‘%J%’”) —> id not in (select id from table where name like ‘%J%’) |
groupBy | 分组 group by 字段,… | 例:groupBy(“id”,“name”) —> group by id,name ⚠️ 注意:如果不查询聚合函数,默认返回第一条数据 |
orderBy | 排序 ordery by 字段,… | 例:orderBy(true,true,“id”,“name”) —> order by id asc,name asc |
orderByAsc | 升排序 order by 字段,… asc | 例:orderByAsc(“id”,“name”) —> order by id,name |
orderByDesc | 降排序 order by 字段,… desc | 例:orderByDesc(“id”,“name”) —> order by id desc,name desc |
having | having (sql语句) | having(“sum(age) > {0}”,18) —> having sum(age) > 18 |
or | 拼接or | 例:eq(“id”,1).or().eq(“name”,“老王”) —> id =1 or name = ‘老王’ |
and | and 嵌套 | 例:and(i -> i.eq(“name”,“李白”).ne(“status”,“活着”)) —> and (name = ’李白‘ and status <> ‘活着’) |
apply | 拼接sql | **例:apply(“date_format(date_time,‘%Y-%m-%d’) = {0}”,“2002-08-08”) —> date_fromat(date_time,‘%Y-%m-%d’) = ‘2008-08-08’ ⚠️ 注意:动态入参对应{index}部分,直接写sql语句,有sql注入风险 |
last | 拼接到sql的最后 | 例:last(“limit 5”) 注意事项:只能调用一次,多次调用以最后一次为准,有sql注入风险 |
exists | 拼接exists (sql语句) | 例:exists(“select id from table where age = 1”) |
not exists | 拼接not exists (sql语句) | 例:not exists(“select id from table where age = 1”) |
nested | 正常嵌套 不带and和or | 例:nested(i-> i.eq(“name”,“李华”).gt(“age”,20)) —> (name = “李华” and age > 20) |
3、组装条件示例
条件的优先级
sql语句规则:and和or一起用,and优先级高
/** 组装修改条件 将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改 **/ @Test public void test01() { /* UPDATE t_user SET user_name=?, email=? age > ? AND user_name LIKE ? OR email IS NOT NULL */ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.gt("age", 20) .like("user_name", "a") .or() .isNotNull("email"); User user = new User(); user.setName("小明"); user.setEmail("test@atguigu.com"); int result = userMapper.update(user, queryWrapper); System.out.println("result = " + result); } /** 条件优先级 将用户名中包含a并且(年龄大于20或邮箱为null)的用户信息修改 **/ @Test public void test02() { // lambda中条件优先级 /* UPDATE t_user SET user_name=?, email=? WHERE user_name LIKE ? AND (age > ? OR email IS NULL) */ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("user_name", "a") .and(i -> i.gt("age", 20).or().isNull("email")); User user = new User(); user.setName("小红"); user.setEmail("test@atguigu.com"); int result = userMapper.update(user, queryWrapper); System.out.println("result = " + result); }
Lambda表达式
@Test public void test3() { /* SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE user_name LIKE ? AND age <= ? */ String username = "a"; Integer ageBegin = null; Integer ageEnd = 30; //组装set子句 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); //避免使用字符串表示字段,防止运行时错误 queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username) .gt(ageBegin != null, User::getAge, ageBegin) .le(ageEnd != null, User::getAge, ageEnd); List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); } @Test public void test4() { /* UPDATE t_user SET user_name=?,email=? WHERE user_name LIKE ? AND (age > ? OR email IS NULL) */ LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.like(User::getName, "a") //lambda表达式内的逻辑优先运算 .and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail)); updateWrapper.set(User::getName, "小黑").set(User::getEmail, "abc@atguigu.com"); int result = userMapper.update(null, updateWrapper); System.out.println("result = " + result); }
四、扩展功能
1、链式查询与修改
IService接口中提供
- 链式查询:可以串着写多个条件
- .one()结尾:查询单条数据
- .list()结尾:查询多条数据
- .count()结尾:查询记录数
@Test public void test1(){ // 单个查询,多个报错 User user = userService.lambdaQuery().eq(User::getName, "Tom").one(); // 集合查询 List<User> userList = userService.lambdaQuery().like(User::getName, "J").eq(User::getAge,20).list(); // 记录数查询 Integer count = userService.lambdaQuery().like(User::getAge, 20).select(User::getName, User::getAge).count(); }
- 链式修改:可以串着写多个条件
- .update()结尾:修改set的字段(只修改set的字段,fill属性更新填充属性不会修改)
- .update(entity)结尾:修改entity数据的字段
- .remove()结尾:删除数据
@Test public void test2() { // set修改某些属性 userService.lambdaUpdate().eq(User::getName, "Tom").set(User::getId, 110).set(User::getAge, 15).update(); // 修改整个对象 User user = new User(); user.setId(110L); user.setAge(15); userService.lambdaUpdate().eq(User::getName, "Tom").update(user); // 根据名称删除 userService.lambdaUpdate().eq(User::getName, "Jack").remove(); }
2、静态工具类Db
Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db
@Test void testDbGet() { User user = Db.getById(1L, User.class); System.out.println(user); } @Test void testDbList() { // 利用Db实现复杂条件查询 List<User> list = Db.lambdaQuery(User.class) .like(User::getUsername, "o") .ge(User::getBalance, 1000) .list(); } @Test void testDbUpdate() { Db.lambdaUpdate(User.class) .set(User::getBalance, 2000) .eq(User::getUsername, "Rose"); }
这样就可以在任意serviceImpl中都可以使用任意的service,而不需要依赖注入了
4、自定义sql
4.1、自定义xml分页sql
UserMapper中定义接口方法
page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位
/** * 根据年龄查询用户列表,分页显示 */ Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
UserMapper.xml中编写SQL
<select id="selectPageVo" resultType="User"> select id,username as name,age,email from t_user where age > #{age} </select>
测试方法
- xml中没有分页语句,mybatisplus自动分页
- 前提必须有分页插件,否则没有分页效果
@Test public void testSelectPageVo(){ //设置分页参数 Page<User> page = new Page<>(1, 5); userMapper.selectPageVo(page, 20); //获取分页数据 List<User> list = page.getRecords(); list.forEach(System.out::println); System.out.println("当前页:"+page.getCurrent()); System.out.println("每页显示的条数:"+page.getSize()); System.out.println("总记录数:"+page.getTotal()); System.out.println("总页数:"+page.getPages()); System.out.println("是否有上一页:"+page.hasPrevious()); System.out.println("是否有下一页:"+page.hasNext()); }
4.2、@Select注解自定义sql
普通sql
@Select("select * from student where age = #{age} and user_name = #{userName}") Student getStudent( @Param("age") Integer age, @Param("userName") String userName);
带Wrapper条件sql
@Param("ew")
:固定写法,可以参照BaseMapper接口里的方法${ew.customSqlSegment}
:固定写法,在sql后面拼接条件,不需要写where关键字
@Param("page") Page<?> page
:Wrapper条件和分页可以共用,但是分页需要写在最前面
// 单表查询 @Select("select * from student ${ew.customSqlSegment}") Student getStudentById( @Param("ew") QueryWrapper<Student> wrapper); // 多表查询 @Select("SELECT u.* FROM student u INNER JOIN address a ON u.id = a.student_id ${ew.customSqlSegment}") List<Student> queryStudentListByWrapper(@Param("page") Page<Student> page, @Param("ew")QueryWrapper<User> wrapper);
4.3、连表left join和inner join的分页查询区别
left join
查询通过Page<?> page参数自动分页时候,查询总条数COUNT(*)时候会将left join去掉,也就是只查主表的数据- 此时如果有副表的字段作为查询条件会报错:Unknown column ‘xxx’ in ‘where clause’(主表找不到xxx字段)
// 情况1:如果查询条件中有address的字段,比如模糊查询地址名称 // 情况2:如果查询条件的字段,两个表都有,这里会有问题 // 自动分页的查询count(*)时候,就会抛出以上异常 @Select("SELECT u.* FROM student u LEFT JOIN address a ON u.id = a.student_id ${ew.customSqlSegment}") List<Student> queryStudentListByWrapper(@Param("page") Page<Student> page, @Param("ew")QueryWrapper<User> wrapper);
解决方式:嵌套一层select * from (连表复杂sql)as result ${ew.customSqlSegment}
@Select("SELECT * FROM (SELECT u.* FROM student u LEFT JOIN address a ON u.id = a.student_id) as result ${ew.customSqlSegment}") List<Student> queryStudentListByWrapper(@Param("page") Page<Student> page, @Param("ew")QueryWrapper<User> wrapper);
inner join
查询通过Page<?> page参数自动分页时候,查询总条数COUNT(*)时候不会将inner join去掉,所以一般不需要嵌套
4.4、连表查询条件Wrapper和响应IPage的泛型
- 查询条件Wrapper和响应IPage的泛型
非表实体
也行,只有符合数据库字段的驼峰命名
即可(需要创建实体对象) - 如果查询条件同时需要多个表字段,也可以
@Param("ew") Wrapper<?> queryWrapper
(不需要创建实体对象)
5、IPage的泛型转换(entity转换为vo)
// 分页查询 Page<UserEntity> userPage = super.lambdaQuery().page(new Page<>(request.getCurrent(), request.getSize())); // entity转换为vo IPage<UserVO> infoResPage = userPage.convert(item -> Convert.convert(UserVO.class, item));
6、SimpleQuery工具类
Simplequery可以对selectList查询后的结果用stream流
进行了一些封装,使其可以返回一些指定结果,简洁api的调用
示例
获取Student对象集合的名字集合
// 方式一 List<String> nameList = SimpleQuery.list( new LambdaQueryWrapper<Student>().like(Student::getUserName, "张"), Student::getUserName ); // 方式二 List<Student> list = studentService.list(); List<String> nameList2 = SimpleQuery.list2List(list, Student::getUserName);
先将Student对象的名称前后添加**再获取名字集合
List<String> nameList = SimpleQuery.list( new LambdaQueryWrapper<Student>().like(Student::getUserName, "李"), Student::getUserName, new Consumer<Student>() { @Override public void accept(Student student) { student.setUserName("**"+student.getUserName()+"**"); } } );
返回key=名字,value=年龄的map
Map<String, Integer> map = SimpleQuery.map( new LambdaQueryWrapper<>(), Student::getUserName, Student::getAge );
group分组,key位名字,value相同名字的Student对象集合
Map<String, List<Student>> group = SimpleQuery.group( new LambdaQueryWrapper<Student>(), Student::getUserName );
7、ActiveRecord
- ActiveRecord(活动记录,简称 AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录
- ActiveRecord 一直广受解释型动态语言( PHP 、 Ruby 等)的喜爱,通过围绕一个数据对象进行CRUD操作
- 仅仅需要让实体类继承Model类且实现主键指定方法
@Data @TableName(value = "student") public class Student extends Model<Student> { /** * 主键id */ private Long id; /** * 姓名 */ private String userName; /** * 年龄 */ private Integer age; } @Test void activeRecordAddQ{ Student student = new Student(); student.setUserName("李四"); student.setAge(12); student.insert(); }
Model抽象类方法
注意: 必须存在对应的原始mapper并继承baseMapper并且可以使用
五、插件
1、Mybatis插件
MybatisPlusInterceptor分页插件
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //注意使用哪种数据库 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
2、MyBatisX插件
2.1、生成逆向工程
找到我们需要生成的表点击右键
填写完信息以后下一步
继续填写信息
结果展示
2.2、快速生成CRUD
MyBaitsX可以根据我们在Mapper接口中输入的方法名【alt+Enter
】快速帮我们生成对应的sql语句
到此这篇关于MyBatis-Plus实用篇超完整教程的文章就介绍到这了,更多相关MyBatis-Plus实用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!