Spring Data JPA 實體類中常用註解說明

javax.persistence 介紹

Spring Data JPA 采用約定大於配置的思想,默認瞭很多東西

JPA是存儲業務實體關聯的實體來源,它顯示定義瞭如何定義一個面向普通Java對象(POJO)作為實體,以及如何與管理關系實體提供一套標準

javax.persistence位於hibernate-jpa-**.jar 包裡面

jpa類層次結構:

JPA類層次結構的顯示單元:

單元 描述
EntityManagerFactory 一個EntityManager的工廠類,創建並管理多個EntityManager實例
EntityManager 一個接口,管理持久化操作的對象,工廠原理類似工廠的查詢實例
Entity 實體是持久性對象,是存儲在數據庫中的記錄
EntityTransaction 與EntityManager是一對一的關系,對於每一個EntityManager的操作由EntityTransaction類維護
Persistence 這個類包含靜態方法來獲取EntityManagerFactory實例
Query 該接口由每個JPA供應商實現,能夠獲得符合標準的關系對象

基本註解

@Entity

@Entity定義對象將會成為被JPA管理的實體,將映射到指定的數據庫表

public @interface Entity {
    String name() default "";
}

@Table

@Table指定數據庫的表名

public @interface Table {
    // 表的名字,可選,默認實體類名為表的名稱(駝峰命名規則)
    String name() default "";
    // 此表的catlog,可選
    String catalog() default "";
    // 此表所在的schema,可選
    String schema() default "";
    // 唯一性約束,隻有在創建表的時候有用,默認不需要
    UniqueConstraint[] uniqueConstraints() default {};
    // 索引,隻有創建表的時候有用,默認不需要
    Index[] indexes() default {};
}

@Id

定義屬性為數據庫中的主鍵,一個實體必須有一個

@IdClass

@IdClass利用外部類的聯合主鍵

public @interface IdClass {
     // 聯合主鍵的類
    Class value();
}
  • 作為聯合主鍵類,需要滿足以下要求:
  • 必須實現Serializable
  • 必須有默認的public無參構造方法
  • 必須覆蓋equals和hashCode方法(EntityManager通過find方法查找Entity時是根據equals來判斷的)

用法:

(1)假設user_article表中的聯合主鍵是 title 與create_user_id,聯合主鍵類代碼如下:

@Data
public class UserArticleKey implements Serializable {
    private String title;
    private Long createUserId;
    public UserArticleKey() {
    }
    public UserArticleKey(String title, String content, Long createUserId) {
        this.title = title;
        this.createUserId = createUserId;
    }
}

(2)user_article表實體類如下:

@Entity
@IdClass(value = UserArticleKey.class)
@Data
public class UserArticle {
    private Integer id;
    @Id
    private String title;
    @Id
    private Long createUserId;
}

(3) repository 類如下:

public interface ArticleRepository extends JpaRepository<UserArticle, UserArticleKey> {
}

(4)調用代碼如下:

@Test
public void testUnionKey(){
    Optional<UserArticle> userArticle = this.articleRepository.findById(new UserArticleKey("新聞",1L));
    if (userArticle.isPresent()){
        System.out.println(userArticle.get());
    }
}

@GenerateValue

主鍵生成策略

public @interface GeneratedValue {
    // Id 的生成策略
    GenerationType strategy() default GenerationType.AUTO;
    // 通過 Sequence生成Id, 常見Oracle生成規則,需要配合@SequenceGenerator使用
    String generator() default "";
}

GenerationType

public enum GenerationType {
    // 通過表產生主鍵,框架由表模擬序列產生主鍵(有益於數據庫移植)
    TABLE,
    // 通過序列產生主鍵,通過@SequenceGenerator註解指定序列名,不支持MySQL
    SEQUENCE,
    // 采用數據ID自增長,一般用於MySQL
    IDENTITY,
    // JPA自動適配的策略,默認選項
    AUTO;
    private GenerationType() {
    }
}

@Basic

屬性是到數據表的字段的映射,實體類上的字段沒有註解時默認為@Basic

public @interface Basic {
    // 可選,默認為立即加載(EAGER),LZAY延遲加載(應用在大字段上)
    FetchType fetch() default FetchType.EAGER;
    // 可選,設置這個字段是否可為null,默認 true
    boolean optional() default true;
}

@Transient

表示該屬性並非一個到數據庫表的字段的映射,表示非持久化屬性,與@Basic作用相反,JPA映射的時候會忽略@Transient標記的字段

@Column

定義屬性對應數據庫中的列名

public @interface Column {
    // 是語句庫中表的列名, 默認字段名和屬性名一樣, 可選
    String name() default "";
    // 是否唯一,默認 false, 可選
    boolean unique() default false;
    // 是否允許空, 默認 true, 可選
    boolean nullable() default true;
    // 執行insert操作的時候是否包含此字段,默認 true, 可選
    boolean insertable() default true;
    // 執行update操作的時候是否包含此字段,默認 true, 可選
    boolean updatable() default true;
    // 該字段在數據庫中的實際類型
    String columnDefinition() default "";
    // 當映射多個表時,指在哪張表的字段,默認為主表
    String table() default "";
    // 字段長度,字段類型為VARCHAR時有效
    int length() default 255;
    // 精度,當字段類型為double時候, precision表示數值總長度
    int precision() default 0;
    // 精度, 當字段類型為double時候, scale表示小數位數
    int scale() default 0;
}

@Temporal

用來設置Date 類型的屬性映射到對應精度的字段

  • @Temporal(Temporal.DATE) 映射為日期
  • @Temporal(Temporal.TIME) 映射為日期(隻有時間)
  • @Temporal(Temporal.TIMESTAMP) 映射為日期(日期+時間)

@Enumerated

映射menu枚舉類型的字段

源碼:

public @interface Enumerated {
    // 映射枚舉的類型
    EnumType value() default EnumType.ORDINAL;
}
public enum EnumType {
    // 映射枚舉字段的下標
    ORDINAL,
    // 映射枚舉的name
    STRING;
}

如果使用 ORDINAL 在數據庫中則會存儲 0,1,2,3 這是一個索引值,這個索引值是由enum中元素的位置決定,如果enum中元素位置出現不正確的變動

很可能與數據庫中的數據無法對應,建議使用 STRING

用法:

@Entity(name = "t_user")
@Data
@Table
public class SystemUser implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Basic
    private String uname;
    private String email;
    private String address;
    @Enumerated(EnumType.STRING)
    @Column(name = "user_gender")
    private Gender sex;
    public enum Gender{
        MALE("男性"),
        FEMALE("女性")
        ;
        private String value;
        Gender(String value) {
            this.value = value;
        }
    }
}

@Lob

映射成數據庫支持的大對象類型

  • Clob(Character Large Objects) 類型是長字符串類型java.sql.Clob、Character[]、char[]和String將被映射為Clob類型
  • Blob(Binary Large Objects) 類型是字節型,java.sql.Blob,Byte[]、byte[]和實現瞭Serializable接口的類型將被映射為Blob類型
  • ClobBlob占用內存空間較大,一般配合@Basic(fetch=FetchType.LAZY)將其設置為延遲加載

@NamedNativeQueries、@NamedNativeQuerie、@SqlResultSetMappings、@SqlResultSetMapping、@ColumnResult

這幾個註解一般配合使用,實際情況中不會自定義這些配置

@NamedNativeQueries({
        @NamedNativeQuery(
                name = "getUsers",
                query = "select id,title,create_user_id,create_date from user_article order by create_date desc",
                resultSetMapping = "userArticleMap"
        )
})
@SqlResultSetMappings({
        @SqlResultSetMapping(
                name = "userArticleMap",
                entities = {},
                columns = {
                        @ColumnResult(name = "id"),
                        @ColumnResult(name = "title"),
                        @ColumnResult(name = "createUserId"),
                        @ColumnResult(name = "createDate"),
                }
        )
})
@Entity
@IdClass(value = UserArticleKey.class)
@Data
public class UserArticle {
    @Column
    private Integer id;
    @Id
    private String title;
    @Id
    private Long createUserId;
    private Date createDate;
}

關聯關系註解

@JoinColumn 定義外鍵關聯的字段名稱

主要配合 @OneToOne、@ManyToOne、@OneToMany一起使用,單獨使用沒有意義

@JoinColumns 定義多個字段的關聯關系

public @interface JoinColumn {
    // 目標表的字段名,必填
    String name() default "";
    // 本實體類的字段名, 非必填, 默認本表ID
    String referencedColumnName() default "";
    // 外鍵字段是否唯一, 可選
    boolean unique() default false;
    // 外鍵字段是否允許為空, 可選
    boolean nullable() default true;
    // 是否跟隨一起新增, 可選
    boolean insertable() default true;
    // 是否跟隨一起更新, 可選
    boolean updatable() default true;
    // 生成DDL的時候使用的SQL片段 可選
    String columnDefinition() default "";
    // 包含列的表的名稱 , 可選
    String table() default "";
    // 外鍵約束類別, 默認為 默認約束行為, 可選
    ForeignKey foreignKey() default @ForeignKey(ConstraintMode.PROVIDER_DEFAULT);
}

@OneToOne 一對一關聯關系

public @interface OneToOne {
    // 關系目標實體, 默認為void.class, 可選
    Class targetEntity() default void.class;
    // 級聯操作策略, PERSIST(級聯新增)、REMOVE(級聯刪除)、REFRESH(級聯刷新)、MERGE(級聯更新)、ALL(全選)
    // 默認表不會產生任何影響
    CascadeType[] cascade() default {};
    // 數據獲取方式,EAGER(立即加載)、LAZY(延遲加載)
    FetchType fetch() default EAGER;
    // 是否允許空
    boolean optional() default true;
    // 關聯關系被誰維護,非必填,一遍不需要特別指定
    // mappedBy不能與@JoinColumn或者@JoinTable同時使用
    // mappedBy的值是指另一方的實體裡面屬性的字段,而不是數據庫字段,也不是實體的對象的名字,即另一方配置瞭@JoinColumn或者@JoinTable註解的屬性的字段名稱
    String mappedBy() default "";
    
    // 是否級聯刪除,和CascadeType.REMOVE 效果一樣,任意配置一種即可生效
    boolean orphanRemoval() default false;
}

用法:

@OneToOne
// name 為當前實體對應數據庫表中的字段名
// referencedColumnName 為 SystemUser 實體中@Id標記的字段對應的數據庫字段名
@JoinColumn(name = "create_user_id",referencedColumnName = "id")
private SystemUser createUser = new SystemUser();

雙向關聯:

@OneToOne(mappedBy = "createUser")
private UserArticle article = new UserArticle();

等價於mappedBy:

@OneToOne
@JoinColumn(name = "user_article_id",referencedColumnName = "id")
private UserArticle article = new UserArticle();

@OneToMany 與 @ManyToOne 一對多與多對一關聯關系

OneToMany與ManyToOne可以相對存在,也可隻存在一方

public @interface OneToMany {
    Class targetEntity() default void.class;
    // 級聯操作策略
    CascadeType[] cascade() default {};
    // 數據獲取方式
    FetchType fetch() default LAZY;
    // 關系被誰維護,單項的
    String mappedBy() default "";
    // 是否級聯刪除
    boolean orphanRemoval() default false;
}
public @interface ManyToOne {
    Class targetEntity() default void.class;
    // 級聯操作策略
    CascadeType[] cascade() default {};
    // 數據獲取方式
    FetchType fetch() default LAZY;     // 關聯是否可選。如果設置,若要為false,則必須始終存在非null關系。
    boolean optional() default true;
}

@ManyToOne 映射的是一個實體對象

@Entity
@Data
public class UserArticle {
    @Column
    private Integer id;
    @Id
    private String title;
    @ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    @JoinColumn(name = "create_user_id",referencedColumnName = "id")
    private SystemUser systemUser = new SystemUser();
    private Date createDate;
}

@OneToMany 映射的是一個是列表

@Entity(name = "t_user")
@Data
@Table
public class SystemUser implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Basic
    private String uname;
    private String email;
    private String address;
    @Enumerated(EnumType.STRING)
    private Gender sex;
@OneToMany
// name 當前表id
// create_user_id 目標表的關聯字段
@JoinColumn(name = "id",referencedColumnName = "create_user_id")
private List<UserArticle> articleList = new ArrayList<>();
    public enum Gender{
        MALE("男性"),
        FEMALE("女性")
        ;
        private String value;
        Gender(String value) {
            this.value = value;
        }
    }
}

@OrderBy 排序關聯查詢

與@OneToMany一起使用

public @interface OrderBy {
    /**
     * 要排序的字段格式如下:
     *    orderby_list::= orderby_item [,orderby_item]*
     *    orderby_item::= [property_or_field_name] [ASC | DESC]
     * 字段可以是實體屬性,也可以是數據字段,默認ASC
     */
    String value() default "";
}

用法:

@OneToMany
@JoinColumn(name = "id",referencedColumnName = "create_user_id")
@OrderBy("createDate DESC") // createDate 是 UserArticle 的實體屬性
private List<UserArticle> articleList = new ArrayList<>();
 

@JoinTable 關聯關系表

對象與對象之間有關聯關系表的時候就用到,@JoinTable, 與@ManyToMany一起使用

public @interface JoinTable {
    // 中間關聯關系表名
    String name() default "";
    // 表的 catalog
    String catalog() default "";
    // 表的 schema
    String schema() default "";
    // 主連接表的字段
    JoinColumn[] joinColumns() default {};
    // 被連接表的字段
    JoinColumn[] inverseJoinColumns() default {};
    // 主連接外鍵約束類別
    ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT);
    // 被連接外鍵約束類別
    ForeignKey inverseForeignKey() default @ForeignKey(PROVIDER_DEFAULT);
    // 唯一約束
    UniqueConstraint[] uniqueConstraints() default {};
    // 表的索引
    Index[] indexes() default {};
}

用法:

@Entity
// 主連接表 blog
public class Blog {
    @ManyToMany
    @JoinTable(
            name = "blog_tag_relation", // 關系表名稱
            joinColumns = @JoinColumn(name = "blog_id",referencedColumnName = "id"), // 主連接表配置
            inverseJoinColumns = @JoinColumn(name = "tag_id",referencedColumnName = "id") // 被連接表配置
    )
    // tag 是被連接表
    private List<Tag> tags = new ArrayList<>();
}

關於雙向多對多:

雙向多對多需要建立 @JoinTable的實體裡, 在被連接表中的@ManyToMany中使用mappedBy=”BlogTagRelation”進行配置

LeftJoin 與 Inner Join 可以提高查詢效率

當使用@ManyToMany、@ManyToOne、@OneToMany、@OneToOne關聯時候,SQL真正執行的時候,由一條主表查詢和N條子表查詢組成

會產生N+1問題

為瞭簡單的提高查詢效率,使用EntityGraph可以解決N+1條SQL的問題

@EntityGraph

@EntityGraph、@NamedEntityGraph用來提高查詢效率(很好的解決瞭N+1條SQL的問題),兩者需要配合使用,缺一不可

實體類:

@NamedEntityGraph(
        name = "Blog.tags", attributeNodes = {
        @NamedAttributeNode("tags")
}
)
@Entity
// 主連接表 blog
public class Blog {
    @ManyToMany
    @JoinTable(
            name = "blog_tag_relation", // 關系表名稱
            joinColumns = @JoinColumn(name = "blog_id", referencedColumnName = "id"), // 主連接表配置
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id") // 被連接表配置
    )
    // tag 是被連接表
    private List<Tag> tags = new ArrayList<>();
}

repository:

public interface BlogRepository extends JpaRepository<Blog,Long> {
    @Override
    @EntityGraph(value = "Blog.tags")
    List<Blog> findAll();
}

關於關系查詢的一些註意事項

所有註解要麼全配置在字段上,要麼配置在get方法上,不能混用,會無法啟動

所有的關聯都是支持單向關聯和雙向關聯的,JSON序列化的時候使用雙向註解會產生死循環,需要手動轉化一次或使用@JsonIgnore

在所有的關聯查詢中,表一般是不需要簡歷外鍵索引的,@mappedBy的使用需要註意

級聯刪除比較危險,建議考慮清楚或完全掌握

不同的關聯關系的配置,@JoinColumn裡面的name,referencedColumnName代表的意思是不一樣的

當配置這些關聯關系的時候建議直接在表上把外鍵關系簡歷好,然後用開發工具直接生成,這樣可以減少調試時間

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: