一、MP简介
它是 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
那么它是怎么增强的呢?
其实就是它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行,就类似于JPA。
二、SpringBoot项目整合 MP
引入了mybatis-plus
,就不用再引入mybatis
的依赖了,因为 mybatis-plus 中包含了。
1、pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xdja</groupId>
<artifactId>springboot-06-mybatisplus</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 要想是一个SpringBoot项目的话,必须继承 parent 启动器-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!--mybatis-plus的springboot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--简化代码的工具包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<configuration>
<mainClass>com.xdja.springsecurity.MyApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2、启动类
在启动类中添加mapper接口的扫描器。注意@MapperScan注解是spring和mybatis整合包下提供的。
/**
* @author liangqi
* @date 2020/9/6 10:16
*
* @MapperScan("com.xdja.mp.mapper") 扫描 mapper接口所在的包,生成代理实现类对象并注入到容器中
*/
@SpringBootApplication
@MapperScan("com.xdja.mp.mapper") // mapper包的扫描注解
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3、配置文件配置 application.yml
-
配置数据源
-
指定 mapper映射文件 xml的位置
-
给实体类Bean 起别名
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisplus?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL =false
username: root
password: root
#mybatis-plus
#指定mapper映射文件的位置
#给实体类Bean 起别名
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.xdja.mp.entity
4、分页插件的配置
只需要将MP的分页插件配置到spring容器中即可使用 MP的分页API。
/**
* 分页插件注入
*/
@Configuration
public class MyBatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor(){
// 分页插件的拦截器
PaginationInterceptor interceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
interceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
interceptor.setLimit(-1);
return interceptor;
}
}
// 列表查询——分页+条件
Page<User> page = new Page<>(2, 2);
IPage<User> userIPage = userMapper.selectPage(page, wrapper);
5、entity:
/**
* employee实体类Bean
*
* @TableName(value = "tb_employee") 指定表名
* @Data //lombok
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_employee")
public class Employee {
/**
* 指定自增策略
* value属性:与数据库表中列名映射。若名称一样(或者满足驼峰和下划线格式)可省略value。
* type属性:指定 主键值得自增策略。 IdType.AUTO自增
*/
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
/**
* value:若属性名与列名不一致,指定映射关系
* exist:标明数据表 中有没有与这个属性 对应的列
*/
@TableField(value = "name",exist = true)
private String name;
private String email;
private Integer age;
}
6、PersonMapper接口编写:
继承mybatis-plus的基础类 BaseMapper<Employee>
,它提供了很多的单表操作CRUD的方法。
除此之外,还可以像 一般的mybatis一样,自定义操作接口,然后编写 mapper.xml文件进行使用。
自定义接口如下:
public interface PersonMapper extends BaseMapper<Person> {
/**
* 自定义操作接口(mybatis方式):
* 获取 person列表
* @return
*/
List<Person> getPersonList();
}
PersonMapper.xml编写:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xdja.mp.mapper.PersonMapper">
<!--映射关系-->
<resultMap id="person" type="com.xdja.mp.entity.Person"/>
<!--sql-->
<select id="getPersonList" resultMap="person">
SELECT * FROM tb_person
</select>
</mapper>
7、接下来就可以写一个测试类 ,注入PersonDao
对象,然后调用 MP提供的CRUD方法进行操作
/**
* @author liangqi
* @date 2020/9/4
*/
@SpringBootTest(classes = MySpringApplication.class)
@RunWith(SpringRunner.class)
public class Test1 {
@Autowired
private PersonMapper personMapper;
@Test
public void getById(){
//1、根据id查询 selectById()
User user = userMapper.selectById(1);
//2、查询所有 selectList(null);
// 参数为查询条件QueryWrapper对象,无条件,则传入null
List<User> userList = userMapper.selectList(null);
//3、1创建一个条件构造器。 泛型:为要查询数据的实体类类型
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//3、2.将 where username = app 等值查询 这个条件封装到 条件构造器
//sql中的username = app 对应于eq("username","app")
// 参数1 字段名 参数2 要查询的值
queryWrapper.eq("username","app");、
//3、3.通过条件构造器 查询
User user = userMapper.selectOne(queryWrapper);
}
}
三、mp的通用crud实现单表操作:
需求:
存在一张tb_employee
表,且已有对应的实体类 Employee,实现tb_employee 表的 CRUD 操作我们需要做什么呢?
基于 Mybatis:
需要编写 EmployeeMapper 接口,并在 EmployeeMapper.xml 映射文件中手动编写 CRUD 方法对应的sql语句。
基于 MP:
只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口。
我们已经有了Employee、tb_employee了,并且EmployeeDao
也继承了BaseMapper
了,接下来就使用crud方法。
1、insert操作:
insert 一个不包含id的实体对象。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class test {
@Autowired
private EmplopyeeDao emplopyeeDao;
@Test
public void testInsert(){
Employee employee = new Employee();
employee.setLastName("东方不败");
employee.setEmail("dfbb@163.com");
employee.setGender(1);
employee.setAge(20);
emplopyeeDao.insert(employee);
//自增后的id写回到该实体中
System.out.println(employee.getId());
}
}
执行添加操作,直接调用insert方法传入实体即可。
传入的实体,若它的属性值为null,则不会插入该字段。
自增后的id会回填到对象中。
2、update操作(2种):
更新操作有2种,一种是根据id更新,另一种是根据条件更新。
第一种:根据id查询 updateById
:
@Test
public void testUpdate(){
Employee employee = new Employee();
employee.setId(1);
employee.setLastName("更新测试");
//根据id进行更新,没有传值的属性 就不会更新(常用)
emplopyeeDao.updateById(employee);
//根据id进行更新,没传值的属性 就更新为null
emplopyeeDao.updateAllColumnById(employee);
}
【注意】 注意这两个update操作的区别:
updateById
方法,没有传值的字段不会进行更新,比如只传入了lastName,那么age、gender等属性就会保留原来的值;
updateAllColumnById
方法,顾名思义,会更新所有的列,没有传值的列会更新为null。
第二种:根据QueryWrapper条件查询 update
:
/**
* update
* 参数1 一个User对象(用于set值) 若写null的话,那么set的值,where的值都需要封装在wrapper对象中。
* 参数2 条件构造器 (用于构造set值 和 where条件)
*/
@Test public void testUpdate() {
User user = new User();
user.setAge(22); //更新的字段
//更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id", 6);
//执行更新操作
int result = this.userMapper.update(user, wrapper);
System.out.println("result = " + result);
}
或者通过:UpdateWrapper
进行更新:
@Test public void testUpdate() {
//更新的条件以及字段
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 6).set("age", 23);
//执行更新操作
int result = this.userMapper.update(null, wrapper);
System.out.println("result = " + result);
}
3、select操作:
(1)、根据id查询单个数据:
Employee employee = emplopyeeDao.selectById(1);
(2)、根据查询条件 查询单个数据:
//1.创建一个条件构造器。 泛型:为要查询数据的实体类类型
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//2.将 where username = app 等值查询 这个条件封装到 条件构造器
//sql中的username = app 对应于eq("username","app")
// 参数1 字段名 参数2 要查询的值
queryWrapper.eq("username","app");
//3.通过条件构造器 查询
User user = userMapper.selectOne(queryWrapper);
【注意】:
这个方法的sql语句就是where id = 1 and last_name = 更新测试
,若是符合这个条件的记录不止一条,那么就会报错。
(3)、 根据查询条件 查询多条数据:
//方法1“
public void crudWrapperDemo2() {
//1.创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//2.构造条件 条件构造器的方法的第一个参数基本上都是字段名
queryWrapper.eq("username","lisi").or().eq("password","123456");
//3.查询
/**
* 批量查询 selectList() 方法中如果参数是null 就是查询所有
*/
List<User> userList = userMapper.selectList(queryWrapper);
}
//方法2:
//构造条件
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("last_name","东方不败");//写表中的列名
columnMap.put("gender","1");
//列表查询
List<Employee> employees = emplopyeeDao.selectByMap(columnMap);
System.out.println(employees.size());
【注意】:
查询条件用map集合封装,columnMap,key写的是数据表中的列名,而非实体类的属性名。
比如属性名为lastName,数据表中字段为last_name,这里应该写的是last_name。selectByMap方法返回值用list集合接收。
(4)、 通过id批量查询:
//id集合
List<Integer> idList = new ArrayList<>();
idList.add(1);
idList.add(2);
idList.add(3);
//查询
List<Employee> employees = emplopyeeDao.selectBatchIds(idList);
System.out.println(employees);
注: 把需要查询的id都add到list集合中,然后调用selectBatchIds方法,传入该list集合即可,该方法返回的是对应id的所有记录,所有返回值也是用list接收。
(5)、 分页+Wrapper 查询:
public void test1(){
// 构造条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age", 22);
// 列表查询——分页+条件
Page<User> page = new Page<>(2, 2);
IPage<User> userIPage = userMapper.selectPage(page, wrapper);
System.out.println("总的数据条数:" + userIPage.getTotal());
System.out.println("总页数:" + userIPage.getPages());
//记录s
List<User> records = userIPage.getRecords();
}
注:
selectPage方法就是分页查询,在page中传入分页信息,后者为分页条件
这个分页其实并不是物理分页
,而是内存分页
。也就是说,查询的时候并没有limit语句。等配置了分页插件后才可以实现真正的分页
。
(6)、条件查询 总的记录数 selectCount
:
@Test public void testSelectCount() {
//构造where 条件
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age", 23); //年龄大于23岁
//根据条件查询数据条数
Integer count = this.userMapper.selectCount(wrapper);
System.out.println("count = " + count);
}
4、delete操作:
(1)、 根据id删除:
emplopyeeDao.deleteById(1);
(2)、 根据 columnMap
条件删除:
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("gender",0);
columnMap.put("age",18);
int n = emplopyeeDao.deleteByMap(columnMap);
注: 该方法与selectByMap类似,将条件封装在columnMap中,然后调用deleteByMap方法,传入columnMap即可,返回值是Integer类型,表示影响的行数。
也可以根据 封装 Wrapper
条件构造器:
@Test public void testDeleteByMap() {
User user = new User();
user.setAge(20);
user.setName("张三");
//将实体对象进行包装,包装为条件
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
int result = this.userMapper.delete(wrapper);
System.out.println("result = " + result);
}
(3)、 根据id批量删除(参数为 Integer类型的id集合):
List<Integer> idList = new ArrayList<>();
idList.add(1);
idList.add(2);
emplopyeeDao.deleteBatchIds(idList);
注: 该方法和selectBatchIds类似,把需要删除的记录的id装进idList,然后调用deleteBatchIds,传入idList即可。
四、全局策略配置:
通过上面的小案例我们可以发现,实体类需要加@TableName注解指定数据库表名,通过@TableId注解指定id的增长策略。实体类少倒也无所谓,实体类一多的话也麻烦。
所以可以进行全局策略配置。
如此一来,实体类中的@TableName注解和@TableId注解就可以去掉了。
五、条件构造器(QueryWrapper):
以上基本的 CRUD 操作,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD 操作。BaseMapper 提供了多达 17 个方法供使用, 可以极其方便的实现单一、批量、分页等操作,极大的减少开发负担。但是mybatis-plus的强大不限于此,请看如下需求该如何处理:
需求:
我们需要分页查询 tb_employee 表中,年龄在 18~50 之间性别为男且姓名为 xx 的所有用户,这时候我们该如何实现上述需求呢?
使用MyBatis : 需要在 SQL 映射文件中编写带条件查询的 SQL,并用PageHelper 插件完成分页. 实现以上一个简单的需求,往往需要我们做很多重复单调的工作。
使用MP: 依旧不用编写 SQL 语句,MP 提供了功能强大的条件构造器 ------ QueryWrapper
。
接下来就直接看几个案例体会QueryWrapper的使用。
1、分页查询年龄在18 - 50且gender为0、姓名为tom的用户:
IPage<User> employees = userMapper.selectPage(new Page<User>(1,3),
new QueryWrapper<User>()
.between("age",18,50)
.eq("gender",0)
.eq("last_name","tom")
);
注: 由此案例可知,分页查询和之前一样,new 一个page对象传入分页信息即可。至于分页条件,new 一个QueryWrapper
对象,调用该对象的相关方法即可。between方法三个参数,分别是column、value1、value2,该方法表示column的值要在value1和value2之间;eq是equals的简写,该方法两个参数,column和value,表示column的值和value要相等。注意column是数据表对应的字段,而非实体类属性字段。
2、查询gender为0且名字中带有老师、或者邮箱中带有a的用户:
List<User> users= userMapper.selectList(
new QueryWrapper<User>()
.eq("gender",0)
.like("last_name","老师")
//.or()//和or new 区别不大
.or()
.like("email","a")
);
注: 未说分页查询,所以用selectList即可,用QueryWrapper的like方法进行模糊查询,like方法就是指column的值包含value值,此处like方法就是查询last_name中包含“老师”字样的记录;“或者”用or或者orNew方法表示,这两个方法区别不大,用哪个都可以,可以通过控制台的sql语句自行感受其区别。
3、查询gender为0,根据age排序,简单分页:
List<User> employees = userMapper.selectList(
new QueryWrapper<User>()
.eq("gender",0)
.orderByAsc("age")
.last("desc limit 1,3")//在sql语句后面追加last里面的内容(分页)
);
注: 若要降序,可以使用orderByDesc方法,也可以如案例中所示用last方法;last方法就是将last方法里面的value值追加到sql语句的后面,在该案例中,追加了limit 1,3
所以可以进行分页。
5、根据条件更新:
@Test
public void testEntityWrapperUpdate(){
Employee employee = new Employee();
employee.setLastName("苍老师");
employee.setEmail("cjk@sina.com");
employee.setGender(0);
emplopyeeDao.update(employee,
new QueryWrapper<Employee>()
.eq("last_name","tom")
.eq("age",25)
);
}
注: 该案例表示把last_name为tom,age为25的所有用户的信息更新为employee中设置的信息。
6、根据条件删除:
emplopyeeDao.delete(
new QueryWrapper<Employee>()
.eq("last_name","tom")
.eq("age",16)
);
注: 该案例表示把last_name为tom、age为16的所有用户删除。
六、几个注解
@TableField
: 常常解决的问题有2个:
value 属性:
- 1、对象中的属性名和字段名不一致的问题(非驼峰)
- 2、对象中的属性字段在表中不存在的问题
exist属性:
当该字段在数据库表中不存在时。
其他用法:select=true
sql查询的结果中 是否要包含这个字段。