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類型Clob
、Blob
占用內存空間較大,一般配合@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。
推薦閱讀:
- 使用JPA雙向多對多關聯關系@ManyToMany
- jpa onetomany 使用級連表刪除被維護表數據時的坑
- 解決使用@ManyToMany查詢數據時的死循環問題
- Spring Boot 整合JPA 數據模型關聯使用操作(一對一、一對多、多對多)
- Java Hibernate中的持久化類和實體類關系