MybatisPlus

1 引入MP依赖代替Mybatis依赖

如果你的问题本文解决不了,找MyBatis-Plus官网

<!--MybatisPlus(包含了mybatis的starter)-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.4</version>
</dependency>

2 配置yaml

        (见之前文章SpringBoot (2) yaml,整合项目)

3 继承BaseMapper

        BaseMapper泛型为所要操作的pojo类,MP依据此泛型通过反射得到类的字节码文件,然后通过字节码信息反推出数据库中的表信息,反推要遵循约定

public interface TeacherDao extends BaseMapper<Teacher> {
}

3.1 默认约定

        (1) 类名的驼峰转下划线作为表名: 类名HighSchool反推出的表名就是high_school

        (2) 名为id的成员变量作为主键字段

        (3) 类成员变量的驼峰转下划线作为字段名

3.2 自定义约定

        @TableName用在pojo类上,指定表名

        @TableId(value="id", type=IdType.AUTO)用在pojo类成员变量上,指定表主键字段

                IdType.AUTO表示自增Id(调用insert方法后,自动给参数的主键成员变量赋值)

                IdType.INPUT表示set方法设置,用户自己填写

                IdType.ASSING_ID(默认)表示MP会自动利用雪花算法生成long类型的整数,长度20位

        @TableField(value="is_married",insertStrategy=FieldStrategy.DEFAULT,updateStrategy=FieldStrategy.DEFAULT,whereStrategy=FieldStrategy.DEFAULT)用在pojo类成员变量上,指定普通字段

                FieldStrategy.DEFAULT按照yaml配置文件

                FieldStrategy.NOT_NULL非NULL才加入SQL

                FieldStrategy.ALWAYS总是加入SQL(无论字段值是否为NULL)

                FieldStrategy.NEVER总不加入SQL

                (1) 对于is开头并且是boolean类型的pojo成员变量,在通过反射机制会将is去掉,那么就起不到驼峰转下划线的效果,因此is开头的boolean类型的成员变量必须用@TableField注解

                (2) 对于关键字的pojo成员变量,必须用@TableField注解,并且要将关键字写在反引号``

                         @TableField("`order`")

                (3) pojo成员变量不是数据库字段,必须用@TableField(exist=false)注解

@TableName("aboluo_person")
public class Person {
    @TableId
    private Long id;
    @TableField("user_name")
    private String name;
    @TableField("is_man")
    private Boolean isMan;
    @TableField("`select`")
    private Integer select;
    @TableField(exist = false)
    private String other;
}

3.3 继承BaseMapper的常用方法

3.3.1 增

teacherDao.insert(T);  //这里的"泛型类"就是BaseMapper的泛型

3.3.2 删

teacherDao.deleteById(5)

teacherDao.delete(Wrapper)

teacherDao.deleteBatchIds(Collection<?>)

3.3.3 改

teacherDao.updateById(T)  //非空字段才更新(可以在yaml中设置)

teacherDao.update(Wrapper<T>)

teacherDao.update(T,Wrapper<T>)

3.3.4 查

单查询:

        teacherDao.selectOne(Wrapper<T>);

        teacherDao.selectById(6);

多查询:

        teacherDao.selectList(Wrapper<T>);  //参数为null表示(无条件)查询所有

        teacherDao.selectBatchIds(List.of(1,3,9));  //根据id的List集合查询

4 条件构造器

        条件构造器Wrapper支持各种复杂的where条件

4.1 QueryWrapper

        下面的关于WHERE的筛选都写在括号中

        有些函数可以使用condition参数判断是否执行

                例如: .like(condition,column,val)   //如果condition是false代表不执行

函数名说明示例
.eq等于=.eq("name","张三")
.ne不等于<>
.gt大于>.gt("age",18)
.ge大于等于>=
.lt小于<
.le小于等于<=
.betweenBETWEEN值1 AND 值2  #包含值1和值2.between("age",18,30)
.notBetweenNOT BETWEEN值1 AND 值2 
.likeLIKE'%值%'.like("name","陈")      //如果数据为null,则;也会参与比较,比较值直接转换为字符串"null",因此可以采用3个参数的like方法,第一个参数加boolean判断(name!=null,"name","陈")
.likeLeftLIKE'%值'
.likeRightLIKE'值%'
.notLikeNOT LIKE'%值%'
.notLikeLeftNOT LIKE'%值'
.notLikeRightNOT LIKE'值%'
.isNull字段IS NULL.isNull("name")
.isNotNull字段IS NOT NULL

.in

字段IN(v1,v2...).in("age",{1,2,3})
.notIn字段NOT IN(v1,v2...).notIn("age",1,2,3)
.inSql字段IN(sql语句).inSql("id","select id from table where id<3")==>id IN (select id from table where id<3)
.notInSql字段NOT IN(sql语句)
.groupBy分组GROUP BY 字段1,字段2....groupBy("id","name")
.orderByAsc排序ORDER BY 字段1,字段2...ASC.orderByAsc("id","name")
.orderByDesc排序ORDER BY 字段1,字段2...DESC
.having分组后筛选.having("CHAR_LENGTH(name)>{0} and age>{1}", 2, 18)

.or

OR

不调用or,则默认使用and

eq("naem","张三").or().eq("naem","李四")

注意1:如果.or()前面或后面的sql没有执行时,则在最终的sql中只会出现执行的sql且没有or,避免了语法错误

.apply拼接sql

sql在WHERE括号中进行拼接,拼接的语句之前默认添加AND

方式一:(有sql注入问题)

        .apply("sql1"+变量1+"sql2"+常量+"sql3")

方式二:(不会有sql注入问题)

        .apply("sql1+{0}+sql2+{1}",变量1,变量2)

.last最后面拼接sqlsql在WHERE括号外进行拼接
.existsEXISTS(子查询)作用相同.exists("拼接sql")
.notExistNOT EXISTS(子查询)作用相同
.setEntity(T)不用再一个个设置条件,直接用pojo类作为条件
.and.and方法中的sql放在一个括号

.and(i->i.like(条件1).or.like(条件2))

注意1:尽量让.and()括号中的sql至少执行一条(即不能为空),否则最后的sql语句中and后面接的是空的,会导致语法错误

        示例1:查询position中带"语文",年龄age大于18的"position,name,age"字段

/*SELECT position, name, age 
FROM teacher 
WHERE
	position LIKE '%语文%' 
	AND age > 18;*/
QueryWrapper<Teacher> wrapper = new QueryWrapper<Teacher>()
		.select("position", "name", "age")
		.like("position", "语文")
		.gt("age", 18);
List<Teacher> teachers = teacherDao.selectList(wrapper);

        示例2:把name为"张三"的Teacher的age修改为33

/*UPDATE teacher 
SET age = 45 
WHERE
	NAME = '张三';*/
Teacher teacher = new Teacher();
teacher.setAge(33);
QueryWrapper<Teacher> wrapper = new QueryWrapper<Teacher>()
		.eq("name", "张三");
teacherDao.update(teacher, wrapper);
return Result.success();

4.2 UpdateWrapper

        UpdateWrapper提供了3种修改记录的方式:

                (1) 将修改内容放在pojo类中,UpdateWrapper只做where筛选(用法同QueryWrapper)

                (2) UpdateWrapper对象.set(类::getXxx,值),直接用""替换对应字段的数据

                (3) UpdateWrapper对象.setSql(自定义sql),替代sql中的set

        示例3:将所有语文老师的年龄增加1岁

/*UPDATE teacher 
SET age = age + 1 
WHERE
	position = '语文老师';*/
UpdateWrapper<Teacher> wrapper = new UpdateWrapper<Teacher>()
		.setSql("age=age+1")
		.eq("position", "语文老师");
teacherDao.update(wrapper);

4.3 AbstractLambdaWrapper(推荐)

        QueryWrapper和UpdateWrapper在写字段时存在硬编码问题,而LambdaQueryWrapperLambdaUpdateWrapper就可以解决这个问题

        LambdaQueryWrapper替换QueryWrapper:查询position中带"语文",年龄age大于18的"position,name,age"字段

/*SELECT position, name, age 
FROM teacher 
WHERE
	position LIKE '%语文%' 
	AND age > 18;*/
LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
		.select(Teacher::getPosition, Teacher::getName, Teacher::getAge)
		.like(Teacher::getPosition, "语文")
		.gt(Teacher::getAge, 18);
List<Teacher> teachers = teacherDao.selectList(wrapper);

        LambdaUpdateWrapper替换UpdateWrapper:将所有语文老师的年龄增加1岁

/*UPDATE teacher 
SET age = age + 1 
WHERE
	position = '语文老师';*/
LambdaUpdateWrapper<Teacher> wrapper = new LambdaUpdateWrapper<Teacher>()
		.setSql("age=age+1")
		.eq(Teacher::getPosition, "语文老师");
teacherDao.update(wrapper);

4.4 自定义sql方法

        问题1:Mybatis只有Dao接口,通过xml映射在SpringIOC中生成Dao实例,因此Wrapper只能写在Service层,导致sql语句无法被其它Service复用

        问题2:条件构造器善于处理where后面的sql,但不善于处理where前的sql(例如:聚合查询,查询字段起别名...),所以只能将sql写在业务层中(如"示例3"),但实际开发中并不建议在业务层写sql

        解决:"where前交给xml文件处理,where后交给MP条件构造器处理"方式解决

4.4.1 dao接口+xml

        同mybatis一样,在dao接口中自定义sql方法(此方法与xml文件能映射),方法参数是@Param("ew")Wrapper@Param("自定义")自定义参数

                //Wrapper对应的注解一定是@Param("ew"),固定写法

        xml文件中用${ew.customerSqlSegment},#{自定义参数}引用

                //${ew.customerSqlSegment},固定写法

4.4.2 用法示例

        示例1:查询position中带"语文",年龄age大于18的"position,name,age"字段

//dao接口
public interface TeacherDao extends BaseMapper<Teacher> {
    List<Teacher> customSQL1(@Param("ew") LambdaQueryWrapper wrapper);
}

//xml文件
<mapper namespace="xyz.aboluo.mybatisPlus.dao.TeacherDao">
    <resultMap id="teacherMap" type="teacher">
        <result column="position" property="position"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
    </resultMap>

    <select id="customSQL1" resultMap="teacherMap">
        select * from teacher ${ew.customSqlSegment}
    </select>
</mapper>

//使用"自定义sql"
@RequestMapping("/customSQL1.do")
public Result customSQL1() {
	LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
			.like(Teacher::getPosition, "英语")
			.gt(Teacher::getAge, 18);
	List<Teacher> teachers = teacherDao.customSQL1(wrapper);
	return Result.success(teachers);
}

        示例2:把name为"张三"的Teacher的age修改为33

//dao接口
public interface TeacherDao extends BaseMapper<Teacher> {
    Integer customSQL2(@Param("ew") LambdaQueryWrapper wrapper,  @Param("teacher") Teacher teacher);
}

//xml文件
<mapper namespace="xyz.aboluo.mybatisPlus.dao.TeacherDao">
    <resultMap id="teacherMap" type="teacher">
        <result column="position" property="position"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
    </resultMap>

    <update id="customSQL2" parameterType="teacher">
        update teacher set age=#{teacher.age} ${ew.customSqlSegment}
    </update>
</mapper>

//使用"自定义sql"
@RequestMapping("/customSQL2.do")
public Result customSQL2() {
	Teacher teacher = new Teacher();
	teacher.setAge(33);
	LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
			.eq(Teacher::getName, "张三");
	teacherDao.customSQL2(wrapper, teacher);
	return Result.success();
}

//使用BaseMapper方法
@RequestMapping("/customSQL3.do")
public Result customSQL3() {
	LambdaUpdateWrapper<Teacher> wrapper = new LambdaUpdateWrapper<Teacher>()
			.set(Teacher::getAge,33)
			.eq(Teacher::getName, "张三");
	teacherDao.update(wrapper);
	return Result.success();
}

5 IService接口

        BaseMapper接口是增强Dao接口的,dao接口直接继承BaseMap"增删改查"等方法

                例如:在AServiceImpl类中,只能使用A对应的IService方法,如果想用B对应的Iservice则无法实现,此时就需要引入BServiceImpl或BDao(但如果AServiceImpl中引入了BServiceImpl,而BServiceImpl中如果也引入了AServiceImpl,就会导致"循环依赖",因此在AServiceImpl中引入BDao比较好),而BDao继承了BaseMap,因此在AServiceImpl中可以用B对应的BaseMap中的方法

        IService接口是增强Service接口的,Service接口直接继承IService"增删改查"等方法

5.1 Iservice常用方法

类型方法说明
save(T)
saveBatch(Collection<T>)
saveOrUpdate(T)
saveOrUpdate(T,Wrapper<T>)
saveOrUpdateBatch(Collection<T>)
remove(Wrapper<T>)
removeById(T)
removeBatchByIds(Collection<?>)
update(Wrapper<T>)
update(T,Wrapper<T>)
updateById(T)
updateBatchById(Collectiion<T>)
getById(Serializable)    //参数是泛型T,并且T继承Serializable,并且用@TableId标记出那个字段是ID查询一个用get开头
getOne(Wrapper<T>)
listByIds(Collection<? extends Serializable>)查询多个用list开头
list(Wrapper<T>)参数null,表示查所有
lambdaQuery(T)参数null,表示查所有
lambdaUpdate()
其它count(Wrapper<T>)统计(参数null,表示统计所有)
page(E)分页查询
page(E,Wrapper<T>)

5.2 使用流程

        因为dao接口是通过xml文件在SpringIOC中反射生成实现类,dao接口直接继承BaseMapper就可以用了,因此不需要写实现类

        继承Iservice接口的xxxService接口有xxxServiceImpl实现类,并且实现类需要重写xxxService接口方法和Iservice接口方法,但在实际开发中我们只想重写自定义的xxxService接口,不想重写Iservice接口方法,xxxServiceImpl可以继承MP提供的ServiceImpl方法

5.3 用法示例

        (1) xxxService继承IService接口要写pojo泛型

        (2) xxxServiceImpl继承ServiceImpl要写dao泛型pojo泛型

//xxxService
public interface TeacherService extends IService<Teacher> {
}

//xxxServiceImpl
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherDao, Teacher> implements TeacherService {
}

//查询position中带"语文",年龄大于18的"position,name,age"字段
@RequestMapping("/service3.do")
public Result service3() {
    LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
            .select(Teacher::getPosition, Teacher::getName, Teacher::getAge)
            .like(Teacher::getPosition, "语文")
            .gt(Teacher::getAge, 18);
    return Result.success(teacherService.list(wrapper));
}

//把name为"张三"的Teacher的age修改为33(方式一)
@RequestMapping("/service4.do")
public Result service4() {
    Teacher teacher = new Teacher();
    teacher.setAge(33);
    LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
            .eq(Teacher::getName, "张三");
    teacherService.update(teacher, wrapper);
    return Result.success();
}

//把name为"张三"的Teacher的age修改为33(方式二)
@RequestMapping("/service5.do")
public Result service5() {
    LambdaUpdateWrapper<Teacher> wrapper = new LambdaUpdateWrapper<Teacher>()
            .set(Teacher::getAge, 33)
            .eq(Teacher::getName, "张三");
    teacherService.update(wrapper);
    return Result.success();
}

5.4 lambdaQuery()方法、lambdaUpdate()方法

        相当于将IService和AbstractLambdaWrapper"2个步骤合并为1个步骤"

                lambdaQuery().one表示查一条记录.list查多条记录.page分页查询.count统计.exists判断是否存在

                lambdaUpdate().set表示设置字段值.update(T)表示执行lambdaUpdate更新  //更新哪些字段可以以.set方法规定的字段为准,也可以以T中的非空成员变量为准

        lambdaQuery()示例:根据"position模糊"查询,"name精确"查询,"大于age"查询,并且position,name,age3个字段允许为null,当为null时对此字段不做筛选

//xxxService
public interface TeacherService extends IService<Teacher> {
    Result lambdaQuery1(Teacher teacher);
}

//xxxServiceImpl
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherDao, Teacher> implements TeacherService {
    @Override
    public Result lambdaQuery1(Teacher teacher) {
        List<Teacher> teachers = lambdaQuery()
                //第一个参数condition表示判断,如果为true则执行这一步筛选
                .like(teacher.getPosition() != null, Teacher::getPosition, teacher.getPosition())
                .eq(teacher.getName() != null, Teacher::getName, teacher.getName())
                .gt(teacher.getAge() != null, Teacher::getAge, teacher.getAge())
                .list();
        return Result.success(teachers);
    }
}

//使用
@RequestMapping("/lambdaQuery1.do")
public Result lambdaQuery1(@RequestBody Teacher teacher) {
    return Result.success(teacherService.lambdaQuery1(teacher));
}

        lambdaUpdate()示例:将name为"张三"的用户年龄-200岁,如果年龄不足则操作失败

//xxxDao
public interface TeacherService extends IService<Teacher> {
    Result lambdaQuery2();
}

//xxxDaoImpl
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherDao, Teacher> implements TeacherService {
    @Override
    public Result lambdaQuery2() {
        //去数据库查询name为"张三"的Teacher
        LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
                .eq(Teacher::getName, "张三");
        Teacher teacher = getOne(wrapper);
        Integer restAge = teacher.getAge() - 200;
        //修改余额
        lambdaUpdate()
                .set(restAge >= 0, Teacher::getAge, restAge)
                .set(restAge < 0, Teacher::getAge, teacher.getAge())//总有一条生效,防止都不生效导致sql错误
                .eq(Teacher::getName, "张三")
                .update();
        return Result.success();
    }
}

//使用
@RequestMapping("/lambdaQuery2.do")
public Result lambdaQuery2() {
    teacherService.lambdaQuery2();
    return Result.success();
}

5.5 批量操作

5.5.1 批量操作对比

        (1) 使用for循环逐条将sql交给mysql处理(性能极低,每执行一条sql,后台就与mysql通讯一次)

        (2) MP基于预编译批量处理(例如xxxDao.saveBatch(list)),将多条sql统一交给(也不能一次给太多,对内存压力大)mysql处理(性能一般,虽然将多条sql统一交给mysql,但mysql依旧是一条条执行)

        (3) 将多条sql写作一条sql    例如:多条INSERT INTO table(xx,xx,xx) VALUE (yy,yy,yy) ==> 一条INSERT INTO table(xx,xx,xx) VALUES (yy,yy,yy),(yy,yy,yy)

                方式一: 使用手写拼接(不推荐)

                方式二: 配置mysql的jdbc驱动(开启rewriteBatchedStatements=true),开启mysql将多条sql整合成一条sql执行     //此步骤是对mysql的改变,本质上与MP无关

5.5.2 代码示例

        例1:新增10000条数据,使用"逐条新增"的方式     //耗时28817ms

@RequestMapping("/batch1.do")
public void batch1() {
    //向数据库逐条插入10000条数据
    for (int i = 0; i < 10000; i++) {
        Teacher teacher = createTeacher(i);
        teacherService.save(teacher);
    }
}

public Teacher createTeacher(Integer positionNo) {
    Teacher teacher = new Teacher();
    teacher.setPosition("" + positionNo);
    return teacher;
}

        例2:新增10000条数据,使用"预编译"的方式    //耗时3020ms

@RequestMapping("/batch2.do")
public void batch2() {
    //每次向数据库插入1000条(一次插入过多对内存要求压力大),插入10次
    for (int i = 1; i <= 10000; i++) {
        teachers.add(createTeacher(i));
        if (i % 1000 == 0) {
            teacherService.saveBatch(teachers);
            teachers.clear();
        }
    }
}

public Teacher createTeacher(Integer positionNo) {
    Teacher teacher = new Teacher();
    teacher.setPosition("" + positionNo);
    return teacher;
}

        例:新增10000条数据,使用"预编译"+"sql整合"的方式    //耗时1284ms

                代码还是沿用例2的代码,但此时MP会将分散的1000条sql组装成1条sql

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sunner?rewriteBatchedStatements=true

         "sql整合"后的代码如下图:

6 多表查询

        (使用.xml文件)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值