SpringBoot总结(3)数据访问

数据访问

一、面向接口编程的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方法名自动解析为 SQLList<User> findByEmailAndStatus(String email, String status);
  • service注入UserRespository接口,不关心底层如何生成SQL
  • 无实现类,由Spring Data 在运行时动态代理生成

二、MyBatis/MyBatis-Plus

背景

  1. 原生JDBC写法较为繁琐
  2. 在变量多的sql中容易写错、遗漏、对不上
  3. 对象映射容易写错

MP解决方法

MP是在MyBatis基础上提供通用的CRUD能力,简单操作无需写SQL,复杂查询仍可通过XML或注解实现

封层调用关系:

Controller ← 接收 HTTP 请求,参数校验,调用 Service

​ ↓

Service ← 业务逻辑编排,事务控制,依赖Mapper接口(面向接口编程)

​ ↓

Mapper ← 数据库操作(继承BaseMapper本身是接口,无实现类)

特点
功能说明
BaseMapper<T>内置 20+ 个通用方法(insert, selectById, update, delete 等)
IService<T> + ServiceImplService 层通用 CRUD(如 list(), page()
条件构造器(Wrapper)链式编程构建 WHERE 条件,无需 XML
自动分页插件一行配置开启物理分页
逻辑删除deleted = 1 自动过滤,无需手写 WHERE deleted = 0
自动填充插入/更新时自动设 createTime / updateTime

  • UserMapper是一个接口,继承BaseMapper<User>
  • Service层通过@Autowired注入UserMapper,完全符合依赖抽象,不依赖具体

实践

  1. 引入依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.5</version>
    </dependency>
    
  2. 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;
    }
    
  3. Mapper继承BaseMapper关键,这是接口

    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        // 无需写任何方法!已有 insert/update/delete/selectById/list/page 等
        //自定义方法仍以接口形式声明
    }
    
  4. Service使用(可选)

    public interface UserService extends IService<User> {}
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        // 自动获得 save(), removeById(), list(), page() 等方法
        // 底层仍通过 UserMapper(接口)操作数据库
    }
    
  5. 使用示例

    @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")
            );
        }
    }
    
  6. 配置分页插件(必须)

    @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 流
实现复杂度简单需前端传“游标”
数据一致性可能重复/漏数据(插入新数据时)更稳定(基于位置)

✅ 总结:数据访问层核心思想

模块关键词一句话总结
JPARepository / Query Methods“方法名即查询”,适合快速开发
MyBatis/MPSQL 自由 / Wrapper“SQL 我做主”,适合复杂场景
多数据源@Primary / @Qualifier一个应用,多个库,事务不跨源
事务@Transactional / AOP 代理方法级原子操作,注意自调用陷阱
分页Pageable / Page自动分页排序,参数约定要统一

🌟 终极目标让数据操作像调用本地方法一样简单,同时不失灵活性和性能可控性。

新手摸索,如有错误,请大佬指出🙏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值