写在前面:本文并不是演示mybatisplus的使用,而是记录一下mybatisplus中使用的一些细节,如果想要详细了解mybatisplus,官网是个不错的选择。这里是官网
1、MybatisPlus快速搭建
在搭建过程中,希望体会以下的几点细节知识。
1.1、引入mybatis-plus相关mavne依赖
这里引入的是mybatis-plus在springboot中的场景启动器。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
- 项目的全部依赖:
<!-- mybatis-plus-->
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 德鲁伊druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- lombok,后面会讲,帮我们生成set、get等-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--springboot的测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.2、数据库
CREATE TABLE `tbl_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` varchar(50) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 插入几条数据
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','tom@atguigu.com',1,22);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','jerry@atguigu.com',0,25);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','black@atguigu.com',1,30);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','white@atguigu.com',0,35);
- 创表完成
1.3、搭建项目结构
1.3.1、select语句让我明白
1.3.1、知识点一
这里直接给出了完整的结构。
- 实体类对应代码
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
EmployeeMapper
里面继承了MybatiPlus
提供的BaseMapper
接口,里面提供了增删改操作。
/**
* @author liushoushou
* @create 2021-10-19-22:15
*/
public interface EmployeeMapper extends BaseMapper<Employee> {
}
== 注意 1、== BaseMapper里面一定填写实体类对象。原因是mybatisplus生成sql语句的方式是:根据BaseMapper泛型所填写的实体类作为数据库表去查找的。
- 验证注意1:
- 将EmployeeMapper改为如下,也即去掉了泛型:
/**
* @author liushoushou
* @create 2021-10-19-22:15
*/
public interface EmployeeMapper extends BaseMapper {
}
我们以单元测试方法进行测试。
- 在
TestMpApplication
中添加测试方法:
@SpringBootTest
public class TestMpApplication {
// 注入Mapper
@Autowired
private EmployeeMapper employeeMapper;
@Test
public void testSelect(){
Employee employee = employeeMapper.selectById(1);
System.out.println(employee);
}
}
- 执行
testSelect()
方法,出现如下异常:说明必须在BaseMapper<T>中加上实体类的名字
- 将BaseMapper再添加上泛型<Employee>
- 再次运行上述的
testSelect()
方法,出现如下异常:实体类名是Employee
,它在查找数据库时是employee
,而我的表名是tbl_employee
,自然会报找不到表了。
将数据库表名改为:Employee
- 再次执行上面的
testSelect方法
- 查询到了数据,并且查询语句的表名是
employee
,数据库是Employee
,可以成功。 - 但是作为数据库命名的方式一般为:“tbl_employee”带下划线,而我们的实体类就都是首字母大写如:“Employee”。要想解决在mybatis根据
BaseMapper<实体类>
,实体类作为表名的问题。可以使用@TableName注解。
注解 | 描述 |
---|---|
TableName | 添加在实体类上,表明数据库中的表名,在进行crud操作时,会将TableName中的value属性当做表名 |
语法格式:
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@TableName("tbl_employee")
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
- 再次运行testSelect方法,不会报错。
小结BaseMapper里面一定填写实体类对象。原因是mybatisplus生成sql语句的方式是:根据BaseMapper泛型所填写的实体类作为数据库表去查找的。如果实体类对象和表名不一样,可以通过添加@TableName("数据库中的表名")来解决不一致问题。
1.3.2、insert语句让我明白
- 在
TestMpApplication
类中新增·testInsert()·方法,并运行
@SpringBootTest
public class TestMpApplication {
// 注入Mapper
@Autowired
private EmployeeMapper employeeMapper;
@Test
public void testSelect(){
Employee employee = (Employee) employeeMapper.selectById(1);
System.out.println(employee);
}
@Test
public void testInsert(){
Employee employee=new Employee(null,"瘦瘦","1270295098@qq.com",1,23 );
employeeMapper.insert(employee);
}
}
- 在创建
Employee
对象时,id
属性设置的null,因为数据库中设置了主键自增的操作,按说应该是能插入进行的,但是控制台报错了–数据类型不匹配。
- 分析:这是因为mybatisplus,会自动的把实体类中的名为
id
的属性当做数据库中的主键,而主键不能为空,所以mybatis就多此一举,自动生成了一个全局唯一的id,并将生成的值赋给当前对象的id(当mybaitsplus发现id属性是数字类型时,会随机生成一个数字,但是这个数字会超过实体类中定义的Integer的范围,所以会报上述的错误,如果把实体类中id改为Long,在将生成的数字赋给当前对象的id时,是不会报错的,但是插入到数据库又会报错,因为数据库设置了自增;同理如果id属性是字符串,就会生成一个字符串赋给当前对象,然后执行数据库插入操作,而此时数据库的id是自增的,自然还会报错。所以目前有两个问题:
1、如何让mybatisplus知道数据库中哪个字段是主键,而不是让mybatis自以为是的认为实体类中叫id的就是主键;
2、如何让mybatisplus知道主键是自增的,不需要mybatisplus为我生成一个值当做主键的值) - 解决:1、在实体类的主键属性上加如下注解,并设置它的属性为自增。
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@TableName("tbl_employee")
public class Employee {
// value对应着数据库中注解的字段名
// type = IdType.AUTO,告诉mybatis,数据库中注解递增,不用你给我创建全局唯一id
@TableId(value = "id" ,type = IdType.AUTO)
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
IdType
的另外两个比较常用的属性
它的使用如下:
@TableName("tbl_employee")
public class Employee {
// value对应着数据库中注解的字段名
// type = IdType.AUTO,告诉mybatis,数据库中注解递增,不用你给我创建全局唯一id
@TableId(value = "id" ,type = IdType.ASSIGN_ID)
private Long id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
value = "id"
告诉mybatisplus,数据库中的主键是id,别瞎猜。
type = IdType.ASSIGN_ID
告诉mybatisplus,数据库中的主键需要你给我生成一个唯一的字符串(这个字符串是纯数字,type = IdType.ASSIGN_UUID
生成的是纯字符串),生成好后赋值给我要插入到数据库对象的id属性上,并且实体类对象的id属性要能够接受到这个值(看上面Employee实体类中id类型已经改为Long
了,因为生成的id 超过了Integer范围,会报类型不匹配异常的)。数据库中的字段属性类型要根据上面官方的要求进行更改。
1.3.1、知识点二
- 将
Employee
中的实体类改为如下,主要是将上面测试的id类型为Long
,再次的改为Integer·
,TableId中的type = IdType.AUTO
。
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@TableName("tbl_employee")
public class Employee {
// value对应着数据库中注解的字段名
@TableId(value = "id" ,type = IdType.AUTO)
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
- 在
TestMpApplication
中,更改上面的testInsert
方法,将Employee对象的部分字段改为空,查看生成的sql语句。
可以看到插入对象时,只会插入非空的字段,对于更新操作,也是只更新非空的属性,sql语句也是只生成非null的字段,像不像mybatis中的动态sql呢
。
- 更改实体类对象,添加一个任意字段:
- 当进行插入操作时,因为该属性在数据库中并不存在,所以根据上面的结论:如果该字段为null的话,在生成的sql语句中就不会包含该字段,如果该字段不为null的话,在生产的sql语句就会有该字段,而数据库中没有就会报错。下面演示错误情况,修改刚才的
testInsert()
方法。 - 结果
- 那么如何让实体类属性中的非空字段不生成在sql语句当中呢?使用注解
@TableField(exist = false)
- zai
- 查看运行结果
1.3.3、知识点三
- 更改方法
- 查看生成的sql语句
- 可以看到sql语句:
INSERT INTO tbl_employee ( last_name, email, gender, age ) VALUES ( ?, ?, ?, ? )
实体类是lastName
,mybatis在执行插入时变成了last_name
,说明mybatisplus默认开启了自动驼峰命名规则(camel case)映射
。去官网
- 想要关闭的话,直接在yml中配置。(默认是开启的)
再次运行上面的插入操作,出现异常。
1.3.4、几个方法的说明
更改项目结构,上面我们的项目并没有service层,而我们在开发的时候三层结构还是要遵守的。下面更改项目结构。主要是添加service包和serviceimp包。
service
包中的EmployeeService
中的类声明如下:
serviceimp包下的EmployeeServiceImpl类如下
- 完成这样,
EmployeeServiceImpl
就有大量的操作数据库的方法 - 测试,新建测试类
注意:在直接使用mapper接口完成插入操作时,直接是insert、而使用service完成插入是save方法。
- 运行结果:
- 注意:它的命名可以学习,且看到它还给我门添加了事务。(贴心的小棉袄)
saveOrUpdate说明
public boolean saveOrUpdate(T entity) {
if (null == entity) {
return false;
} else {
Class<?> cls = entity.getClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!", new Object[0]);
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!", new Object[0]);
Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
return !StringUtils.checkValNull(idVal) && !Objects.isNull(this.getById((Serializable)idVal)) ? this.updateById(entity) : this.save(entity);
}
}
- 执行如下的方法
- 执行结果,此时数据库已经存在id为2的数据了。
- 改id数据库中不存在的,查看情况,我把employee对象的id改为了5,查看
2、分页操作
一、自定义使用
第一步:添加分页插件官网
更新一下依赖的版本,我上面的版本不支持新的插件配置。以前的配置可以看官网,这里使用新的插件配置。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
在项目下创建一个配置类,先创建一个配置的包。这里直接在主启动类里面配置了,也可以加入到容器中。
第二步:使用分页
-
在测试中使用分页
-
测试运行结果:
-
可以看到,上面执行了两条sql语句,第一条得到数据库中的总数量,然后给
IPage
类中的total
属性,根据总数量算出来分页数给了
·pages·属性。然后再执行依据分页语句,查询到当前页的数据。给了IPage
类中的records
属性。
二、XML使用
比如:多表联合查询到的数据,我们就需要自定义sql语句了,那么我们该怎样完成分页操作呢。
- 在mapper中编写接口
/**
* @author liushoushou
* @create 2021-10-19-22:15
*/
public interface EmployeeMapper extends BaseMapper<Employee> {
/**
* 查询gender=1的信息,并使用分页
* @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(你可以继承Page实现自己的分页对象)
* @param gender 参数
* return 分页对象
*/
IPage<Employee> selectPageVo(IPage<Employee> page ,String gender);
}
- EmployeeMapper.xml等同于编写一个普通 list 查询,mybatis-plus 自动替你分页,EmployeeMapper.xml声明在如下的结构中:
<mapper namespace="com.shoushou.mapper.EmployeeMapper">
<select id="selectPageVo" resultType="com.shoushou.pojo.Employee">
select * from tbl_employee where gender=#{gender}
</select>
</mapper>
- EmployeeServiceImpl调用分页方法
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
public IPage<Employee> selectEmployeePage(IPage<Employee>page,String gender){
// 要点!! 分页返回的对象与传入的对象是同一个
IPage<Employee> iPage = employeeMapper.selectPageVo(page, gender);
return iPage;
}
}
注意:在该操作中,我并没有指明mapper文件的位置,这是因为mybatisplus,默认给我们指明了路径。
我们知道,一般springboot会自动加载xxxxAutoConfiguration自动配置类,那我们就找到MybatisPlusAutoConfiguration:
- 该类中,
@EnableConfigurationProperties({MybatisPlusProperties.class})
,一看就是配置类,点进去查看:
- - 这就是为啥不用在yml或者properties中配置的原因。可以在yml或者properties中改变默认扫描的路径。
三、条件构造器
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件,QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
AbstractWrapper比较重要,里面的方法需要
重点学习. 该抽象类提供的重要方法如下:
- 示例:
@Test
public void testQueryWrapper(){
QueryWrapper<Employee> queryWrapper=new QueryWrapper();
// 注意select想要的字段,字段名是数据库中的名字
// 该语句的意思是,查询的字段为last_name,age 并且gender=1
// 方式一:这种方法属于编码,在代码中写死了
queryWrapper.select("last_name", "age")
// inSql是子查询
.inSql("id","select 1 from dual");
//方式二:使用lambda方式
LambdaQueryWrapper<Employee> lambda = queryWrapper.lambda();
// select中是函数式接口 Function<T, R>
lambda.select(Employee::getLastName,Employee::getAge)
.eq(Employee::getId,"2");
List<Employee> employees = employeeService.list(queryWrapper);
employees.forEach(System.out::println);
}
@Test
public void testUpdateWrapper(){
UpdateWrapper<Employee> updateWrapper=new UpdateWrapper<>();
updateWrapper.set("last_name","张x").set("age",18)
.eq("id",3);
// 同理lambda
LambdaUpdateWrapper<Employee> lambda = updateWrapper.lambda();
lambda.set(Employee::getLastName,"zhangx");
boolean update = employeeService.update(updateWrapper);
System.out.println(update);
}