数据访问
文章目录
一、面向接口编程的ORM
定义
ORM(Object对象,Relational数据库表,Mapping映射)是一种将Java对象与数据库表自动映射的技术,JPA则是ORM的官方规范,SpringDataJPA是对JPA进行进一步的封装和增强
**核心思想:**操作对象,让框架自动转换为SQL操作数据库
共同原则:数据访问层以接口形式暴露能力,Service层只依赖接口
Spring Data JPA 的实现方式
| 组件 | 作用 | 示例 |
|---|---|---|
@Entity | 标记 Java 类为数据库表映射 | @Entity @Table(name = "user") public class User { ... } |
Repository | 数据访问接口(继承 JpaRepository<T, ID>) | public interface UserRepository extends JpaRepository<User, Long> {} |
| Query Methods | 方法名自动解析为 SQL | List<User> findByEmailAndStatus(String email, String status); |
- service注入
UserRespository接口,不关心底层如何生成SQL- 无实现类,由Spring Data 在运行时动态代理生成
二、MyBatis/MyBatis-Plus
背景
- 原生JDBC写法较为繁琐
- 在变量多的sql中容易写错、遗漏、对不上
- 对象映射容易写错
MP解决方法
MP是在MyBatis基础上提供通用的CRUD能力,简单操作无需写SQL,复杂查询仍可通过XML或注解实现
封层调用关系:
Controller ← 接收 HTTP 请求,参数校验,调用 Service
↓
Service ← 业务逻辑编排,事务控制,依赖Mapper接口(面向接口编程)
↓
Mapper ← 数据库操作(继承
BaseMapper本身是接口,无实现类)
特点
| 功能 | 说明 |
|---|---|
BaseMapper<T> | 内置 20+ 个通用方法(insert, selectById, update, delete 等) |
IService<T> + ServiceImpl | Service 层通用 CRUD(如 list(), page()) |
| 条件构造器(Wrapper) | 链式编程构建 WHERE 条件,无需 XML |
| 自动分页插件 | 一行配置开启物理分页 |
| 逻辑删除 | deleted = 1 自动过滤,无需手写 WHERE deleted = 0 |
| 自动填充 | 插入/更新时自动设 createTime / updateTime |
UserMapper是一个接口,继承BaseMapper<User>Service层通过@Autowired注入UserMapper,完全符合依赖抽象,不依赖具体
实践
-
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> -
Entity加注解
以user表为例子
@Data//需要依赖lombok的引入 @TableName("user") // 指定表名(默认类名转下划线) public class User { @TableId(type = IdType.AUTO) // 主键策略 private Long id; @TableField("user_name") // 指定字段名(默认驼峰转下划线) private String userName; @TableLogic // 逻辑删除标记(0=未删, 1=已删) private Integer deleted; @TableField(fill = FieldFill.INSERT) // 自动填充创建时间 private LocalDateTime createTime; } -
Mapper继承
BaseMapper关键,这是接口@Mapper public interface UserMapper extends BaseMapper<User> { // 无需写任何方法!已有 insert/update/delete/selectById/list/page 等 //自定义方法仍以接口形式声明 } -
Service使用(可选)
public interface UserService extends IService<User> {} @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { // 自动获得 save(), removeById(), list(), page() 等方法 // 底层仍通过 UserMapper(接口)操作数据库 } -
使用示例
@Service public class DemoService { @Autowired private UserMapper userMapper; @Autowired private UserService userService; public void example() { // 1. 通用插入 User u = new User(); u.setUserName("张三"); userMapper.insert(u); // 自动填充 createTime, id // 2. 条件查询(Wrapper) List<User> list = userMapper.selectList( new QueryWrapper<User>() .eq("status", "ACTIVE") .like("user_name", "张") .orderByDesc("create_time") ); // 3. 分页查询(需先配置分页插件!) IPage<User> page = userService.page( new Page<>(1, 10), // 第1页,每页10条 new QueryWrapper<User>().eq("status", "ACTIVE") ); } } -
配置分页插件(必须)
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusAssistant interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
无论JPA还是MP,都遵循“面向接口编程”原则–单纯实现机制不同,本质不同
三、事务管理:@Transactional 的原理与陷阱
事务的四大特性:ACID
- Atomicity(原子性):不可分割,全做或全不做。
- Consistency(一致性):数据从一个合法状态变到另一个合法状态。
- Isolation(隔离性):多个事务并发执行时互不干扰。
- Durability(持久性):一旦提交,结果永久保存。
🔑 核心机制:AOP 代理 + ThreadLocal
- Spring 通过 动态代理(JDK/CGLIB) 在方法执行前后加入事务控制。
- 事务上下文(Connection)通过
ThreadLocal绑定到当前线程,保证同一个线程内多次 DB 操作共用一个连接。
✅ 正确使用姿势
一般在公共的、Service中调用多的方法上使用
1@Service
2public class OrderService {
3
4 @Autowired
5 private OrderRepository orderRepo;
6
7 @Transactional // 默认:传播行为 REQUIRED,回滚 RuntimeException
8 public void createOrder(Order order) {
9 orderRepo.save(order);
10 // 其他操作...
11 }
12}
如果是要求返回异常可以通过@Transactional(rollbackfor=Exception.class)即可
⚠️ 常见陷阱(面试高频!)
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 自调用失效 | this.method() 绕过代理 | 注入自己(@Autowired OrderService self)或提取到另一个 Service |
| 非 public 方法 | Spring AOP 要求 public | 改为 public |
| 异常被捕获未抛出 | Spring 只对未处理的 RuntimeException 回滚 | 手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() |
| 异步方法 | 新线程无事务上下文 | 在异步方法内部重新开启事务(不推荐),或改用消息队列 |
💡 记住:
@Transactional是 声明式事务,底层依赖 数据库的 ACID 和 连接池的 Connection 管理。
五、分页与排序(Pageable)—— 重点详解!
这是 Spring Data JPA 和 Spring MVC 集成的精华功能,无需手写 LIMIT/OFFSET。
✅ 核心组件:Pageable + Page<T>
Pageable:封装分页参数(页码、每页大小、排序字段)Page<T>:包含分页结果 + 总数 + 是否有下一页等元信息
使用步骤(JPA 示例)
1. Repository 方法返回 Page<T>
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByStatus(String status, Pageable pageable);
}
2. Controller 接收 Pageable(Spring MVC 自动转换)
1@GetMapping("/users")
2public Page<User> getUsers(
3 @RequestParam(defaultValue = "ACTIVE") String status,
4 Pageable pageable // ← 自动绑定分页参数!
5) {
6 return userRepository.findByStatus(status, pageable);
7}
3. 前端传参(默认参数名)
| 参数 | 说明 | 默认值 |
|---|---|---|
page | 页码(从 0 开始!) | 0 |
size | 每页数量 | 20 |
sort | 排序字段(可多个) | 无 |
请求示例:
GET /users?status=ACTIVE&page=1&size=10&sort=name,desc&sort=createTime,asc
🔧 自定义分页参数名(可选)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableHandlerMethodArgumentResolver resolver =
new PageableHandlerMethodArgumentResolver();
resolver.setPageParameterName("pageNum"); // 页码参数名改为 pageNum
resolver.setSizeParameterName("pageSize"); // 每页大小改为 pageSize
resolver.setOneIndexedParameters(true); // 页码从 1 开始(默认 0)
resolvers.add(resolver);
}
}
🆚 MyBatis-Plus 如何分页?
MP 需要 显式开启分页插件:
1. 配置插件(Spring Boot 3.x)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
2. Service 中使用 IPage<T>
public IPage<User> getUsers(int page, int size) {
Page<User> pageObj = new Page<>(page, size);
return userMapper.selectPage(pageObj, null); // 第二个参数是条件 Wrapper
}
⚠️ 注意:MP 的
Page默认页码从 1 开始,而 Spring Data JPA 默认从 0 开始!
💡 分页最佳实践
-
前端传参:建议统一约定(如
pageNum/pageSize,从 1 开始) -
后端校验:限制
size最大值(防恶意请求,如size=1000000) -
性能优化:大数据量分页用 游标分页(cursor-based),而非
OFFSET(避免深度分页性能暴跌)
| 特性 | OFFSET 分页(传统) | 游标分页(Cursor) |
|---|---|---|
| 是否支持跳页 | ✅ 支持(如直接第100页) | ❌ 不支持(只能顺序加载) |
| 性能 | 深度分页极慢 | 恒定高效 |
| 适用场景 | 后台管理、报表 | APP 列表、Feed 流 |
| 实现复杂度 | 简单 | 需前端传“游标” |
| 数据一致性 | 可能重复/漏数据(插入新数据时) | 更稳定(基于位置) |
✅ 总结:数据访问层核心思想
| 模块 | 关键词 | 一句话总结 |
|---|---|---|
| JPA | Repository / Query Methods | “方法名即查询”,适合快速开发 |
| MyBatis/MP | SQL 自由 / Wrapper | “SQL 我做主”,适合复杂场景 |
| 多数据源 | @Primary / @Qualifier | 一个应用,多个库,事务不跨源 |
| 事务 | @Transactional / AOP 代理 | 方法级原子操作,注意自调用陷阱 |
| 分页 | Pageable / Page | 自动分页排序,参数约定要统一 |
🌟 终极目标:让数据操作像调用本地方法一样简单,同时不失灵活性和性能可控性。
新手摸索,如有错误,请大佬指出🙏
171

被折叠的 条评论
为什么被折叠?



