-
前端需要给后端传递的参数:
page
:当前页码,用于指定用户想要查看的页。pageSize
:每页展示记录数,用于指定每页应显示多少条记录。
-
后端需要给前端返回的结果:
total
:总记录数,用于告诉前端数据库中总共有多少条记录。rows
:结果列表,这是当前页的记录数据,前端会用这些数据来填充页面上的内容。
java
EmpController.java
java
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("分页请求参数: {}, {}", page, pageSize);
PageResult<Emp> pageResult = empService.page(page, pageSize);
return Result.success(pageResult);
}
EmpService.java
java
public PageResult<Emp> page(Integer page, Integer pageSize) {
// 1. 获取总记录数
Long count = empMapper.count();
// 2. 获取每一页的数据列表
Integer start = (page - 1) * pageSize;
List<Emp> empList = empMapper.list(start, pageSize);
// 3. 封装分页结果
return new PageResult<>(count, empList);
}
EmpMapper.java
java
@Mapper
public interface EmpMapper {
@Select("select count(*) from emp e left join dept d on e.dept_id = d.id")
public Long count(); // 查询总记录数
@Select("select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id limit #{start}, #{pageSize}")
public List<Emp> list(Integer start, Integer pageSize); // 查询结果列表
1.<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
2.// 查询员工数据
@Select("select e.* from emp e...")
- public List<Emp> list();
-
设置分页参数。
-
调用 Mapper 接口方法。
-
解析并封装结果。
java
public PageResult<Emp> page(Integer page, Integer pageSize) {
// 1. 设置分页参数
PageHelper.startPage(page, pageSize);
// 2. 调用 Mapper 接口方法
List<Emp> empList = empMapper.list();
// 3. 解析并封装结果
return new PageResult<Emp>(...);
}
1. PageHelper 实现机制
a. 查询总记录数:
- 使用
select count(0) from emp e ...
来查询总记录数。这里使用count(0)
是一种优化技巧,因为count(*)
会计算所有列,而count(0)
则不会计算任何列,这在某些数据库中可能会更快。
b. 分页查询:
- 使用
select ... from emp e ... limit ?, ?
来实现分页查询。这里的?
是占位符,用于在执行时由 PageHelper 替换为实际的OFFSET
和LIMIT
值。
2. 注意事项
a. SQL语句结尾不要加分号(;):
- 当使用 PageHelper 时,SQL 语句的结尾不应该有分号。这是因为 PageHelper 需要在执行 SQL 语句之前修改它,以添加分页的
LIMIT
和OFFSET
子句。
b. PageHelper只会对紧跟在其后的第一条SQL语句进行分页处理:
- PageHelper 只对调用
PageHelper.startPage()
方法后紧跟的第一条 SQL 语句进行分页处理。如果需要对多个查询进行分页,需要为每个查询分别调用startPage()
。
1. MyBatis 中动态 SQL 的使用场景
- 动态 SQL 用于处理 SQL 语句不固定的情况,即 SQL 语句会根据用户的输入或外部条件的变化而变化。这允许开发者构建灵活的查询,以适应不同的业务需求。
2. MyBatis 中动态 SQL 的 <if>
及 <where>
标签的作用
-
<if>
标签:用于条件判断。如果指定的条件成立,MyBatis 会将对应的 SQL 片段拼接到最终的 SQL 语句中。这允许开发者根据条件动态地添加或修改 SQL 语句的一部分。 -
<where>
标签:用于根据查询条件生成WHERE
关键字。MyBatis 会自动处理条件,确保WHERE
子句的正确性,例如自动去除条件前面多余的AND
或OR
。这有助于避免 SQL 语法错误,并使生成的 SQL 语句更加清晰和易于维护。
新增员工-保存员工基本信息
EmpController.java
@PostMapping
public Result save(@RequestBody Emp emp) {
log.info("请求参数emp: {}", emp);
empService.save(emp);
return Result.success();
}
EmpServiceImpl.java
public void save(Emp emp) {
// 1. 补全基础属性
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
// 2. 保存员工基本信息
empMapper.insert(emp);
}
EmpMapper.java
@Insert("insert into emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) " +
"values(#{username}, #{name}, #{gender}, #{phone}, #{job}, #{salary}, #{image}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime})")
void insert(Emp emp);
说明:
-
EmpController :定义了一个
save
方法,使用@PostMapping
注解来处理 HTTP POST 请求。它接收一个Emp
对象作为请求体,并调用empService.save(emp)
来保存员工信息。 -
EmpService :
save
方法首先设置员工的创建时间和更新时间,然后调用empMapper.insert(emp)
来执行数据库插入操作。 -
EmpMapper :定义了一个
insert
方法,使用 MyBatis 的@Insert
注解来执行 SQL 插入语句。这个方法将Emp
对象的属性值映射到 SQL 语句中的占位符,并执行插入操作。
这种分层的架构有助于保持代码的清晰和可维护性,同时也便于进行单元测试。每个层都负责特定的职责,Controller 处理 HTTP 请求,Service 处理业务逻辑,Mapper 负责数据库操作。
1. 在插入数据之后,如何获取到主键值?
- 使用
@Options
注解可以配置 MyBatis 在插入数据后获取数据库自动生成的主键值。 useGeneratedKeys=true
表示启用自动获取主键值。keyProperty="id"
指定了实体类中用于存储主键值的属性名。
java
@Options(useGeneratedKeys=true, keyProperty="id")
2. <foreach>
动态 SQL 标签的作用及其属性的含义?
-
<foreach>
标签用于遍历集合或数组,以便在 SQL 语句中动态地构建包含多个元素的语句,如IN
子句。 -
属性含义:
collection
:指定要遍历的集合或数组的名称。item
:指定遍历过程中当前元素的别名,用于在 SQL 语句中引用当前元素。separator
:指定遍历元素之间的分隔符,这是可选的。open
:指定遍历开始前要拼接的 SQL 片段,这是可选的。close
:指定遍历结束后要拼接的 SQL 片段,这是可选的。
xml
<foreach ...>
<!-- SQL 片段 -->
</foreach>
使用 <foreach>
标签可以避免手动编写循环逻辑和字符串拼接,这在处理动态 SQL 时非常有用,特别是当你需要根据传入的集合或数组构建 SQL 语句时。
事务管理
1. 什么是事务?
- 事务是一组操作的集合,它是一个不可分割的工作单位。这意味着事务中的所有操作要么全部成功,要么全部失败。这种特性确保了数据的一致性和完整性。
2. 如何控制事务?
- 开启事务 :使用
start transaction
或begin
命令来开始一个新的事务。 - 提交事务 :使用
commit
命令来提交事务。这表示事务中的所有操作都已成功完成,并且对数据库的更改将被永久保存。 - 回滚事务 :使用
rollback
命令来回滚事务。如果事务中的任何一项操作失败,整个事务将被撤销,所有更改都不会被保存到数据库中。
3. 场景?
- 银行转账:在银行转账过程中,需要确保从转出账户扣除金额和转入账户增加金额这两个操作都成功,否则整个转账过程应该被撤销。
- 下单扣减库存:在电子商务中,当用户下单时,需要确保订单创建和库存扣减这两个操作都成功。如果库存扣减失败,订单创建也应该被撤销,以避免库存数量不准确。
事务管理是数据库操作中非常重要的一部分,它确保了数据的一致性和可靠性。在实际应用中,事务通常与数据库的锁定机制和并发控制一起使用,以处理多个用户或进程同时访问数据库的情况。
Spring 事务管理 - 控制事务
注解:@Transactional
- 作用 :
@Transactional
注解用于将当前方法的事务管理交给 Spring。在方法执行前,Spring 会开启一个事务;如果方法成功执行完毕,Spring 会提交事务;如果方法执行过程中出现异常,Spring 会回滚事务。 - 位置 :
@Transactional
注解可以用于业务层(service
)的方法上、类上或接口上。
示例代码
- 方法上使用
@Transactional(推荐)
java
@Transactional
@Override
public void save(Emp emp) {
// 1. 补全基础属性
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
// 2. 保存员工基本信息
empMapper.insert(emp);
int i = 1/0; // 这里故意制造一个异常,用于演示事务回滚
// 3. 保存员工的工作经历信息 - 批量
Integer empId = emp.getId();
List<EmpExpr> exprList = ...
}
- 类上使用
@Transactional
java
@Transactional
@Service
public class EmpServiceImpl implements EmpService {
// 类中的方法都会自动加入事务管理
}
- 接口上使用
@Transactional
java
@Transactional
public interface EmpService {
// 接口中的方法都会自动加入事务管理
}
配置日志
在 application.yml
文件中配置日志级别,可以查看 Spring 事务管理的底层日志,这对于调试和了解事务的内部工作机制非常有用。
yaml
# 配置日志信息,查看 Spring 事务管理的底层日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
通过设置 org.springframework.jdbc.support.JdbcTransactionManager
的日志级别为 debug
,可以获取到事务管理的详细信息,包括事务的开启、提交和回滚等操作的日志输出。这对于开发和维护阶段的调试非常有帮助。
1. @Transactional
注解的作用
@Transactional
注解用于在 Spring 管理的事务中声明性地配置事务规则。它告诉 Spring 在方法执行之前开启一个事务,并在方法执行完毕后,根据方法的执行结果来决定是提交(commit)还是回滚(rollback)这个事务。- 如果方法正常执行完毕,没有抛出任何异常,那么事务将会被提交。
- 如果方法执行过程中抛出了运行时异常(RuntimeException)或者错误(Error),那么事务将会被回滚。
2. @Transactional
注解的使用位置
- 方法上 :可以将
@Transactional
注解直接加在方法上,这样只有该方法内部的代码会参与到事务中。 - 类上 :可以将
@Transactional
注解加在类上,这样类中的所有方法都会被包含在事务中,除非某些方法明确指定了不同的事务规则。 - 接口上 :在接口上使用
@Transactional
注解可以为实现该接口的所有类的方法提供事务管理。这种方式在面向切面编程(AOP)中非常有用,因为它允许你定义一个通用的事务策略,然后应用于实现接口的所有类。
使用 @Transactional
注解可以简化事务管理的代码,使得业务逻辑更加清晰,同时保持事务的一致性和可靠性。