此篇博客主要针对于有开发基础的朋友学习~
首先提几个问题:
1、什么是Mybatis?
2、什么是MybatisPlus?
3、Mybatis和MybatisPlus又有什么区别呢?
问题1:Mybatis是一个持久层的框架,我们通过配置mapper.xml的方式来自定义sql,在xml中,mybatis给我们提供了很多标签,比如:
<resultMap>
用来映射数据库字段和实体类属性之间的对应关系
<select>
标签用来定义一个select查询方法
<where>
标签用来构建where条件
<if>
标签用来定义一个条件判断等等。
Mybaits的优点也很明显:
1、我们可以自定义任何sql语句来实现我们的业务,也就是说sql我们可以想怎么写怎么写,想怎么查怎么查。
2、实现了sql语句和java代码解耦。
问题2:MybatisPlus属于Mybatis的一个增强版本,对于Mybatis来说我们要想简单实现一个增删改查,还是要在mapper.xml中编写sql来实现。但是对于MybatisPlus来说它已经为我们封装好了很多crud的接口供我们调用,如果只是简单地增删改查我们完全不用再在mapper.xml中编写sql了,一行代码就搞定。
问题3:区别的话问题2中已经说了一部分,对于Mybatis来说我们还是要基于编写sql来实现业务,这也就意味着如果项目中要从mysql数据库换成oralce数据库会是一件很麻烦的事情,很多sql语句都要修改。但是对于MybatisPlus来说很多业务我们不需要写sql而是调一个接口就能搞定,这里就方便了很多。
下面我们就来学习一下MybatisPlus的相关知识:
一、引入依赖
首先我们先构建一个简单的springboot项目,并在pom文件中加入MybatisPlus的依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
二、建表
我们在数据库中建一张表:
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`student_name` varchar(30) DEFAULT '' COMMENT '学生姓名',
`student_num` varchar(30) DEFAULT '' COMMENT '学号',
`address` varchar(500) DEFAULT '' COMMENT '家庭住址',
`student_age` int(3) DEFAULT NULL COMMENT '年龄',
`student_sex` char(1) DEFAULT '0' COMMENT '性别(0男 1女 2未知)',
`student_birthday` datetime DEFAULT NULL COMMENT '生日',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='学生表';
三、创建实体类
建完之后我们就在项目中建我们的Student实体类:
package com.citc.develop.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
/**
* 学生对象 student
*
* @author szq
* @date 2021-02-22
*/
@Data
public class Student
{
private static final long serialVersionUID = 1L;
/** 主键id */
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 学生名称 */
private String studentName;
/** 学号 */
private String studentNum;
/** 家庭住址 */
private String address;
/** 年龄 */
private Integer studentAge;
/** 性别(0男 1女 2未知) */
private String studentSex;
/** 生日 */
private Date studentBirthday;
}
这里有一个细节分享一下,id字段有一个@TableId
注解,这个注解标明了这个字段是id主键并指定了id生成策略为数据库id自增,也就是说我们在插入的时候不需要给id属性set值。
我们点进去这个AUTO可以发现mybatisplus现支持五种id生成策略(3.3.0版本之后):
四、创建Mapper
package com.citc.develop.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.citc.develop.entity.Student;
import org.apache.ibatis.annotations.Mapper;
/**
* @author szq
* @since 2021-12-14
*/
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}
五、创建Service接口
package com.citc.develop.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.citc.develop.entity.Student;
/**
*
* @author szq
* @since 2021-12-14
*/
public interface StudentService extends IService<Student> {
}
六、创建相应的Service实现
package com.citc.develop.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.citc.develop.dao.StudentMapper;
import com.citc.develop.entity.Student;
import com.citc.develop.service.StudentService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
/**
*
* @author szq
* @since 2021-12-14
*/
@Service
@AllArgsConstructor
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
}
其实写到这里最基础的增删改查已经实现了,只要我们的mapper接口继承了BaseMapper就已经能进行crud了,并且这个接口中封装了很多常用的查询方法。
下面我们写一个简单的接口来试验一下:
首先我先配置一下application.yml打印一下sql日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
然后我们编写StudentApi
package com.citc.develop.api;
import com.citc.develop.dto.StudentDto;
import com.citc.develop.service.StudentService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* @author szq
* @Description
* @date Created in 2022/3/1
*/
@RestController
@RequestMapping("/api/v1/student")
@AllArgsConstructor
public class StudentApi {
private final StudentService studentService;
@PostMapping("/add")
public Long add(StudentDto studentDto) {
return studentService.save(studentDto);
}
}
然后在service实现类里,我简单的做了一个对象的copy,并返回了记录的id:
package com.citc.develop.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.citc.develop.dao.StudentMapper;
import com.citc.develop.dto.StudentDto;
import com.citc.develop.entity.Student;
import com.citc.develop.service.StudentService;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
*
* @author szq
* @since 2021-12-14
*/
@Service
@AllArgsConstructor
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
@Override
public Long save(StudentDto studentDto) {
Student student = new Student();
BeanUtils.copyProperties(studentDto,student);
this.save(student);
return student.getId();
}
}
下面我们启动一下项目,打开postman传入参数点击send:
看返回结果已经成功了,我们来看一下控制台打印的sql:
然后看一下表中的数据:
记录已经成功插入,至此我们已经实现了“增”的功能。
下面我们来看一下删除功能:
删除功能更简单,前端只需要传递一个记录id,然后在调用一下service的remove接口即可,下面我把控制器层的代码贴出来:
@GetMapping("/delete")
public boolean delete(Long id) {
return studentService.removeById(id);
}
用postman调用一下:
好了,删除功能已经实现了,但是在我们实际业务中,很多场景都是需要逻辑删除的,这个mybatis-plus也已经有相关的注解实现下面我们来看一下:
@TableLogic
就是这个注解,我们只需要在表里新加一个删除标记的字段,例如我在student表里加一个delete_flag字段,0表示没有删除,1表示已经删除,
然后在我们实体类中也加上这个字段:
这样就已经实现逻辑删除了,下面我们再调用一下刚才的删除接口会发现,日志里已经不走delete语句而是update语句了:
0表示没有删除,1表示已经删除,这是mybatisplus默认的映射关系,如果你想用0表示已经删除,1标识没有删除,那么我们注解可以这么写:
那么在实际项目中,我们又有很多张表,每张表可能都有delete_flag字段,那么在每个实体类中都加这一行代码多少有点冗余,有没有更简单的配置方式?
答案当然是有的,我们可以通过yml配置的方式来实现逻辑删除:
这样的实现方式是和上面一样的,我们就不需要把每个表对应的实体类都通过加注解来实现逻辑删除了,一个配置就能搞定所有表的逻辑删除,但是前提条件是这些表的逻辑删除字段都得是一样的,如果说有的表不一样,那我们就还是通过注解方式来实现,注解的实现方式是比配置文件的的优先级高,在两种都配置的情况下,mybatisplus会优先执行注解的配置。
好了,下面我们来看mybatisplus的更新:
mybatisplus的更新和查询都是通过条件构造器来实现的,这个条件构造器也很好理解,就是sql语句中的where后的语句,下面我们来看一下:
这里先插入几条数据
INSERT INTO `student` VALUES (2, '张三', '1231231', '河南省郑州市', 20, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (3, '李四', '22222', '山东省青岛市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (4, '张无忌', '444122', '河南省信阳市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (5, '大朗', '53431', '山东省烟台市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (6, '嘿嘿嘿', '855654', '北京市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (7, '里无数', '7544556', '四川省成都市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
INSERT INTO `student` VALUES (8, '李武', '9984554', '河南省驻马店市', 22, '0', '1995-09-08 09:27:18', NULL, '0');
通常在项目中我们更新的接口前端会传来一个带有id字段的dto,我们直接根据id更新记录就好了:
但是这里有一个问题需要注意一下,mybatisplus只会更新改变了的字段,比如说前端传的dto中只有address这一个字段的值,那么mybatisplus就只会更新这一个字段,其他字段的值不变,如果我们想把某一条记录中的一两个字段从非null更新成null,那我们需要手动将实体类中对应的属性set成null。
上面我们说的是根据id做整体更新,那么如果我们想自定义条件来更新呢?
mybatisplus给我们提供了UpdateWrapper
条件构造器,我们来看一下如何使用:
这是现在表里的数据:
比如说我现在想更新所有年龄是22岁的学生的性别,从0更新成1,那么我们可以这么来写条件构造器:
我们来看一下打印出来的sql:
下面是条件构造器的常用方法:
eq(R column, Object val); // 等价于 =,例: eq("name", "老王") ---> name = '老王'
ne(R column, Object val); // 等价于 <>,例: ne("name", "老王") ---> name <> '老王'
gt(R column, Object val); // 等价于 >,例: gt("age", 18) ---> age > 18
ge(R column, Object val); // 等价于 >=,例: ge("age", 18) ---> age >= 18
lt(R column, Object val); // 等价于 <,例: lt("age", 18) ---> age < 18
le(R column, Object val); // 等价于 <=,例: le("age", 18) ---> age <= 18
between(R column, Object val1, Object val2); // 等价于 between a and b, 例: between("age", 18, 30) ---> age between 18 and 30
notBetween(R column, Object val1, Object val2); // 等价于 not between a and b, 例: notBetween("age", 18, 30) ---> age not between 18 and 30
in(R column, Object... values); // 等价于 字段 IN (v0, v1, ...),例: in("age",1,2,3) ---> age in (1,2,3)
notIn(R column, Object... values); // 等价于 字段 NOT IN (v0, v1, ...), 例: notIn("age",1,2,3) ---> age not in (1,2,3)
inSql(R column, Object... values); // 等价于 字段 IN (sql 语句), 例: inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
notInSql(R column, Object... values); // 等价于 字段 NOT IN (sql 语句), 例: notInSql("id", "select id from table where id < 3") ---> id not in (select id from table where id < 3)
like(R column, Object val); // 等价于 LIKE '%值%',例: like("name", "王") ---> name like '%王%'
notLike(R column, Object val); // 等价于 NOT LIKE '%值%',例: notLike("name", "王") ---> name not like '%王%'
likeLeft(R column, Object val); // 等价于 LIKE '%值',例: likeLeft("name", "王") ---> name like '%王'
likeRight(R column, Object val); // 等价于 LIKE '值%',例: likeRight("name", "王") ---> name like '王%'
isNull(R column); // 等价于 IS NULL,例: isNull("name") ---> name is null
isNotNull(R column); // 等价于 IS NOT NULL,例: isNotNull("name") ---> name is not null
groupBy(R... columns); // 等价于 GROUP BY 字段, ..., 例: groupBy("id", "name") ---> group by id,name
orderByAsc(R... columns); // 等价于 ORDER BY 字段, ... ASC, 例: orderByAsc("id", "name") ---> order by id ASC,name ASC
orderByDesc(R... columns); // 等价于 ORDER BY 字段, ... DESC, 例: orderByDesc("id", "name") ---> order by id DESC,name DESC
having(String sqlHaving, Object... params); // 等价于 HAVING ( sql语句 ), 例: having("sum(age) > {0}", 11) ---> having sum(age) > 11
or(); // 等价于 a or b, 例:eq("id",1).or().eq("name","老王") ---> id = 1 or name = '老王'
or(Consumer<Param> consumer); // 等价于 or(a or/and b),or 嵌套。例: or(i -> i.eq("name", "李白").ne("status", "活着")) ---> or (name = '李白' and status <> '活着')
and(Consumer<Param> consumer); // 等价于 and(a or/and b),and 嵌套。例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
nested(Consumer<Param> consumer); // 等价于 (a or/and b),普通嵌套。例: nested(i -> i.eq("name", "李白").ne("status", "活着")) ---> (name = '李白' and status <> '活着')
apply(String applySql, Object... params); // 拼接sql(若不使用 params 参数,可能存在 sql 注入),例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") ---> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last(String lastSql); // 无视优化规则直接拼接到 sql 的最后,可能存若在 sql 注入。
exists(String existsSql); // 拼接 exists 语句。例: exists("select id from table where age = 1") ---> exists (select id from table where age = 1)
这些条件构造同样的适用于在构造查询条件中,更新时我们获取的是UpdateWrapper
而在查询时我们获取的是QueryWrapper
,下面是表中现有的数据:
最简单的我们想根据主键id查询一条记录我们可以这么写:
Student student = this.getBaseMapper().selectById(2);
比如说现在想要查询年龄大于20岁的学生记录,那么我们可以这么写:
private void customCondition(){
// 通过Wrappers获取一个更新的条件构造器
QueryWrapper<Student> queryWrapper = Wrappers.query();
queryWrapper.gt("student_age",20);
// 符合条件的学生记录
List<Student> studentList = this.list(queryWrapper);
}
这样记录就被我们查询出来了:
剩下的方法大家可以自己试一试。
希望我的文章能够帮助到大家,有问题请指出谢谢您。