官网文档 https://baomidou.com/pages/24112f/
pom
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
Mybatis Plus常见注解
@TableName
用来指定表名,如果实体类符合驼峰命名,就可以省略不写,例如实体类为User
,数据库表名为user
,就可以不写 如果实体类名和表名不一样,可以使用@TableName
来指定。例如实体类为SysUser
,数据库表名为user
,就要使用注解,指定表名
@TableName("user")
public class SysUser{
}
@TableId
和@TableFieId
和@TableName
类似,实体类中属性和数据库表中的字段命名不一致的时候使用,@TableId
是用在主键ID上面的,而@TableFieId
用在普通属性上面, 需要注意的是,
- 如果数据库中字段以
is
开头的字段,类似is_ok
这种,就算符合驼峰命名,也得使用@TableFieId
@TableFieId("is_ok")
private Boolean isOK;
- 如果字段和数据库关键词一样,也得使用
@TableFieId
并且转义
@TableFieId("`order`")
private Integer order;
- 类中有不属于数据库的字段,要用
@TableFieId(exist=false)
标记,不然会被当成数据库表字段查询,引发报错
使用@TableId
的时候,可以指定主键的生成策略,有以下几种
@TableId(type = IdType.AUTO) //使用数据库ID自增策略
@TableId(type = IdType.NONE), //主键不自增。
@TableId(type = IdType.INPUT), //主键由外部输入,而不是由数据库自动生成。
@TableId(type = IdType.ASSIGN_ID), //使用雪花算法自动生成主键 ID,主键类型为 Long 或 String。
@TableId(type = IdType.ASSIGN_UUID), //自动生成不含中划线的 UUID 作为主键,主键类型为 String。
条件构造器
Wrapper
构造器条件:https://baomidou.com/pages/10c804/#abstractwrapper
- 查询
/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//select id,name,age from user where name like '%zhang%' and age>10
QueryWrapper<User> userQuery=new QueryWrapper<User>()
.select("id", "name", "age")
.like("name","zhang")
.gt("age",10);
List<User> users=userMapper.selectList(userQuery);
- 替换掉硬编码,通过反射设置条件
//select id,name,age from user where name like '%zhang%' and age>10
LambdaQueryWrapper<User> userQuery=new LambdaQueryWrapper<User>()
.select(User::getId, User::getName, User::getAge)
.like(User::getName,"zhang")
.gt(User::getAge,10);
List<User> users=userMapper.selectList(userQuery);
- 更新
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
//要更新的数据
User user=new User();
user.age(20);
//更新的条件,username='zhangsan'
QueryWapper<User> wrapper=new QueryWapper<User>().eq("username","zhangsan");
userMapper.update(user,wrapper);
- 批量更新
//update user set dept=a123 where id in (1,2)
ArrayList<Long> list=new ArrayList<Long>(){{
add(1L);
add(2L);
}};
UpdateWrapper<User> wrapper=new UpdateWrapper<>()
.setSql("dept=a123")
.in("id",list);
userMapper.update(null,wrapper);
Wappers
Wrappers 是一个用于构建SQL查询条件的工具类。 Wrappers 提供了一系列静态方法,代替了new一个构造器的方式,用于创建不同类型的查询条件包装器,以便在MyBatis-Plus的查询操作中使用。
查询:Wrappers.<实体类>lambdaQuery() 更新:Wrappers.<实体类>lambdaUpdate()
//查询,查询id为a13579的数据
UserloginUser = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getId,"a13579"));
//修改,修改id等于a13579的数据,name值改为zhangsan
userService.update(Wrappers.<User>lambdaUpdate().set(User::getName, "zhangsan").eq(User::getId, "a13579"));
查询username为zhangsan的数据
修改taskname为zhangsan的数据,改为lisi
自定义sql
条件使用wrapper构建,前面的语句,写在sql中
- 基于wrapper构造where条件
LambdaQueryWrapper<User> wrapper=new LambdaQueryWrapper<>().in("id",list);
String dept="a123";
//mapper接口中自定义的方法
userMapper.updateDeptById(dept,wrapper);
- 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ewmapper中接口方法:
void updateDeptById(@Param("dept")String dept,@Param("ew")LambdaQueryWrapper<User> wrapper)
- 自定义sql,并使用Wrapper条件
可以写在xml中,也可以使用@Select直接写在mapper方法上
<update id="updateDeptById">
update user set dept=#{dept} ${ew.customSqlSegment}
</update>
修改
- 根据id修改
User user=new User();
user.setId="a13579";
user.setName="zhangsan";
userMapper.updateById(user);
- 条件构造器作为参数进行更新
//把名字为zhangsan的用户年龄更新为18,其他属性不变
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name","zhangsan");
User user = new User();
user.setAge(18);
userMapper.update(user, updateWrapper);
要是只改一个值,还有一种方法
//只更新一个属性,把名字为zhangsan的用户年龄更新为18,其他属性不变
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name","zhangsan").set("age", 18);
userMapper.update(null, updateWrapper);
- lambda构造器
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(User::getName, "zhangsan").set(User::getAge, 18);
Integer rows = userMapper.update(null, lambdaUpdateWrapper);
类似动态sql的LambdaQueryWrapper
- lambdaQuery().
lambdaQuery().eq返回的其实就是一个wrapper,条件一共三个参数,参数1:boolean条件,类似动态sql的if,可选参数 参数2:条件的字段 参数3:条件的值 前面构造的是条件,最后一个list()是执行的方法,list()结果是个集合,还有其它的,例如one,返回的是一个对象 封装了一层又一层,总之能实现就行,太麻烦去找API,就直接Mapper上面使用@Select实现算了
- service
public interface IUserService extends IService<User> {
List<User> queryUsers(String name, String age, String email);
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public List<User> queryUsers(String name, String age, String email) {
/**
* select * from user
* <where>
* <if test="name!=null">
* and name=#{name}
* </if>
* <if test="email!=null">
* and email like #{email}
* </if>
* <if test="age!=null">
* and age > #{age}
* </if>
* </where>
*/
List<User> list = lambdaQuery()
.eq(StrUtil.isNotBlank(name), User::getName, name)
.gt(StrUtil.isNotBlank(age), User::getAge, age)
.like(StrUtil.isNotBlank(email), User::getEmail, email).list();
return list;
}
}
- lambdaUpdate()
set和eq都是三个参数,第一个参数可选set是要修改的值 eq是条件 update()是执行的方法
/**
* update user
* <set>
* <if test="age != null">
* age = #{age}
* </if>
* <if test="email != null">
* email = #{email}
* </if>
* where name = #{name}
*
* </set>
*/
boolean update = lambdaUpdate()
.set(StrUtil.isNotBlank(age), User::getAge, 10)
.set(StrUtil.isNotBlank(email), User::getEmail, "123@163.com")
.eq(StrUtil.isNotBlank(name), User::getName, name).update();
IService和BaseMapper
MyBatisPlus的IService和BaseMapper接口,提供了很多方法,只需要实现,就能调用,
- Mapper
public interface SysDeptMapper extends BaseMapper<SysDept> {
}
- IService这个IService接口中,有很多方法,但是又没有实现,具体的实现在IServiceImpl中,通过对应的IServiceImpl调用
Service
public interface ISysDeptService extends IService<SysDept> {
}
- ServiceImpl
实现类ServiceImpl,需要两个对象,一个是对应的Mapper,一个是对应的实体类,在ServiceImpl中,就是通过这个Mapper调用对应的Mapper,同时还要继承service接口
@Service
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements ISysDeptService {
}
用的时候,就可以通过SysDeptServiceImpl 调用了
要不要加@Mapper,
要加,为什么有的代码没有加,mapper也生效了,肯定是以下三种情况之一,只是没有注意到
- 在springboot启动类使用了
@MapperScan
指定mapper位置, - 在mapper接口使用
@Mapper
- 在mybatis的配置类中,使用了``@MapperScan
@Configuration
@MapperScan("")
public class MybatisConfig {
批量新增
- 普通for循环逐条插入(最差)
- mybatisplus的批量新增(ServiceImpl.saveBatch(),性能较好)
- 在数据库连接后面添加参数rewriteBatchedStatements=true,(速度最快)
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatisplus?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: asd#117as53$
代码生成
使用一款IDEA插件MyBatisPlus
- 安装插件
- 安装好插件后,右上角出现一个
Other
,配置数据库, - 生成代码代码
- 效果因为我填了module,但是又没有module,所以生成单独一个包里面
静态工具类Db
MybatisPlus封装了一个静态工具类,在比较新的版本中才有,就是Db
使用方法,Db.
的方式使用,它的方法和Mapper中的方法基本一样,直接通过Db写条件就可以了
/*
这里的lambdaQuery()能直接用,是因为public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {,ServiceImpl中有lambdaQuery()
List<User> list = lambdaQuery().eq(StrUtil.isNotBlank(name), User::getName, name)
.gt(StrUtil.isNotBlank(age), User::getAge, age)
.like(StrUtil.isNotBlank(email), User::getEmail, email).list();*/
List<User> list = Db.lambdaQuery(User.class)
.gt(StrUtil.isNotBlank(age), User::getAge, age)
.like(StrUtil.isNotBlank(email), User::getEmail, email).list();
List<User> users = userService.listByIds(ids);
List<String> collect = users.stream().map(User::getId).collect(Collectors.toList());
List<User> list = Db.lambdaQuery(User.class).in(User::getId, collect).list();
逻辑删除
将一个字段标记为指定值,例如默认delete=0,逻辑删除后delete=1只需要修改配置文件,remove相关的方法就变成了逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-fied: delete #全局逻辑删除的实体类中的代表逻辑删除的字段
logic-delete-value: 1 #逻辑删除 1
logic-not-delete-value: 0 #逻辑未删除 0
枚举处理器
- 加配置
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
- 定义枚举
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
@Getter
public enum DeleteEnums {
YES(0,"正常"),
NO(1,"删除");
@EnumValue //使用@EnumValue,对应的是数据库里的值
private final int value;
@JsonValue //@JsonValue,代表查询后,显示的值,如果不加,默认显示枚举值,也就是value对应的YES或者NO,加载哪个字段,就显示对应的字段的值,加入加在上面的value字段,就显示对应的0或者1,
private final String desc;
DeleteEnums(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
JSON处理器
数据库
实体类
@Data
@TableName(autoResultMap = true) //自动的结果集映射
public class User implements Serializable {
@TableField(value = "info",typeHandler = JacksonTypeHandler.class) //字段上面指定映射的类型为JSON
private UserInfo userInfo;
映射json的类型
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of") //静态方法,名为of
public class UserInfo {
private String name;
private Integer age;
}
分页
分页插件
配置类
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//初始化拦截器,有很多拦截器,这里只添加一个分页的插件拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件,指定数据库
PaginationInnerInterceptor paginationInnerInter=new PaginationInnerInterceptor(DbType.MYSQL);
//设置分页上限
paginationInnerInter.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInnerInter);
return interceptor;
}
}
//配置分页的一些属性,第几页,每页几条
Page<User> page = Page.of(1, 10);
//按照age大小排序
page.addOrder(new OrderItem("age",true));
//执行分页查询,没有写条件就是查所有,可以在.page之前写条件
Page<User> userPage = Db.lambdaQuery(User.class).page(page);
//分页后的数据
List<User> records = userPage.getRecords();
封装成想要的格式返回给前端
返回给前端的分页数据
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<T> rows;
/**
* 消息状态码
*/
private int code;
/**
* 消息内容
*/
private String msg;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<T> list, long total) {
this.rows = list;
this.total = total;
}
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
return rspData;
}
}
调用
public TableDataInfo<SysUser> selectPageUserList(SysUser user, PageQuery pageQuery) {
//pageQuery.build()就是构造一个分页对象,也就是page对象,包含第几页,每页几条,这种。this.buildQueryWrapper(user)就是构造一个查询条件,也就是wrapper
Page<SysUser> page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user));
//拿到分页数据以后,传到需要返回给前端的格式的实体类中处理一下。弄成想要的格式
return TableDataInfo.build(page);
}
自定义sql分页
要有两条sql,区别在于,where条件要一样,但是一个有分页参数,一个没有分页参数没有分页参数的用来统计总条数有分页参数的,按照分页条件获取想要的数据
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("<script>" +
"select count(*) from user " +
"<where> " +
"<if test='name != null and name != \"\"'>" +
" name like concat(\"%\",#{name},\"%\")" +
"</if>" +
"</where>" +
"</script>")
Integer selectCount(String name);
@Select("<script>" +
"select count(*) from user " +
"<where> " +
"<if test='name != null and name != \"\"'>" +
" name like concat(\"%\",#{name},\"%\")" +
"</if>" +
"</where>" +
" limit #{page}, #{num}" +
"</script>")
List<User> selectPage(String name, @Param("page") int page, @Param("num") int num);
}
然后调用方法
@Resource
UserMapper userMapper;
public void selectData(String name,int page,int num){
//查询第1页,每页十条数据,需要(page-1)*num计算
/*
* 第一页,那就是0-10的数据,(1-1)*10,10,结果limit 0,10,从0开始取10条数据
* 第二页,那就是11-20的数据,(2-1)*10,10,结果limit 10,10,从10开始取10条数据
* ......
*/
List<User> users=userMapper.selectPage(name,(page-1)*num,num);
}
还存在一个问题,如果是查询列表页面,如果你先跳转到第2页,然后再查询,那这个page默认就是2了,取不到值了,讲道理页码应该是从1开始才对,所以得加条件,前端直接传页码值为1进来,要不然就是后端处理,设置页码为第一页
@Resource
UserMapper userMapper;
public void selectData(String name,int page,int num){
if(StringUtils.isNotBlank(name)){ //如果是查询,条件肯定不是空的,所以设置页码为第一页,从头开始查
page=1;
}
List<User> users=userMapper.selectPage(name,(page-1)*num,num);
}