官网文档 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的数据

Mybatis Plus的使用_mysql

修改taskname为zhangsan的数据,改为lisi

Mybatis Plus的使用_mybatis plus_02

自定义sql

条件使用wrapper构建,前面的语句,写在sql中

  1. 基于wrapper构造where条件
LambdaQueryWrapper<User> wrapper=new LambdaQueryWrapper<>().in("id",list);
String dept="a123";
//mapper接口中自定义的方法
userMapper.updateDeptById(dept,wrapper);
  1. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ewmapper中接口方法:
void updateDeptById(@Param("dept")String dept,@Param("ew")LambdaQueryWrapper<User> wrapper)
  1. 自定义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 {

}

Mybatis Plus的使用_mybatis plus_03

用的时候,就可以通过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;
    }
}

Mybatis Plus的使用_mybatis plus_04

JSON处理器

数据库

Mybatis Plus的使用_mybatis plus_05

实体类

@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;
}

Mybatis Plus的使用_springboot_06

分页

分页插件

配置类

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);
}