-------------------------------------------------------------------------------------------
一、MyBatis 中 Mapper 注解与 XML 方式在处理复杂业务逻辑时的核心差异
复杂业务逻辑通常涉及 动态 SQL(多条件拼接)、多表关联查询、嵌套对象映射、批量操作、子查询 等场景。两种方式在这些场景下的处理能力和体验有显著区别:
复杂场景 | Mapper 注解方式 | XML 映射文件方式 |
---|---|---|
动态 SQL 处理 | 需通过 @SelectProvider 等 Provider 类 实现,需在 Java 代码中用 SQL 类拼接 SQL(类似字符串拼接),逻辑分散在 Java 类中,可读性差,维护困难。
WHERE 子句,容易出错。 |
原生支持 <if> 、<where> 、<foreach> 、<choose> 等动态标签,SQL 逻辑与条件判断在 XML 中结构化呈现,直观清晰,便于修改。
<if test="name != null"> 直接控制条件拼接,无需手动处理 AND 前缀。 |
多表关联与结果映射 | 复杂结果映射(如嵌套对象、集合)需通过 @Results + @Result 注解手动配置,多层嵌套时注解冗长,易混淆。
@Result ,可读性差。 |
通过 <resultMap> 标签统一管理映射关系,支持 <association> (一对一)、<collection> (一对多)等标签,清晰表达对象间关系,多层嵌套结构一目了然。
resultMap 中分层定义。 |
批量操作(插入 / 更新) | 批量插入需在注解中写长 SQL 字符串(如 VALUES (#{u1.name}), (#{u2.name})... ),参数多时字符串拼接繁琐,易出错,且不支持动态集合长度。 |
通过 <foreach> 标签遍历集合动态生成 SQL,支持任意长度的批量操作,语法简洁(如 VALUES <foreach collection="list" item="u" separator=",">(#{u.name})</foreach> )。 |
子查询与复杂 JOIN | 子查询或多表 JOIN 的 SQL 语句冗长,写在注解中会导致接口类臃肿,且换行、缩进等格式难以维护,影响可读性。 | XML 中可通过格式化 SQL(换行、缩进)清晰表达子查询和 JOIN 逻辑,甚至可拆分 SQL 为多行便于理解(如将 LEFT JOIN 拆分到单独行)。 |
SQL 复用与模块化 | 无法直接复用 SQL 片段,相同逻辑需在多个注解中重复编写,冗余度高。 | 支持 <sql> 标签定义可复用 SQL 片段(如通用查询字段、条件),通过 <include> 标签引用,减少重复代码,便于统一维护。 |
团队协作与 DBA 介入 | SQL 逻辑嵌入 Java 代码,DBA 需熟悉 Java 语法才能优化,协作成本高。 | SQL 集中在 XML 文件中,DBA 可直接修改优化,无需关注 Java 代码,协作更顺畅。 |
总结:在复杂业务逻辑下,XML 方式是更优选择—— 其结构化标签、动态 SQL 支持、结果映射灵活性和 SQL 与代码分离的特性,能显著降低复杂逻辑的维护成本;而注解方式仅适合简单场景,复杂逻辑下会导致代码臃肿、可读性差。
二、MyBatis 与 JPA 的核心对比
MyBatis 和 JPA 都是 Java 生态中主流的 ORM(对象关系映射)框架,但设计理念和适用场景截然不同:
对比维度 | MyBatis | JPA(以 Hibernate 实现为例) |
---|---|---|
设计理念 | 半自动 ORM:聚焦 SQL 与 Java 对象的映射,需手动编写 SQL(或通过注解 / XML 定义),保留对 SQL 的完全控制。 | 全自动 ORM:基于 “对象模型驱动”,通过注解 / XML 定义实体与表的映射关系,自动生成 SQL,屏蔽数据库细节。 |
SQL 控制能力 | 完全手动控制 SQL,支持复杂查询(多表 JOIN、子查询、动态条件等),可针对性优化 SQL 性能(如索引利用、分页逻辑)。 | 默认自动生成 SQL(简单 CRUD),复杂查询需通过 JPQL(Java 持久化查询语言)或原生 SQL 实现,自动生成的 SQL 可能存在性能问题(如冗余字段查询、低效 JOIN)。 |
学习成本 | 低:核心是 SQL + 映射规则,开发者只需熟悉 SQL 和 MyBatis 基本标签 / 注解,上手快。 | 高:需理解 ORM 核心概念(如持久化上下文、一级缓存)、JPQL 语法、关联映射策略(如懒加载、级联)等,对 SQL 不熟悉的开发者可能难以调试。 |
灵活性与定制化 | 高:可根据业务需求自由编写 SQL,适配各种复杂场景(如分库分表、多数据源),对数据库特性(如 MySQL 存储过程、PostgreSQL 特有函数)支持友好。 | 中:简单场景无需写 SQL,但复杂场景(如动态多条件查询、特殊索引利用)需绕过自动生成逻辑,定制化成本高。 |
性能 | 性能可控:手动编写 SQL 可避免冗余操作(如只查必要字段),适合高并发、大数据量场景。 | 性能依赖自动生成的 SQL 质量,复杂查询可能产生低效 SQL(如 N+1 查询问题),需额外优化(如 FetchType 调整、查询缓存)。 |
适用场景 | 1. 复杂业务逻辑(多表关联、动态 SQL、批量操作);
|
1. 简单 CRUD 场景(如后台管理系统);
|
总结:
- MyBatis 是 “SQL 优先” 的框架,适合需要 精细控制 SQL、处理复杂业务逻辑 的场景,灵活度和性能可控性更强;
- JPA 是 “对象优先” 的框架,适合 快速开发、简单 CRUD 为主 的场景,能减少重复代码,但复杂场景下灵活性不足。
选择时需结合业务复杂度(简单 vs 复杂)、团队技术栈(熟悉 SQL vs 熟悉 ORM)、性能要求(高并发 vs 一般需求)综合决策。
-------------------------------------------------------------------------------------------
Spring Boot JPA 是 Spring 生态中基于 JPA(Java Persistence API,Java 持久化规范) 的简化数据访问方案,通过 Spring Data JPA 实现对 JPA 的封装,大幅减少数据访问层(DAO)的重复代码,让开发者更专注于业务逻辑。
一、核心概念与关系
- JPA:是 Java 官方定义的 ORM(对象关系映射)规范,定义了实体类与数据库表的映射规则、CRUD 操作接口等,但本身不提供实现。
- Spring Data JPA:是 Spring 对 JPA 规范的具体实现,通过 “接口定义方法签名” 自动生成 SQL,无需手动编写 DAO 实现类。
- Spring Boot JPA:Spring Boot 对 Spring Data JPA 的自动配置支持(如自动配置数据源、实体管理器等),进一步简化集成流程。
二、Spring Boot 集成 JPA 步骤
1. 引入依赖
在
pom.xml
中添加核心依赖(以 MySQL 为例):xml
<!-- Spring Data JPA -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
2. 配置数据源与 JPA 特性
在
application.yml
中配置数据库连接和 JPA 行为:yaml
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/jpa_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghaiusername: rootpassword: rootjpa:hibernate:ddl-auto: update # 自动创建/更新表结构(create:每次启动重建表;update:保留数据更新结构;none:关闭)show-sql: true # 控制台打印 SQLproperties:hibernate:format_sql: true # 格式化 SQLdatabase-platform: org.hibernate.dialect.MySQL8Dialect # 指定数据库方言(适配数据库特性)
3. 定义实体类(Entity)
通过 JPA 注解将 Java 类映射到数据库表:
java
运行
import jakarta.persistence.*;
import java.time.LocalDateTime;@Entity // 标记为 JPA 实体(对应数据库表)
@Table(name = "t_user") // 指定映射的表名(默认类名小写)
public class User {@Id // 标记为主键@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略(IDENTITY:自增 ID)private Long id;@Column(name = "username", length = 50, nullable = false, unique = true) // 映射表字段(长度、非空、唯一)private String username;@Column(name = "age")private Integer age;@Column(name = "create_time")private LocalDateTime createTime;// 省略 getter、setter、构造方法
}
核心注解说明:
@Entity
:声明类为实体类(必须)。@Table
:指定映射的表名(可选,默认类名小写)。@Id
:标记主键字段(必须)。@GeneratedValue
:指定主键生成策略(IDENTITY
:自增;SEQUENCE
:序列;AUTO
:自动选择)。@Column
:配置字段属性(名称、长度、非空、唯一等)。
4. 定义 Repository 接口
通过继承 Spring Data JPA 提供的接口,自动获得 CRUD 能力,无需编写实现类:
java
运行
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.jpa.entity.User;// 继承 JpaRepository<实体类, 主键类型>,自动获得基础 CRUD 方法
public interface UserRepository extends JpaRepository<User, Long> {// 可自定义查询方法(Spring Data JPA 自动解析方法名生成 SQL)
}
Spring Data JPA 核心接口:
Repository
:最基础接口,无任何方法。CrudRepository
:继承Repository
,提供 CRUD 方法(save
、findById
、findAll
、delete
等)。PagingAndSortingRepository
:继承CrudRepository
,增加分页和排序方法(findAll(Pageable)
、findAll(Sort)
)。JpaRepository
:继承PagingAndSortingRepository
,扩展更多 JPA 特性(如批量操作、刷新缓存等),最常用。
5. 业务层调用
在 Service 中注入
UserRepository
直接使用:java
运行
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 新增用户public User createUser(String username, Integer age) {User user = new User();user.setUsername(username);user.setAge(age);user.setCreateTime(LocalDateTime.now());return userRepository.save(user); // 调用 JpaRepository 自带的 save 方法}// 查询所有用户public List<User> getAllUsers() {return userRepository.findAll(); // 自带的查询所有方法}// 根据 ID 查询用户public User getUserById(Long id) {// findById 返回 Optional,需处理空值return userRepository.findById(id).orElseThrow(() -> new RuntimeException("用户不存在"));}
}
三、核心功能详解
1. 自动生成 CRUD 方法
继承
JpaRepository
后,无需编写实现,直接使用以下方法:save(T entity)
:新增或更新(根据主键是否为空判断)。findById(ID id)
:根据主键查询(返回Optional<T>
)。findAll()
:查询所有。deleteById(ID id)
:根据主键删除。count()
:统计总数。
2. 自定义查询方法(方法名解析)
Spring Data JPA 支持通过 “方法名规则” 自动生成 SQL,无需手动编写。规则是:
动词 + 条件(属性 + 关键字)
。常用关键字:
By
(条件)、And
/Or
(多条件)、Like
、Between
、OrderBy
、Top
等。示例:
java
运行
public interface UserRepository extends JpaRepository<User, Long> {// 1. 根据 username 查询(等价于:SELECT * FROM t_user WHERE username = ?)User findByUsername(String username);// 2. 根据 age 大于指定值查询(等价于:SELECT * FROM t_user WHERE age > ?)List<User> findByAgeGreaterThan(Integer age);// 3. 根据 username 模糊查询 + 年龄范围(等价于:SELECT * FROM t_user WHERE username LIKE %?% AND age BETWEEN ? AND ?)List<User> findByUsernameLikeAndAgeBetween(String username, Integer minAge, Integer maxAge);// 4. 查询年龄最大的 3 条数据(等价于:SELECT * FROM t_user ORDER BY age DESC LIMIT 3)List<User> findTop3ByOrderByAgeDesc();
}
3. 自定义 JPQL 或原生 SQL
复杂查询可通过
@Query
注解手动编写 JPQL(JPA 查询语言,面向对象)或原生 SQL。示例:
java
运行
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;public interface UserRepository extends JpaRepository<User, Long> {// 1. JPQL(表名用实体类名,字段用属性名)@Query("SELECT u FROM User u WHERE u.age > :age AND u.username LIKE %:name%")List<User> findByAgeAndName(@Param("age") Integer age, @Param("name") String name);// 2. 原生 SQL(需指定 nativeQuery = true,直接操作数据库表和字段)@Query(value = "SELECT * FROM t_user WHERE create_time > :time", nativeQuery = true)List<User> findByCreateTimeAfter(@Param("time") LocalDateTime time);// 3. 更新操作(需配合 @Modifying 和事务注解)@Modifying // 标记为修改操作(更新/删除)@Query("UPDATE User u SET u.age = :newAge WHERE u.id = :id")int updateAgeById(@Param("id") Long id, @Param("newAge") Integer newAge);
}
注意:更新 / 删除操作需在 Service 层添加
@Transactional
注解开启事务。4. 分页与排序
通过
Pageable
和 Sort
实现分页和排序,无需手动编写 LIMIT
或 ORDER BY
。示例:
java
运行
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 分页查询(第 1 页,每页 10 条,按 createTime 降序)public Page<User> getUsersByPage() {// 排序规则:按 createTime 降序Sort sort = Sort.by(Sort.Direction.DESC, "createTime");// 分页参数:page(从 0 开始)、size(每页条数)、排序规则Pageable pageable = PageRequest.of(0, 10, sort);return userRepository.findAll(pageable);}// 带条件的分页查询(结合自定义方法)public Page<User> getUsersByAgePage(Integer minAge, Pageable pageable) {return userRepository.findByAgeGreaterThan(minAge, pageable);}
}
5. 关联映射(多表关系)
JPA 支持通过注解定义实体间关系(一对一、一对多、多对多),自动处理表关联。
(1)一对一(如 User 与 UserDetail)
java
运行
// 主表实体(User)
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;// 一对一关联( cascade: 级联操作;fetch: 加载策略)@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)@JoinColumn(name = "detail_id", unique = true) // 外键字段(user 表的 detail_id 关联 user_detail 表的 id)private UserDetail detail;
}// 从表实体(UserDetail)
@Entity
public class UserDetail {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String address;private String phone;// 反向关联(可选)@OneToOne(mappedBy = "detail") // mappedBy 指向主表中关联属性名private User user;
}
cascade = CascadeType.ALL
:操作 User 时自动级联操作 UserDetail(如保存 User 时自动保存 Detail)。fetch = FetchType.LAZY
:延迟加载(查询 User 时不主动查询 Detail,用到时才查)。
(2)一对多(如 User 与 Order)
java
运行
// 一的一方(User)
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;// 一对多关联(一个用户多个订单)@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)private List<Order> orders = new ArrayList<>();
}// 多的一方(Order)
@Entity
public class Order {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String orderNo;// 多对一关联(多个订单属于一个用户)@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "user_id") // 外键字段(order 表的 user_id 关联 user 表的 id)private User user;
}
mappedBy = "user"
:指定关联由 Order 类的user
属性维护(避免双向维护外键)。orphanRemoval = true
:当从 orders 集合中移除某个 Order 时,自动删除该 Order 记录。
6. 事务管理
通过
@Transactional
注解管理事务(Spring 提供,与 JPA 集成):java
运行
import org.springframework.transaction.annotation.Transactional;@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 声明事务(默认:发生异常时回滚)@Transactionalpublic void batchCreate(List<User> users) {userRepository.saveAll(users);// 若中途抛出异常,所有保存操作会回滚}// 自定义事务属性(如只读、超时时间)@Transactional(readOnly = true, timeout = 10) // 只读事务,超时 10 秒public List<User> getUsersWithLock() {// 复杂查询逻辑return userRepository.findAll();}
}
四、缓存机制
JPA 自带两级缓存,提升查询性能:
- 一级缓存:默认开启,作用于
EntityManager
生命周期(同一事务内多次查询同一数据,只查一次数据库)。 - 二级缓存:全局缓存(跨事务共享),需手动开启(通过
@Cacheable
注解)。
开启二级缓存(以 Hibernate 实现为例):
yaml
spring:jpa:properties:hibernate:cache:use_second_level_cache: true # 开启二级缓存region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory # 指定缓存实现(如 EhCache)
在实体类上添加缓存注解:
java
运行
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // 二级缓存策略(读写)
public class User { ... }
五、优缺点与适用场景
优点:
- 简化代码:无需编写 DAO 实现类,通过接口方法签名自动生成 SQL。
- 面向对象:通过实体类和关联映射操作数据,屏蔽 SQL 细节。
- 快速开发:适合简单 CRUD 场景,大幅减少重复代码。
- 跨数据库兼容:通过方言适配不同数据库(如 MySQL、PostgreSQL),切换数据库无需修改 SQL。
缺点:
- 复杂查询灵活性低:自动生成的 SQL 可能不优,复杂查询需手动编写 JPQL 或原生 SQL。
- 性能优化成本高:默认查询可能存在冗余(如查询所有字段),需额外配置(如投影查询)。
- 学习曲线:关联映射、缓存机制等概念较复杂,新手需理解 ORM 核心思想。
适用场景:
- 简单 CRUD 为主的业务(如后台管理系统)。
- 快速开发原型或中小型项目。
- 团队更关注对象模型而非 SQL 细节。
- 需要跨数据库兼容的场景。
总结
Spring Boot JPA 是简化数据访问层的优秀方案,通过 “接口定义 + 注解配置” 实现无代码 DAO,适合快速开发简单业务。但在复杂查询或性能敏感场景,需结合 JPQL、原生 SQL 或缓存机制优化。实际开发中,需根据业务复杂度平衡开发效率与性能需求。
-------------------------------------------------------------------------------------------
Spring Boot JPA 基于 JPA 规范(Java Persistence API),通过注解实现 实体类与数据库表的映射、查询逻辑定义 等核心功能。掌握这些注解是使用 JPA 进行 ORM 开发的基础。以下是 Spring Boot JPA 中常用注解的详细说明,按功能分类整理:
一、实体与表映射注解(核心基础)
用于将 Java 实体类与数据库表建立映射关系,是 JPA 最基础的注解。
1. @Entity
- 作用:标记一个类为 JPA 实体类(即该类对应数据库中的一张表)。
- 位置:类级别(类定义上方)。
- 注意:
- 实体类必须有一个无参构造方法(JPA 反射需要)。
- 若未指定表名,默认表名与类名一致(小写)。
java
运行
@Entity // 标记为实体类,对应数据库表
public class User { ... }
2. @Table
- 作用:自定义实体类映射的数据库表名、 schema 等信息(补充
@Entity
的表映射细节)。 - 核心属性:
name
:指定数据库表名(默认类名小写)。schema
:指定数据库 schema(如 MySQL 的数据库名,Oracle 的方案名)。uniqueConstraints
:定义表级唯一约束(多个字段组合唯一)。
- 位置:类级别,与
@Entity
配合使用。
java
运行
@Entity
@Table(name = "t_user", // 映射到数据库表 t_userschema = "jpa_demo", // 所属数据库(schema)uniqueConstraints = { // 表级唯一约束:username 和 phone 组合唯一@UniqueConstraint(columnNames = {"username", "phone"})}
)
public class User { ... }
二、字段与列映射注解(属性级)
用于将实体类的属性与数据库表的字段建立映射,控制字段的类型、约束等。
1. @Id
- 作用:标记实体类的属性为 主键字段(对应数据库表的主键)。
- 位置:属性级别(或 getter 方法上,推荐属性级)。
- 注意:每个实体类必须有且仅有一个
@Id
标记的主键。
java
运行
@Entity
public class User {@Id // 标记为主键private Long id;// 其他属性...
}
2. @GeneratedValue
- 作用:指定主键的 生成策略(如何自动生成主键值)。
- 核心属性:
strategy
:生成策略(必选),取值包括:GenerationType.IDENTITY
:依赖数据库自增主键(如 MySQL 的AUTO_INCREMENT
),最常用。GenerationType.SEQUENCE
:依赖数据库序列(如 Oracle 的SEQUENCE
),需配合@SequenceGenerator
使用。GenerationType.TABLE
:通过中间表维护主键生成(跨数据库兼容,但性能较差,很少用)。GenerationType.AUTO
:由 JPA 自动选择策略(默认值,不推荐,可能导致跨环境不一致)。
- 位置:主键属性上,与
@Id
配合使用。
java
运行
@Entity
public class User {@Id// 主键自增(适合 MySQL)@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;
}
SEQUENCE 策略示例(适合 Oracle):
java
运行
@Entity
public class User {@Id// 使用序列生成主键@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "user_seq" // 关联序列生成器)// 定义序列生成器@SequenceGenerator(name = "user_seq", // 生成器名称(与 generator 对应)sequenceName = "SEQ_USER_ID", // 数据库中序列名allocationSize = 1 // 每次增长步长)private Long id;
}
3. @Column
- 作用:自定义属性映射的数据库字段细节(如字段名、类型、约束等)。
- 核心属性:
name
:数据库字段名(默认与属性名一致)。nullable
:是否允许为 null(默认true
)。unique
:是否唯一约束(默认false
)。length
:字符串长度(默认 255,仅对字符串类型有效)。precision
/scale
:用于数值类型(precision
总长度,scale
小数位数,如DECIMAL(10,2)
)。columnDefinition
:直接指定字段的数据库定义(如VARCHAR(50) NOT NULL COMMENT '用户名'
),优先级最高。updatable
:是否允许更新(默认true
,设为false
则 update 时忽略该字段)。insertable
:是否允许插入(默认true
,设为false
则 insert 时忽略该字段)。
- 位置:属性级别。
java
运行
@Entity
@Table(name = "t_user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;// 映射到字段 username,非空,唯一,长度 50@Column(name = "username", // 字段名nullable = false, // 非空unique = true, // 唯一length = 50 // 长度 50)private String username;// 年龄字段:允许为 null,整数类型@Column(name = "age")private Integer age;// 余额字段:DECIMAL(10,2)(总长度 10,2 位小数)@Column(name = "balance",precision = 10,scale = 2)private BigDecimal balance;// 自定义字段定义(直接写 SQL 片段)@Column(name = "create_time",columnDefinition = "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")private LocalDateTime createTime;
}
4. @Transient
- 作用:标记属性 不映射到数据库表(即该属性仅在 Java 代码中使用,与数据库无关)。
- 位置:属性级别。
- 注意:若未加此注解,JPA 会默认将所有属性映射到数据库字段(可能导致表结构异常)。
java
运行
@Entity
public class User {@Idprivate Long id;private String username;// 临时属性:不映射到数据库(如用于计算的临时结果)@Transientprivate String tempInfo;
}
5. @Temporal
(JPA 2.2 前,推荐用 Java 8 时间类)
- 作用:在使用
java.util.Date
或java.util.Calendar
时,指定日期时间在数据库中的存储类型(DATE/Time/TIMESTAMP)。 - 替代方案:Java 8 及以上推荐使用
LocalDate
(日期)、LocalTime
(时间)、LocalDateTime
(日期时间),无需@Temporal
,JPA 会自动映射。 - 示例:
java
运行
// 旧方式(不推荐)
@Temporal(TemporalType.DATE) // 仅存储日期(如 '2024-09-30')
private Date birthday;@Temporal(TemporalType.TIMESTAMP) // 存储日期+时间(如 '2024-09-30 12:00:00')
private Date createTime;// 新方式(推荐)
private LocalDate birthday; // 自动映射为 DATE
private LocalDateTime createTime; // 自动映射为 TIMESTAMP
6. @CreationTimestamp
与 @UpdateTimestamp
(Hibernate 扩展,非 JPA 标准)
- 作用:自动填充创建时间和更新时间(插入时自动设为当前时间,更新时自动刷新)。
- 注意:这是 Hibernate 的扩展注解(非 JPA 标准),但 Spring Boot JPA 默认集成 Hibernate,可直接使用。
- 示例:
java
运行
@Entity
public class User {// 插入时自动设置为当前时间(不随更新变化)@CreationTimestamp@Column(name = "create_time", updatable = false) // 禁止更新private LocalDateTime createTime;// 插入和更新时自动设置为当前时间@UpdateTimestamp@Column(name = "update_time")private LocalDateTime updateTime;
}
三、关联映射注解(多表关系)
用于定义实体类之间的关系(如一对一、一对多、多对多),对应数据库表的外键关联。
1. @OneToOne
(一对一关系)
- 作用:定义两个实体之间的 一对一关联(如
User
与UserDetail
)。 - 核心属性:
cascade
:级联操作策略(如保存用户时自动保存详情),取值包括:CascadeType.ALL
:所有操作级联(推荐)。CascadeType.PERSIST
:级联保存。CascadeType.MERGE
:级联更新。CascadeType.REMOVE
:级联删除。
fetch
:加载策略(默认FetchType.EAGER
立即加载,推荐FetchType.LAZY
延迟加载)。mappedBy
:指定关联关系的维护方(在 “被控方” 中使用,值为 “主控方” 中关联属性的名称)。@JoinColumn
:在 “主控方” 中指定外键字段(与@OneToOne
配合使用)。
示例:用户(User)与用户详情(UserDetail)一对一
java
运行
// 主控方(User):维护外键 detail_id
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;// 一对一关联 UserDetail,主控方@OneToOne(cascade = CascadeType.ALL, // 级联所有操作(保存用户时自动保存详情)fetch = FetchType.LAZY // 延迟加载(查询用户时不主动加载详情))@JoinColumn(name = "detail_id", // 外键字段名(user 表的 detail_id 关联 user_detail 表的 id)unique = true // 一对一必须唯一(一个详情只能属于一个用户))private UserDetail detail;
}// 被控方(UserDetail):不维护外键,由 User 维护
@Entity
public class UserDetail {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String address;private String phone;// 反向关联(可选),mappedBy 指向主控方的关联属性名(User 类的 detail)@OneToOne(mappedBy = "detail")private User user;
}
2. @OneToMany
与 @ManyToOne
(一对多 / 多对一关系)
- 作用:定义两个实体之间的 一对多 / 多对一关联(如
User
与Order
:一个用户多个订单,多个订单属于一个用户)。 - 核心属性:
@OneToMany
用在 “一” 的一方,@ManyToOne
用在 “多” 的一方。mappedBy
:在@OneToMany
中指定,值为 “多” 的一方中关联属性的名称(表示由 “多” 的一方维护外键)。cascade
和fetch
:同@OneToOne
(@ManyToOne
默认FetchType.EAGER
,@OneToMany
默认FetchType.LAZY
)。
示例:用户(User)与订单(Order)一对多
java
运行
// “一”的一方(User)
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;// 一对多关联订单,由 Order 类的 user 属性维护外键@OneToMany(mappedBy = "user", // 关联由 Order.user 维护cascade = CascadeType.ALL, // 级联操作(保存用户时保存订单)orphanRemoval = true // 移除集合中的订单时,自动删除数据库记录)private List<Order> orders = new ArrayList<>(); // 初始化避免空指针
}// “多”的一方(Order)
@Entity
public class Order {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String orderNo;// 多对一关联用户(维护外键)@ManyToOne(fetch = FetchType.LAZY) // 延迟加载(查询订单时不主动加载用户)@JoinColumn(name = "user_id") // 外键字段(order 表的 user_id 关联 user 表的 id)private User user;
}
3. @ManyToMany
(多对多关系)
- 作用:定义两个实体之间的 多对多关联(如
User
与Role
:一个用户多个角色,一个角色多个用户)。 - 核心属性:
joinTable
:在 “主控方” 中定义中间表(多对多通过中间表实现)。mappedBy
:在 “被控方” 中指定,值为 “主控方” 中关联属性的名称。
- 注意:多对多关系必须通过中间表关联,中间表的两个外键分别指向两个实体的主键。
示例:用户(User)与角色(Role)多对多
java
运行
// 主控方(User):定义中间表
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;@ManyToMany(cascade = CascadeType.PERSIST, // 级联保存(保存用户时保存角色,但不级联删除)fetch = FetchType.LAZY)// 定义中间表 user_role@JoinTable(name = "user_role", // 中间表名joinColumns = @JoinColumn(name = "user_id"), // 中间表关联当前实体的外键(user_id → user.id)inverseJoinColumns = @JoinColumn(name = "role_id") // 中间表关联关联实体的外键(role_id → role.id))private Set<Role> roles = new HashSet<>(); // 用 Set 避免重复
}// 被控方(Role):不定义中间表,由 User 维护
@Entity
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String roleName;// 反向关联,mappedBy 指向 User 类的 roles 属性@ManyToMany(mappedBy = "roles")private Set<User> users = new HashSet<>();
}
四、查询与操作注解(Repository 层)
用于在 Repository 接口中自定义查询逻辑(替代 SQL)。
1. @Query
- 作用:在 Repository 方法上定义自定义查询(支持 JPQL 或原生 SQL)。
- 核心属性:
value
:查询语句(JPQL 或 SQL)。nativeQuery
:是否为原生 SQL(默认false
,即 JPQL;设为true
则使用数据库原生 SQL)。countQuery
:分页查询时的总数查询语句(可选)。
- 参数绑定:通过
@Param
注解指定参数名,与查询语句中的:参数名
对应。
示例 1:JPQL 查询(面向实体,非表)
java
运行
public interface UserRepository extends JpaRepository<User, Long> {// JPQL:查询年龄大于指定值的用户(表名用实体类名 User,字段用属性名)@Query("SELECT u FROM User u WHERE u.age > :minAge")List<User> findByAgeGreaterThan(@Param("minAge") Integer minAge);
}
示例 2:原生 SQL 查询(面向数据库表)
java
运行
public interface UserRepository extends JpaRepository<User, Long> {// 原生 SQL:查询指定日期后创建的用户(表名用实际表名 t_user,字段用数据库字段名)@Query(value = "SELECT * FROM t_user WHERE create_time > :startTime",nativeQuery = true // 标记为原生 SQL)List<User> findByCreateTimeAfter(@Param("startTime") LocalDateTime startTime);
}
2. @Modifying
- 作用:标记
@Query
注解的方法为 修改操作(UPDATE/DELETE),而非查询。 - 注意:
- 必须与
@Query
配合使用。 - 执行修改操作的方法必须在事务中运行(需在 Service 层添加
@Transactional
注解)。
- 必须与
java
运行
public interface UserRepository extends JpaRepository<User, Long> {// 更新用户年龄@Modifying // 标记为修改操作@Query("UPDATE User u SET u.age = :newAge WHERE u.id = :id")int updateAgeById(@Param("id") Long id, @Param("newAge") Integer newAge);// 删除指定年龄以下的用户@Modifying@Query("DELETE FROM User u WHERE u.age < :maxAge")int deleteByAgeLessThan(@Param("maxAge") Integer maxAge);
}// Service 层需添加事务注解
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactional // 必须开启事务public void updateUserAge(Long id, Integer newAge) {userRepository.updateAgeById(id, newAge);}
}
3. @Param
- 作用:在 Repository 方法参数前标记参数名,便于在
@Query
语句中通过:参数名
引用。 - 位置:方法参数级别。
- 注意:若方法参数只有一个且为基本类型,可省略
@Param
,但多参数时必须添加(避免混淆)。
java
运行
// 多参数必须用 @Param 标记
@Query("SELECT u FROM User u WHERE u.username LIKE %:name% AND u.age > :age")
List<User> findByUsernameLikeAndAgeGreaterThan(@Param("name") String username, @Param("age") Integer age
);
五、其他常用注解
1. @Repository
- 作用:标记接口为数据访问层(DAO)组件,Spring 会自动扫描并注入该接口的实现(JPA 自动生成代理实现)。
- 注意:Spring Data JPA 的 Repository 接口继承
JpaRepository
后,可省略@Repository
(Spring 会自动识别),但显式添加更清晰。
java
运行
@Repository // 标记为数据访问组件
public interface UserRepository extends JpaRepository<User, Long> { ... }
2. @Transactional
(Spring 注解,非 JPA 标准)
- 作用:声明方法或类需要在事务中运行(如新增、更新、删除操作),确保数据一致性。
- 核心属性:
readOnly = true
:声明为只读事务(查询操作,可优化性能)。rollbackFor
:指定哪些异常触发回滚(默认 RuntimeException 及其子类)。propagation
:事务传播行为(如REQUIRED
、REQUIRES_NEW
等)。
- 位置:Service 层的方法或类级别(推荐方法级,更灵活)。
java
运行
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 只读事务(查询操作)@Transactional(readOnly = true)public User getUserById(Long id) {return userRepository.findById(id).orElse(null);}// 读写事务(更新操作)@Transactional(rollbackFor = Exception.class) // 所有异常都回滚public void updateUser(User user) {userRepository.save(user);}
}
总结
Spring Boot JPA 的注解是实现 ORM 映射的核心,按功能可分为:
- 实体映射:
@Entity
、@Table
定义类与表的关系; - 字段映射:
@Id
、@GeneratedValue
、@Column
定义属性与字段的关系; - 关联映射:
@OneToOne
、@OneToMany
、@ManyToMany
定义多表关系; - 查询操作:
@Query
、@Modifying
、@Param
定义自定义查询逻辑。
掌握这些注解的使用场景和属性,能帮助开发者高效实现数据访问层,减少重复代码,同时灵活处理复杂业务场景(如多表关联、自定义查询)。实际开发中,需根据数据库类型(如 MySQL/Oracle)和业务需求选择合适的注解配置。
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------