SpringBoot+MyBatis CRUD 进阶:局部更新、批量删除、条件查询与分页查询(1.30)
一、Spring Boot Controller 请求参数获取
1.如何从Controller中取出以前Servlet中的对象?
在方法的参数列表中定义HttpServiceRequest request,HttpServletResponse response,对象打点调用即可。
@Controller
public class PageController {
@RequestMapping("/page2")
public String page2(Map<String,Object> map,HttpServiceRequest request,HttpServletResponse response){
//例如:
request.getParameter("username");
return "page2";
}
}
2.如何获取前端传过来的Header?

在方法的参数列表中定义@RequestHeader("token") String token,@RequestHeader括号中的名字与前端一致。
@Controller
public class PageController {
@RequestMapping("/page2")
public String page2(Map<String,Object> map,@RequestHeader("token") String token){
//输出token
System.out.println(token);
return "page2";
}
}
二、MyBatis项目搭建
1.数据库的配置:
在pom.xml中,我们把MyBatis的配置放开后,再启动会报错,如下:

原因是:我们在启动时,启动器会帮我们创建一个DataSource数据源,DataSource会建立数据库的连接,然后把这个连接对象放到IOC中。
接下来,我们就要在application.properties下配置数据库:
spring.application.name=BootTest
#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/javafk?serverTimezone=GMT%2B8&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2.domain包下新建Employee类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Long id;
private String name;
private Double sal;
private Boolean sex;
}
3.mapper包下新建EmployeeMapper接口:
public interface EmployeeMapper {
}
4.(可省略此步骤)resources下创建配置文件,选择创建目录,要求同包同名。


选择一个Mapper.xml文件加入----->其实不用建xml文件也可以,后期可替换。
小结:
1.mapper接口的实现类已经不用手动创建了,但需要在mapper接口上写@Mapper注解,它会帮我们自动生成mapper的实现类。
2.mapper.xml文件可省略,但要在mapper的方法中加上注解。
3.MyBais阶段中service层和controller层还用不到。主要是在test类中写方法,再到mapper接口通过注解实现。
4.本次测试只选择用注解方式实现sql语句的使用,若是复杂的操作,如多表查询,则建议用Mapper.xml文件书写sql。
5.项目结构:

三、SpringBoot+MyBatis实战
1.查询所有:
test类:注意要先从IOC容器中取出EmployeeMapper对象
@SpringBootTest
class SpringBootCrudTset1ApplicationTests {
@Autowired
EmployeeMapper employeeMapper;//注意
//查询所有
@Test
void selectAll() {
List<Employee> empList = employeeMapper.selectAll();
empList.forEach(System.out::println);
}
}
mapper接口:注意要先写@Mapper注解
@Mapper//注意
public interface EmployeeMapper {
//查询所有
@Select("select * from employee")
//注解式映射配置
@Results({
@Result(property = "id",column = "id",id = true),
@Result(property = "name",column = "name"),
@Result(property = "sal",column = "sal"),
@Result(property = "sex",column = "sex")
})
List<Employee> selectAll();
2.添加:
test类:我们没有设置id,是因为我们使用了@Options(useGeneratedKeys = true,keyProperty ="id")注解,即让数据库主键自增长和从数据库中获取到的id值赋值给实体对象中的id属性。即使手动设置了 id,也会被这个注解覆盖掉。
//添加
@Test
void Add() {
Employee employee = new Employee(null,"翠花",6000.0,true);
int m =employeeMapper.Add(employee);
System.out.println(m>0?"添加成功":"添加失败";
}
mapper接口:
//添加
@Insert("insert into employee (name,sal,sex) values (#{name},#{sal},#{sex})")
@Options(useGeneratedKeys = true,keyProperty ="id")
int Add(Employee employee);
小结:
如果不用@Options(useGeneratedKeys = true, keyProperty = "id")这个注解,执行save添加方法后,传入的 Employee 对象的 id 属性会是 null,数据库里虽然能正常插入数据(依赖数据库主键自增),但主键 id 不会回写到实体对象中。
3.全量更新:
test类:注意全量更新要传递的是对象,不是id,MyBatis会根据你传的id进行查找,再用对象的其他属性更新值。
//全量更新 -->根据id
@Test
void update() {
Employee employee = new Employee(3L,"蹦蹦",3000.0,true);
int m = employeeMapper.update(employee);
System.out.println(employee);
System.out.println(m>0?"更新成功":"更新失败");
}
mapper接口:
//全量更新
@Update("update employee set name=#{name},sal=#{sal},sex=#{sex} where id = #{id}")
int update(Employee employee);
4.局部更新(动态):
test类:
设为null的字段不更新,需要使用Mapper接口的SQL 构建器内部类自定义方法实现动态拼接sql。
@UpdateProvider的method属性值,需要和SQLProvider内部类中实际拼接 SQL 的方法名完全一致;与测试类方法名无关
//局部更新
@Test
void patchUpdate() {
Employee employee = new Employee(10L,"团子",null,null);
int m = employeeMapper.patchUpdate(employee);
System.out.println(employee);
System.out.println(m>0?"更新成功":"更新失败");
}
mapper接口:
//局部更新
@UpdateProvider(type = EmployeeMapper.SQLProvider.class,method = "patchUpdate")
int patchUpdate(Employee employee);
class SQLProvider{
public String patchUpdate(Employee employee) {
StringBuffer sql = new StringBuffer("update employee set ");
if(!StringUtils.isEmpty(employee.getName())){
sql.append("name = #{name},");
}
if(!StringUtils.isEmpty(employee.getSal())){
sql.append("sal = #{sal},");
}
if(!StringUtils.isEmpty(employee.getSex())){
sql.append("sex = #{sex},");
}
String subString = sql.substring(0,sql.length()-1);//要取出sql.substring的返回值,不然,还是没去掉
subString += " where id = #{id}";//注意where前有空格
return subString.toString();
}
}
5.局部更新2:
test类:
//局部更新2
@Test
void patchUpdate2() {
//传入的更新对象
Employee employee = new Employee(11L,"冰点",8000.0,true);
//根据id查找原始对象
Employee emp = employeeMapper.findById(employee.getId());
//替换,为空 --> 不替换,不为空 -->替换
if(!StringUtils.isEmpty(emp.getName())){
emp.setName(employee.getName());
}
if(!StringUtils.isEmpty(emp.getSal())){
emp.setSal(employee.getSal());
}
if(!StringUtils.isEmpty(emp.getSex())){
emp.setSex(employee.getSex());
}
//全量更新
int m = employeeMapper.update(emp);
System.out.println(m>0?"更新成功":"更新失败");
}
mapper接口:
//局部更新2
@Select("select * from employee where id = #{id}")
@Results({
@Result(property = "id",column = "id",id = true),
@Result(property = "name",column = "name"),
@Result(property = "sal",column = "sal"),
@Result(property = "sex",column = "sex")
})
Employee findById(Long id);
注意:
@Results注解的作用范围默认只作用在它当前修饰的查询方法上,并非全局生效。- 不要忘了if语句中的
!
6.删除:
test类:
//删除
@Test
void deleteById() {
Long id = 11L;
int m = employeeMapper.deleteById(id);
System.out.println(m>0?"删除成功":"删除失败");
}
mapper接口:
//删除
@Delete("delete from employee where id = #{id}")
int deleteById(Long id);
7.批量删除:
test类:
//批量删除
@Test
void deleteByIds() {
Long[] ids = {12L,13L};
int m = employeeMapper.deleteByIds(ids);
System.out.println(m>0?"删除成功":"删除失败");
}
mapper接口:
//批量删除
@DeleteProvider(value = EmployeeMapper.SQLProvider.class,method = "deleteByIds")
int deleteByIds(Long[] ids);
class SQLProvider{
public String deleteByIds(Long[] ids){
StringBuffer sql = new StringBuffer("delete from employee where id in (");
for(Long id:ids){
sql.append(id).append(",");
}
String subString = sql.substring(0,sql.length()-1);
subString += ")";
return subString;
}
}
8.条件查询:
①定义qo包下EmployeeQO类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeQO {
private String name;
private int salStart;
private int salEnd;
}
②test类:
//条件查询
@Test
void queryByCondition() {
EmployeeQO qo = new EmployeeQO();
qo.setName("吉吉");
qo.setSalStart(new BigDecimal(1000));
qo.setSalEnd(new BigDecimal(10000));
List<Employee> employeeList = employeeMapper.queryByCondition(qo);
employeeList.forEach(System.out::println);
}
③mapper接口:
//条件查询
@SelectProvider(value = EmployeeMapper.SQLProvider.class,method = "queryByCondition")
List<Employee> queryByCondition(EmployeeQO qo);
class SQLProvider{
public String queryByCondition(EmployeeQO qo) {
StringBuffer sql = new StringBuffer("select * from employee where 1=1");
if(!StringUtils.isEmpty(qo.getName())){
sql.append(" and name like concat('%',#{name},'%')");
}
if(!StringUtils.isEmpty(qo.getSalStart())) {
sql.append(" and sal >= #{salStart}");
}
if(!StringUtils.isEmpty(qo.getSalEnd())) {
sql.append(" and sal <= #{salEnd}");
}
return sql.toString();
}
}
9.分页查询:
①引入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
②test类:
//分页
@Test
void Page() {
EmployeeQO qo = new EmployeeQO();
//设定分页信息
PageHelper.startPage(1,3);
//无条件查询 -->查询所有数据,此查询会被PageHelper自动分页,返回的是分页后的结果集
List<Employee> employeeList = employeeMapper.queryByCondition(qo);
//计算参数+封装
PageInfo<Employee> pageInfo = new PageInfo<>(employeeList);
//获取分页后的小集合
List<Employee> list = pageInfo.getList();
employeeList.forEach(System.out::println);
}
流程概述:
-
实例化一个qo对象,为后续条件查询传入字段全为默认值null的对象
-
设定分页信息,指定第?页,每页显示?条
-
无条件查询,即查询所有数据;而PageHelper的拦截器会拦截startPage后的第一条查询语句,自动为原 sql 拼接分页语法;
返回的List是(页码 1,页大小 3)的结果集
-
PageHelper会通过上面的分页参数和查询结果,自动计算出总条数、总页数、上一页 / 下一页等信息,封装到 PageInfo 中
-
获取当前页数据,即employeeList
思考:为什么 PageHelper.startPage();筛选出了要分页的数据,还要用new PageInfo<>()计算参数信息并进行封装?
1.实际开发中,网页还需要显示总记录数、总页数以及当前页数等数据,不仅仅只是显示出你要的分页后的数据,例如,第一页的三条数据。
2.new PageInfo<>()简化操作,无需手写大量重复代码。
浙公网安备 33010602011771号