jpa 注解大全

在Java Persistence API (JPA) 中,注解是用来定义实体类及其映射到数据库表的方式。这些注解位于javax.persistence包中,并被广泛应用于实体类、字段、关联以及其他JPA配置中。下面是一些主要的JPA注解及其用途:

1. 实体类注解

  • @Entity:标记一个持久的实体类。

  • @Table:指定实体类对应的数据库表名。

  • @Id:标记一个字段作为主键。

  • @GeneratedValue:指定主键的生成策略。

  • @SequenceGenerator / @TableGenerator:用于指定序列生成器,通常与@GeneratedValue配合使用。

2. 字段映射注解

  • @Column:用于指定列的详细信息,如列名、是否可为null等。

  • @Temporal:用于指定日期或时间类型的精度(如DATE、TIME、TIMESTAMP)。

  • @Enumerated:用于将枚举类型映射到数据库。

  • @Lob:用于大对象(如CLOB、BLOB)。

  • @Basic:标记一个字段为基本字段,可以定义字段的访问方式等。

3. 关联关系注解

  • @OneToOne:定义一对一关系。

  • @OneToMany / @ManyToOne:定义一对多关系。

  • @ManyToMany:定义多对多关系。

  • @JoinColumn / @JoinTable:用于指定关联关系的连接列或表。

  • @ElementCollection:用于集合类型字段的映射,如List或Set。

  • @MapKey:当使用Map集合时,指定Map的键。

4. 继承策略注解

  • @Inheritance:指定实体类的继承策略(如SINGLE_TABLE, JOINED, TABLE_PER_CLASS)。

  • @DiscriminatorColumn / @DiscriminatorValue:用于在继承结构中区分不同的子类。

5. 其他注解

  • @NamedQuery / @NamedQueries:定义命名的查询。

  • @NamedNativeQuery / @NamedNativeQueries:定义命名的本地SQL查询。

  • @EntityListeners:指定实体监听器类。

  • @PrePersist@PreRemove@PreUpdate@PostPersist@PostRemove@PostUpdate@PostLoad:定义实体生命周期回调方法。

  • @Transient:标记一个字段不是数据库持久化属性。

  • @Access:指定实体或字段的访问类型(如FIELD, PROPERTY)。

  • @PrimaryKeyJoinColumn:在继承策略为JOINED时,指定主键的连接列。

  • @SecondaryTable:指定除主表外的额外表来存储属性。

  • @Embeddable:标记一个类为可嵌入的(Embeddable)类,通常用于值对象。

  • @Embedded:将一个Embeddable类的实例嵌入到另一个实体类中。

 

 ------------------------------------------------------

 

实体相关注解

1. 基本实体注解

注解说明
@Entity 标记类为JPA实体
@Table(name="表名") 指定实体对应的表名
@MappedSuperclass 标识为父类,不会映射为单独表
@Embeddable 标识可嵌入类(值对象)
@Embedded 引用嵌入类

2. 主键注解

注解说明
@Id 标识主键字段
@GeneratedValue 主键生成策略
@SequenceGenerator 序列生成器配置
@TableGenerator 表生成器配置
@IdClass 复合主键类

3. 字段映射

注解说明
@Column 字段映射配置
@Enumerated 枚举类型映射
@Temporal 日期时间类型映射
@Lob 大对象映射
@Transient 非持久化字段

关联关系注解

1. 基本关联

注解说明
@OneToOne 一对一关联
@OneToMany 一对多关联
@ManyToOne 多对一关联
@ManyToMany 多对多关联
@JoinColumn 外键列配置
@JoinTable 关联表配置

2. 级联与加载

注解说明
@OrderBy 集合排序
@OrderColumn 维护列表顺序
@Cascade 级联操作类型
@Fetch 加载策略

查询相关注解

1. 查询方法

注解说明
@NamedQuery 命名JPQL查询
@NamedNativeQuery 命名原生SQL查询
@Query Spring Data JPA自定义查询
@Procedure 存储过程映射

2. 查询参数

注解说明
@Param 命名参数绑定
@Modifying 标识修改查询

继承策略注解

注解说明
@Inheritance 继承映射策略
@DiscriminatorColumn 鉴别器列
@DiscriminatorValue 实体鉴别值

缓存相关注解

注解说明
@Cacheable 二级缓存配置
@Cache 更细粒度的缓存配置

验证注解

注解说明
@NotNull 非空校验
@Size 大小范围校验
@Email 邮箱格式校验
@Pattern 正则校验

Spring Data JPA特有注解

注解说明
@EntityGraph 实体图加载配置
@CreatedDate 自动设置创建时间
@LastModifiedDate 自动设置修改时间
@CreatedBy 自动设置创建人
@LastModifiedBy 自动设置最后修改人

完整示例

java
复制
@Entity
@Table(name = "employees")
@NamedQuery(name = "Employee.findByStatus", 
           query = "SELECT e FROM Employee e WHERE e.status = :status")
@Cacheable
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "emp_name", nullable = false, length = 100)
    private String name;
    
    @Enumerated(EnumType.STRING)
    private Gender gender;
    
    @Temporal(TemporalType.DATE)
    private Date birthDate;
    
    @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy("startDate DESC")
    private List<Address> addresses = new ArrayList<>();
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    private Department department;
    
    @ManyToMany
    @JoinTable(name = "emp_projects",
             joinColumns = @JoinColumn(name = "emp_id"),
             inverseJoinColumns = @JoinColumn(name = "project_id"))
    private Set<Project> projects = new HashSet<>();
    
    @CreatedDate
    private LocalDateTime createTime;
    
    @LastModifiedDate
    private LocalDateTime updateTime;
    
    // getters and setters
}

@Embeddable
public class Address {
    private String street;
    private String city;
    private String postalCode;
    // getters and setters
}

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    
    @Query("SELECT e FROM Employee e WHERE e.department.name = :deptName")
    List<Employee> findByDepartmentName(@Param("deptName") String deptName);
    
    @EntityGraph(attributePaths = {"department", "projects"})
    List<Employee> findByNameContaining(String name);
}

使用建议

  1. 根据业务需求选择合适的关联关系和加载策略

  2. 复杂查询优先使用@Query而非方法名推导

  3. 注意@OneToMany@ManyToMany的性能影响

  4. 实体应实现equals()hashCode()方法

  5. 考虑使用审计注解自动维护创建/修改信息

这些注解覆盖了JPA和Spring Data JPA的大部分功能,合理组合使用可以满足绝大多数持久化需求。

----------------------------------------------------------

一、java对象与数据库字段转化

1.@Entity:标识实体类是JPA实体,告诉JPA在程序运行时生成实体类对应表

2.@Table:设置实体类在数据库所对应的表名

3.@Id:标识类里所在变量为主键

4.@GeneratedValue:设置主键生成策略,此方式依赖于具体的数据库

5.@Basic:表示简单属性到数据库表字段的映射(几乎不用)

6.@Column:表示属性所对应字段名进行个性化设置

7.@Transient:表示属性并非数据库表字段的映射,ORM框架将忽略该属性

8.@Temporal:(很重要)

  当我们使用到java.util包中的时间日期类型,则需要此注释来说明转化成java.util包中的类型。

  注入数据库的类型有三种:

    TemporalType.DATE(2008-08-08)

    TemporalType.TIME(20:00:00)

    TemporalType.TIMESTAMP(2008-08-08 20:00:00.000000001)

9.@Enumerated:(很重要)

  使用此注解映射枚举字段,以String类型存入数据库

  注入数据库的类型有两种:EnumType.ORDINAL(Interger)、EnumType.STRING(String)

10.@Embedded@Embeddable

  当一个实体类要在多个不同的实体类中进行使用,而其不需要生成数据库表

  @Embeddable:注解在类上,表示此类是可以被其他类嵌套

  @Embedded:注解在属性上,表示嵌套被@Embeddable注解的同类型类

11.@ElementCollection:集合映射

12.@CreatedDate@CreatedBy@LastModifiedDate@LastModifiedBy:(很重要)

  表示字段为创建时间字段(insert自动设置)、创建用户字段(insert自动设置)、最后修改时间字段(update自定设置)、最后修改用户字段(update自定设置)

  用法:

    1、@EntityListeners(AuditingEntityListener.class):申明实体类并加注解

    2、@EnableJpaAuditing:在启动类中加此注解

    3、在实体类中属性中加上面四种注解

    4、自定义添加用户

import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

@Configuration
public class UserIDAuditorBean implements AuditorAware<Long> {
@Override
public Long getCurrentAuditor() {
SecurityContext ctx = SecurityContextHolder.getContext();
if (ctx == null) {
return null;
}
if (ctx.getAuthentication() == null) {
return null;
}
if (ctx.getAuthentication().getPrincipal() == null) {
return null;
}
Object principal = ctx.getAuthentication().getPrincipal();
if (principal.getClass().isAssignableFrom(Long.class)) {
return (Long) principal;
} else {
return null;
}
}
}

13.@MappedSuperclass:(很重要)

  实现将实体类的多个属性分别封装到不同的非实体类中

  注解的类将不是完整的实体类,不会映射到数据库表,但其属性将映射到子类的数据库字段
  注解的类不能再标注@Entity或@Table注解,也无需实现序列化接口

  注解的类继承另一个实体类 或 标注@MappedSuperclass类,他可使用@AttributeOverride 或 @AttributeOverrides注解重定义其父类属性映射到数据库表中字段。

 

 


二、java对象与json转化

1.@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8"):将Date属性转换为String类型, timezone解决(相差8小时)

 

2.@JsonSerialize:作用在类或字段上,转化java对象到json格式(需自定义转化类继承JsonSerializer<T>)

class DateSerializer extends JsonSerializer<Date> {
@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
        </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException {
    SimpleDateFormat formatter </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> SimpleDateFormat(BankAccount.DATE_PATTERN);
    jgen.writeString(formatter.format(value));
}

}

3.@JsonDeserialize:作用在类或字段上,转化json格式到java对象(需自定义转化类继承JsonDeserializer<T>)

 
class DateDeSerializer extends JsonDeserializer<Date> {
@Override
</span><span style="color: #0000ff;">public</span> Date deserialize(JsonParser jp, DeserializationContext ctxt) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException {
    Date date;
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        date </span>=<span style="color: #000000;"> DateUtils.parseDate(jp.getText(), BankAccount.DATE_PATTERN);
    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
        </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> date;
}

}

4.@JsonProperty:作用在属性上,把属性名称序列化为另一个名称(trueName属性序列化为name)

5.@JsonIgnoreProperties(ignoreUnknown = true):作用在类上,忽略掉json数据里包含了实体类没有的字段

6.@JsonIgnore:在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响

 

--------------------------------------------------------------

Spring JPA 提供了一系列注解,用于简化数据库操作和实现ORM功能。以下是Spring JPA中一些核心注解的引用:

  • @Entity: 标识类为JPA实体,映射到数据库表。
  • @Table: 指定实体对应的数据库表名。
  • @Id: 标记实体的主键字段。
  • @GeneratedValue: 定义主键的生成策略。
  • @Column: 定义实体属性与数据库列的映射。
  • @Basic: 描述基本的持久属性。
  • @ManyToOne@OneToMany@OneToOne@ManyToMany: 描述实体间的关联关系。
  • @JoinColumn: 用于关联字段的外键映射。
  • @Transient: 标识不持久化的属性。
  • @Embeddable: 标记可嵌入的属性或对象。
  • @Embedded: 将一个类作为另一个类的属性嵌入。
  • @Cacheable: 指定方法结果是可缓存的。
  • @Transactional: 声明事务性方法。 这些注解共同构成了Spring Data JPA的基础,提供了一种声明式的方式来处理数据持久化,极大地提高了开发效率。

1. 实体定义

@Entity

  • 注解作用介绍

@Entity 标记类作为JPA实体,表明此类可以映射到数据库的一张表上。

  • 注解属性介绍
    • name: 指定实体的名称,若未指定,默认使用类名。
  • 注解业务案例
@Entity(name = "user_entity")
public class User {
 // 类成员
} 

@Table

  • 注解作用介绍

@Table 用于指定实体类对应的数据库表名。

  • 注解属性介绍
    • name: 指定表名。
    • schema: 指定数据库的schema。
    • catalog: 指定数据库的catalog。
  • 注解业务案例
@Entity
@Table(name = "users", schema = "my_schema", catalog = "my_catalog")
public class User {
 // 类成员
} 

2. 主键定义

@Id

  • 注解作用介绍

@Id 标记实体类中的属性作为主键。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @Id
 private Long id;
 // 其他成员
} 

@GeneratedValue

  • 注解作用介绍

@GeneratedValue 指定主键的生成策略。

  • 注解属性介绍
    • strategy: 指定生成策略,如 GenerationType.IDENTITYGenerationType.AUTO 等。
  • 注解业务案例
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; 

3. 属性映射

@Column

  • 注解作用介绍

@Column 用于指定实体属性与数据库表列的映射关系。

  • 注解属性介绍
    • name: 指定数据库表的列名。
    • nullable: 指定列是否允许为空。
  • 注解业务案例
public class User {
 @Column(name = "username", nullable = false)
 private String username;
} 

@Enumerated

  • 注解作用介绍

@Enumerated 指定枚举类型的存储方式。

  • 注解属性介绍
    • value: 指定枚举的存储类型,如 EnumType.ORDINAL 或 EnumType.STRING
  • 注解业务案例
public enum Status {
    ACTIVE, INACTIVE
}

public class User {
 @Column(nullable = false)
 @Enumerated(EnumType.STRING)
 private Status status;
} 

4. 嵌入类型

@Embedded

  • 注解作用介绍

@Embedded 用于将一个类的属性嵌入到另一个实体类中。

  • 注解属性介绍
    • id: 指定嵌入类的ID。
  • 注解业务案例
@Embeddable 
public class Address {
 private String street;
 private String city;
}

public class User {
 @Embedded
 private Address address;
} 

5. 关联关系映射

@ManyToOne

  • 注解作用介绍

@ManyToOne 用于标记一个实体与另一个实体存在“多对一”的关联关系。

  • 注解属性介绍
    • targetEntity: 指定关联实体的类型。
    • fetch: 指定加载策略,如 FetchType.LAZY 或 FetchType.EAGER
    • optional: 指定是否允许为空,默认为 true
  • 注解业务案例
@Entity
public class OrderItem {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "order_id")
 private Order order;

 // 其他属性和方法
} 

@OneToMany

  • 注解作用介绍

@OneToMany 用于标记一个实体与另一个实体存在“一对多”的关联关系。

  • 注解属性介绍
    • targetEntity: 指定关联实体的类型。
    • mappedBy: 指定对方实体中用来映射当前实体的属性名。
    • cascade: 指定级联操作,如 CascadeType.ALL
  • 注解业务案例
@Entity
public class Order {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
 private List<OrderItem> items = new ArrayList<>();

 // 其他属性和方法
} 

@OneToOne

  • 注解作用介绍

@OneToOne 用于标记一个实体与另一个实体存在“一对一”的关联关系。

  • 注解属性介绍
    • targetEntity: 指定关联实体的类型。
    • mappedBy: 指定对方实体中用来映射当前实体的属性名。
    • cascade: 指定级联操作。
  • 注解业务案例
@Entity
public class UserDetails {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @OneToOne(mappedBy = "details", cascade = CascadeType.ALL)
 private User user;

 // 其他属性和方法
}

@Entity
public class User {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @OneToOne(cascade = CascadeType.ALL)
 @JoinColumn(name = "user_details_id")
 private UserDetails details;

 // 其他属性和方法
} 

@ManyToMany

  • 注解作用介绍

@ManyToMany 用于标记一个实体与另一个实体存在“多对多”的关联关系。

  • 注解属性介绍
    • targetEntity: 指定关联实体的类型。
    • mappedBy: 指定对方实体中用来映射当前实体的属性名。
    • cascade: 指定级联操作。
    • fetch: 指定加载策略。
  • 注解业务案例
@Entity
public class User {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 @JoinTable(name = "user_roles",
               joinColumns = @JoinColumn(name = "user_id"),
               inverseJoinColumns = @JoinColumn(name = "role_id"))
 private Set<Role> roles = new HashSet<>();

 // 其他属性和方法
}

@Entity
public class Role {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
 private Set<User> users = new HashSet<>();

 // 其他属性和方法
} 

@JoinColumn

  • 注解作用介绍

@JoinColumn 用于指定关联关系中的外键列名。

  • 注解属性介绍
    • name: 指定外键列的名称。
    • referencedColumnName: 指定引用的主键列的名称。
  • 注解业务案例
public class OrderItem {
 @ManyToOne
 @JoinColumn(name = "order_id", referencedColumnName = "id")
 private Order order;
} 

6. 生命周期和行为

@Basic

  • 注解作用介绍

@Basic 标记基本属性,可以指定加载策略等。

  • 注解属性介绍
    • fetch: 指定加载策略,如 FetchType.LAZY 或 FetchType.EAGER
  • 注解业务案例
public class User {
 @Basic(fetch = FetchType.LAZY)
 private String description;
} 

@Transient

  • 注解作用介绍

@Transient 标记不映射到数据库表的属性。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @Transient
 private String temporaryField;
} 

7. 主键生成策略

@SequenceGenerator

  • 注解作用介绍

@SequenceGenerator 用于指定一个主键生成策略,基于数据库序列。

  • 注解属性介绍
    • name: 唯一标识主键生成器的名称。
    • sequenceName: 数据库中序列的名称。
    • initialValue: 生成主键的起始值。
    • allocationSize: 每次从数据库序列中预分配的ID数量。
  • 注解业务案例
@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "user_sequence_generator"
)
@SequenceGenerator(
    name = "user_sequence_generator",
    sequenceName = "user_sequence",
    initialValue = 1,
    allocationSize = 1
)
private Long id; 

@TableGenerator

  • 注解作用介绍

@TableGenerator 用于指定一个主键生成策略,基于数据库表。

  • 注解属性介绍
    • name: 唯一标识主键生成器的名称。
    • table: 存储主键值的表名。
    • pkColumnValue: 表中用于跟踪高水位的列值。
    • valueColumn: 存储生成的主键值的列名。
    • allocationSize: 每次从表中预分配的ID数量。
  • 注解业务案例
@Id
@GeneratedValue(
    strategy = GenerationType.TABLE,
    generator = "user_table_generator"
)
@TableGenerator(
    name = "user_table_generator",
    table = "sequence_table",
    pkColumnValue = "user",
    valueColumn = "sequence_value",
    allocationSize = 10
)
private Long id; 

8. 查询和缓存

@NamedQuery

  • 注解作用介绍

@NamedQuery 用于定义一个命名查询,可以是HQL、JPQL或SQL查询。

  • 注解属性介绍
    • name: 查询的名称,用于在应用程序中引用。
    • query: 查询的字符串。
  • 注解业务案例
@NamedQuery(
    name = "User.findAllActive",
    query = "SELECT u FROM User u WHERE u.status = 'ACTIVE'"
)
public class User {
 // 类成员
} 

@NamedQueries

  • 注解作用介绍

@NamedQueries 用于定义多个命名查询的集合。

  • 注解属性介绍
    • 包含多个 @NamedQuery 注解。
  • 注解业务案例
@NamedQueries({
 @NamedQuery(
        name = "User.findAll",
        query = "SELECT u FROM User u"
 ),
 @NamedQuery(
        name = "User.findActive",
        query = "SELECT u FROM User u WHERE u.status = 'ACTIVE'"
 )
})
public class User {
 // 类成员
} 

@Cacheable

  • 注解作用介绍

@Cacheable 用于指定一个方法的结果是可缓存的。

  • 注解属性介绍
    • value: 缓存名称,用于存储结果。
    • condition: 缓存条件,使用SpEL表达式。
  • 注解业务案例
@Cacheable(value = "userList", condition = "#root.args[0] == 1")
public List<User> getUsersByStatus(Integer status) {
 // 查询逻辑
} 

9. 事务管理

@Transactional

  • 注解作用介绍

@Transactional 用于声明事务性方法,确保方法在事务的上下文中执行。

  • 注解属性介绍
    • propagation: 事务传播行为。
    • isolation: 事务隔离级别。
    • timeout: 事务超时时间。
    • readOnly: 指示事务是否为只读。
    • rollbackFor: 定义在哪些异常下事务应该回滚。
  • 注解业务案例
@Transactional(readOnly = false, rollbackFor = Exception.class)
public void updateUser(User user) {
 // 更新用户信息的逻辑
} 

10. 事件监听

@EntityListeners

  • 注解作用介绍

@EntityListeners 用于指定一个或多个类作为实体的事件监听器。

  • 注解属性介绍
    • 指定监听器类的数组。
  • 注解业务案例
@EntityListeners({AuditListener.class, AnotherListener.class})
public class AuditableEntity {
 // 实体成员
} 

@PrePersist

  • 注解作用介绍

@PrePersist 用于标记在实体持久化(插入)前执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PrePersist
 public void onPrePersist() {
 // 持久化前的逻辑
 }
} 

@PostPersist

  • 注解作用介绍

@PostPersist 用于标记在实体持久化(插入)后执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PostPersist
 public void onPostPersist() {
 // 持久化后的逻辑
 }
} 

@PreUpdate

  • 注解作用介绍

@PreUpdate 用于标记在实体更新前执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PreUpdate
 public void onPreUpdate() {
 // 更新前的逻辑
 }
} 

@PostUpdate

  • 注解作用介绍

@PostUpdate 用于标记在实体更新后执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PostUpdate
 public void onPostUpdate() {
 // 更新后的逻辑
 }
} 

@PreRemove

  • 注解作用介绍

@PreRemove 用于标记在实体删除前执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PreRemove
 public void onPreRemove() {
 // 删除前的逻辑
 }
} 

@PostRemove

  • 注解作用介绍

@PostRemove 用于标记在实体删除后执行的方法。

  • 注解属性介绍

无特定属性。

  • 注解业务案例
public class User {
 @PostRemove
 public void onPostRemove() {
 // 删除后的逻辑
 }
} 

11. 高级映射

@AttributeOverride

  • 注解作用介绍

@AttributeOverride 用于覆盖继承自父类的属性映射。

  • 注解属性介绍
    • name: 指定要覆盖的属性名称。
    • column: 指定新的列映射。
  • 注解业务案例
public class BaseEntity {
 @Column(name = "base_id")
 protected Long id;
}

public class User extends BaseEntity {
 @AttributeOverride(name = "id", column = @Column(name = "user_id"))
 protected Long id;
} 

@AssociationOverride

  • 注解作用介绍

@AssociationOverride 用于覆盖继承自父类的关联属性映射。

  • 注解属性介绍
    • name: 指定要覆盖的关联属性名称。
    • joinColumns: 指定新的关联列映射。
  • 注解业务案例
public class BaseEntity {
 @ManyToOne
 protected Department department;
}

public class User extends BaseEntity {
 @AssociationOverride(
        name = "department",
        joinColumns = @JoinColumn(name = "user_department_id")
 )
 protected Department department;
} 

@SqlResultSetMapping

  • 注解作用介绍

@SqlResultSetMapping 用于定义SQL查询结果与Java类型之间的映射。

  • 注解属性介绍
    • name: 指定结果集映射的名称。
    • classes: 指定结果集映射的类。
    • columns: 指定结果集中列的名称。
  • 注解业务案例
@EntityResultMapping(
    name = "UserDetails",
    classes = User.class,
    columns = {
 @ColumnResult(name = "id", type = Long.class),
 @ColumnResult(name = "username", type = String.class)
 }
)
public class User {
 //类成员
} 

12. 条件查询

@Where

  • 注解作用介绍

@Where 用于在实体类级别指定一个额外的查询条件,作为所有查询方法的默认条件。

  • 注解属性介绍
    • 通常与 clause 属性一起使用,指定要添加的条件语句。
  • 注解业务案例
public class User {
 @Where(clause = "status = 'ACTIVE'")
 @OneToMany(mappedBy = "user")
 private List<Post> posts;
} 

13. 所有注解综合案例

1. 数据库表结构

定义数据库中的表结构:

-- 用户表
CREATE TABLE User (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 订单表
CREATE TABLE Order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    total DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES User(id)
);

-- 订单项表
CREATE TABLE OrderItem (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    product_name VARCHAR(255) NOT NULL,
    quantity INT NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES Order(id)
); 

2. 实体定义与映射

定义Java实体和对应的JPA注解。

User 实体

import javax.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Table(name = "User")
@EntityListeners(AuditingEntityListener.class) // 用于添加创建和更新时间戳
public class User {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @Column(nullable = false, unique = true)
 private String username;

 @Column(nullable = false, unique = true)
 private String email;

 @CreatedDate
 private Date createdAt;

 @PrePersist
 private void onPrePersist() {
        email = email.toLowerCase();
 }

 // Getters and Setters
} 

Order 实体

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "Order")
public class Order {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "user_id", nullable = false)
 private User user;

 @Column(nullable = false)
 private double total;

 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
 private List<OrderItem> items;

 // Getters, Setters, and other methods
} 

OrderItem 实体

import javax.persistence.*;

@Entity
@Table(name = "OrderItem")
public class OrderItem {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "order_id", nullable = false)
 private Order order;

 @Column(nullable = false)
 private String productName;

 @Column(nullable = false)
 private int quantity;

 @Column(nullable = false)
 private double price;

 // Getters and Setters
} 

3. 仓库定义

定义Spring Data JPA仓库接口。

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
 List<User> findByUsernameContaining(String username);
}

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
 List<Order> findByUserId(Long userId);
} 

4. 服务层定义

定义服务层,使用 @Transactional注解管理事务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

 @Autowired
 private OrderRepository orderRepository;

 @Transactional
 public Order createOrder(User user, List<OrderItem> items) {
 Order order = new Order();
        order.setUser(user);
        order.setItems(items);
 // 计算总价格等逻辑
 return orderRepository.save(order);
 }
} 

5. 事件监听与自定义注解

定义事件监听器和自定义注解 @Auditable

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class UserRegistrationListener {

 @EventListener
 public void onUserRegistration(UserRegisteredEvent event) {
 User user = event.getUser();
 // 处理用户注册事件逻辑
 }
}

// 自定义注解 @Auditable
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
} 

6. 应用程序启动类

最后,定义Spring Boot应用程序的启动类。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ECommerceApplication {

 public static void main(String[] args) {
 SpringApplication.run(ECommerceApplication.class, args);
 }
} 

 

这也是简单到令人发指,spring-data-jpa所有的语法规定如下图:

通过上面,基本CRUD和基本的业务逻辑操作都得到了解决,我们要做的工作少到仅仅需要在UserRepository接口中定义几个方法,其他所有的工作都由spring-data-jpa来完成。

接下来:就是比较复杂的操作了,比如动态查询,分页,下面详细介绍spring-data-jpa的第二大杀手锏,强大的动态查询能力。

在上面的介绍中,对于我们传统的企业级应用的基本操作已经能够基本上全部实现,企业级应用一般都会有一个模糊查询的功能,并且是多条的查询,在有查询条件的时候我们需要在where后面接上一个 xxx = yyy 或者 xxx like ‘% + yyy + %’类似这样的sql。那么我们传统的JDBC的做法是使用很多的if语句根据传过来的查询条件来拼sql,mybatis的做法也类似,由于mybatis有强大的动态xml文件的标签,在处理这种问题的时候显得非常的好,但是二者的原理都一致,那spring-data-jpa的原理也同样很类似,这个道理也就说明了解决多表关联动态查询根儿上也就是这么回事。

  那么spring-data-jpa的做法是怎么的呢?有两种方式。可以选择其中一种,也可以结合使用,在一般的查询中使用其中一种就够了,就是第二种,但是有一类查询比较棘手,比如报表相关的,报表查询由于涉及的表很多,这些表不一定就是两两之间有关系,比如字典表,就很独立,在这种情况之下,使用拼接sql的方式要容易一些。下面分别介绍这两种方式。

 ---------------------------------------------------------------------------

1. 概述
Spring Data提供了许多方法来定义我们可以执行的查询。其中之一是@Query注释。

在本教程中,我们将演示如何使用春季数据JPA中的@Query注释来执行JPQL和本机SQL查询。

我们还将展示在@Query注释不够用时如何构建动态查询。

延伸阅读:
春季数据 JPA 存储库中的派生查询方法
探索春季数据 JPA 中的查询派生机制。

阅读更多→

春季数据 JPA @Modifying注释
通过组合@Query和@Modifying注释,在春季数据 JPA 中创建 DML 和 DDL 查询

阅读更多→

2. 选择“查询”
为了定义要为Spring数据存储库方法执行的SQL,我们可以使用@Query注释来注释该方法 - 其值属性包含要执行的JPQL或SQL。

@Query批注优先于命名查询,命名查询使用@NamedQuery进行批注或在 orm.xml 文件中定义。

将查询定义放在存储库内的方法正上方,而不是作为命名查询放在域模型内部,这是一种很好的方法。存储库负责持久性,因此它是存储这些定义的更好地方。

2.1. JPQL
默认情况下,查询定义使用 JPQL。

让我们看一个简单的存储库方法,该方法从数据库中返回活动的 User 实体:

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
2.2. 原生
我们也可以使用本机 SQL 来定义我们的查询。我们所要做的就是将本机Query属性的值设置为true,并在注释的值属性中定义本机SQL查询:

@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();
3. 在查询中定义顺序
我们可以将 Sort 类型的附加参数传递给具有@Query批注的 Spring Data 方法声明。它将被转换为传递给数据库的 ORDER BY 子句。

3.1. JPA 提供的方法和派生方法的排序
对于我们开箱即用的方法,例如 findAll(Sort) 或通过解析方法签名生成的方法,我们只能使用对象属性来定义我们的排序:

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));
现在想象一下,我们要按 name 属性的长度进行排序:

userRepository.findAll(Sort.by("LENGTH(name)"));
当我们执行上述代码时,我们将收到一个异常:

org.springframework.data.mapping.PropertyReferenceException: No property LENGTH(name) found for type User!
3.2. JPQL
当我们使用JPQL进行查询定义时,Spring Data可以毫无问题地处理排序 - 我们所要做的就是添加一个Sort类型的方法参数:

@Query(value = "SELECT u FROM User u")
List<User> findAllUsers(Sort sort);
我们可以调用此方法并传递 Sort 参数,该参数将按 User 对象的 name 属性对结果进行排序:

userRepository.findAllUsers(Sort.by("name"));
由于我们使用了@Query注释,因此我们可以使用相同的方法来按名称的长度获取排序的用户列表:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));
使用 JpaSort.unsafe() 创建排序对象实例至关重要。

当我们使用:

Sort.by("LENGTH(name)");
然后,我们将收到与上面看到的 findAll() 方法完全相同的异常。

当Spring Data发现使用@Query注释的方法的不安全排序顺序时,它只会将排序子句附加到查询中 - 它会跳过检查排序依据的属性是否属于域模型。

3.3. 原生
当@Query批注使用本机 SQL 时,则无法定义排序。

如果我们这样做,我们将收到一个例外:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination
正如异常情况所说,本机查询不支持排序。错误消息提示分页也会导致异常。

但是,有一种启用分页的解决方法,我们将在下一节中介绍它。

4. 分页
分页允许我们在主页中仅返回整个结果的子集。例如,在网页上的多个数据页面中导航时,这很有用。

分页的另一个优点是,从服务器发送到客户端的数据量是最小的。通过发送较小的数据片段,我们通常可以看到性能的提高。

4.1. JPQL
在 JPQL 查询定义中使用分页非常简单:

@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
我们可以传递一个页面请求参数来获取一页数据。

本机查询也支持分页,但需要一些额外的工作。

4.2. 原生
我们可以通过声明其他属性 countQuery 来为本机查询启用分页。

这定义了要执行的 SQL,以计算整个结果中的行数:

@Query(
value = "SELECT * FROM Users ORDER BY id",
countQuery = "SELECT count(*) FROM Users",
nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);
4.3. 2.0.4 之前的弹簧数据JPA版本
上述本机查询解决方案适用于Spring数据JPA版本2.0.4及更高版本。

在该版本之前,当我们尝试执行此类查询时,我们将收到我们在上一节有关排序中描述的相同异常。

我们可以通过在查询中添加一个额外的分页参数来克服这个问题:

@Query(
value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n",
countQuery = "SELECT count(*) FROM Users",
nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);
在上面的例子中,我们添加

\n-- #pageable\n
作为分页参数的占位符。这告诉春季数据JPA如何解析查询并注入可分页参数。此解决方案适用于 H2 数据库。

我们已经介绍了如何通过 JPQL 和本机 SQL 创建简单的选择查询。接下来,我们将演示如何定义其他参数。

5. 索引查询参数
有两种可能的方法可以将方法参数传递给查询:索引参数和命名参数。

在本节中,我们将介绍索引参数。

5.1. JPQL
对于 JPQL 中的索引参数,Spring Data 将按照方法声明中出现的顺序将方法参数传递给查询:

@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);

@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);
对于上述查询,状态方法参数将分配给索引为 1 的查询参数,并将 name 方法参数分配给索引为 2 的查询参数。

5.2. 原生
本机查询的索引参数的工作方式与 JPQL 完全相同:

@Query(
value = "SELECT * FROM Users u WHERE u.status = ?1",
nativeQuery = true)
User findUserByStatusNative(Integer status);
在下一节中,我们将展示一种不同的方法:通过 name 传递参数。

6. 命名参数
我们还可以使用命名参数将方法参数传递给查询。我们使用存储库方法声明中的@Param注释来定义这些内容。

用 @Param 注释的每个参数都必须具有与相应的 JPQL 或 SQL 查询参数名称匹配的值字符串。具有命名参数的查询更易于阅读,并且在需要重构查询时不易出错。

6.1. JPQL
如上所述,我们在方法声明中使用@Param注释,以将JPQL中按名称定义的参数与方法声明中的参数进行匹配:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
@Param("status") Integer status,
@Param("name") String name);
请注意,在上面的示例中,我们将 SQL 查询和方法参数定义为具有相同的名称,但只要值字符串相同,就不需要它:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByUserStatusAndUserName(@Param("status") Integer userStatus,
@Param("name") String userName);
6.2. 原生
对于本机查询定义,与 JPQL 相比,我们如何通过名称将参数传递给查询没有区别 — 我们使用@Param注释:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name",
nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
@Param("status") Integer status, @Param("name") String name);
7. 集合参数
让我们考虑一下 JPQL 或 SQL 查询的 where 子句包含输入(或非 IN)关键字的情况:

SELECT u FROM User u WHERE u.name IN :names
在这种情况下,我们可以定义一个将 Collection 作为参数的查询方法:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
由于参数是集合,因此可以与列表、哈希集等一起使用。

接下来,我们将演示如何使用 @修改注释修改数据。

8. 使用@Modifying更新查询
我们可以使用 @Query 注释来修改数据库的状态,方法是将 @Modifying 注释添加到存储库方法中。

8.1. JPQL
与选择查询相比,修改数据的存储库方法有两个区别 — 它具有@Modifying注释,当然,JPQL 查询使用 update 而不是 select:

@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
@Param("name") String name);
返回值定义执行查询时更新的行数。索引参数和命名参数都可以在更新查询中使用。

8.2. 原生
我们也可以使用本机查询来修改数据库的状态。我们只需要添加@Modifying注释:

@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);
8.3. 插入
要执行插入操作,我们必须应用@Modifying并使用本机查询,因为 INSERT 不是 JPA 接口的一部分:

@Modifying
@Query(
value =
"insert into Users (name, age, email, status) values (:name, :age, :email, :status)",
nativeQuery = true)
void insertUser(@Param("name") String name, @Param("age") Integer age,
@Param("status") Integer status, @Param("email") String email);
9. 动态查询
通常,我们会遇到基于条件或数据集构建SQL语句的需要,这些条件或数据集的值仅在运行时才知道。在这些情况下,我们不能只使用静态查询。

9.1. 动态查询示例
例如,让我们想象一种情况,我们需要从运行时定义的集合中选择所有电子邮件类似于一个的用户 - email1,email2,...,emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%'
or u.email LIKE '%email2%'
...
or u.email LIKE '%emailn%'
由于集合是动态构造的,因此我们无法在编译时知道要添加多少 LIKE 子句。

在这种情况下,我们不能只使用@Query注释,因为我们无法提供静态 SQL 语句。

相反,通过实现自定义复合存储库,我们可以扩展基本的 JpaRepository 功能,并提供我们自己的逻辑来构建动态查询。让我们来看看如何做到这一点。

9.2. 自定义仓库和 JPA 标准 API
对我们来说幸运的是,Spring提供了一种通过使用自定义片段接口来扩展基本存储库的方法。然后,我们可以将它们链接在一起以创建复合存储库。

我们将从创建自定义片段接口开始:

public interface UserRepositoryCustom {
List<User> findUserByEmails(Set<String> emails);
}
然后我们将实现它:

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

@PersistenceContext
private EntityManager entityManager;

@Override
public List<User> findUserByEmails(Set<String> emails) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);

Path<String> emailPath = user.get("email");

List<Predicate> predicates = new ArrayList<>();
for (String email : emails) {
predicates.add(cb.like(emailPath, email));
}
query.select(user)
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

return entityManager.createQuery(query)
.getResultList();
}
}

如上所示,我们利用 JPA 标准 API 来构建动态查询。

此外,我们需要确保在类名中包含 Impl 后缀。Spring 将搜索“用户存储库自定义”实现为“用户存储库自定义”。由于片段本身不是存储库,因此Spring依靠这种机制来查找片段实现。

9.3. 扩展现有仓库
请注意,从第 2 节到第 7 节的所有查询方法都在用户存储库中。

因此,现在我们将通过在用户存储库中扩展新界面来集成我们的片段:

public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom {
// query methods from section 2 - section 7
}
9.4. 使用仓库
最后,我们可以调用我们的动态查询方法:

Set<String> emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);
我们已成功创建了一个复合存储库,并调用了我们的自定义方法。

posted @ 2025-04-15 14:52  hanease  阅读(63)  评论(0)    收藏  举报