Spring Data JPA實體詳解

1. Spring Data JPA實體概述

JPA提供了一種簡單高效的方式來管理Java對象(POJO)到關係數據庫的映射,此類Java對象稱爲JPA實體或簡稱實體。實體一般與底層數據庫中的單個關係表相關聯,每一個實體的實例表示數據庫表格中的某一行。java

2. Spring Data JPA實體管理器

2.1 實體管理器概述

實體管理器(EntityManager)用於管理系統中的實體,它是實體與數據庫之間的橋樑,經過調用實體管理器的相關方法能夠把實體持久化到數據庫中,同時也能夠把數據庫中的記錄打包成實體對象。數據庫

2.2 實體管理器的經常使用方法

2.2.1 實體的四種狀態

在此以前咱們要先了解實體的狀態及其轉換,見下圖數組

JPA實體生命週期有四種狀態app

  • 新建狀態(New):對象在保存進數據庫以前爲臨時狀態。此時數據庫中沒有該對象的信息,該對象的ID屬性也爲空。若是沒有被持久化,程序退出時臨時狀態的對象信息將丟失。
  • 託管狀態(Managed):對象在保存進數據庫後或者從數據庫中加載後、而且沒有脫離Session時爲持久化狀態。這時候數據庫中有對象的信息,改對象的id爲數據庫中對應記錄的主鍵值。因爲還在Session中,持久化狀態的對象能夠執行任何有關數據庫的操做,例如獲取集合屬性的值等。
  • 遊離狀態(Datached):是對象曾經處於持久化狀態、可是如今已經離開Session了。雖然分離狀態的對象有id值,有對應的數據庫記錄,可是已經沒法執行有關數據庫的操做。例如,讀取延遲加載的集合屬性,可能會拋出延遲加載異常。
  • 刪除狀態(Removed):刪除的對象,有id值,尚且和Persistence Context有關聯,可是已經準備好從數據庫中刪除。
狀態名  做爲java對象存在  在實體管理器中存在  在數據庫存在
New  Y N N
Managed Y Y Y
Datached N N N
Removed Y Y N

用一段程序來示範ide

@Transactional
public void save(){

    //New 狀態
    Task t = new Task();
    t.setTaskName("task" + new Date().getTime());
    t.setCreateTime(new Date());

    //Managed狀態
    em.persist(t); //實體類t已經有id t.getId();
    t.setTaskName("kkk");  //更新任務名稱,這時,若是提交事務,則直接將kkk更新到數據庫

    //Detached狀態 事務提交或者調用em.clear都直接將實體任務狀態變爲Detached
    em.clear();
    t.setTaskName("kkk"); //更新數據不會更新到數據庫

    //Removed狀態
    em.remove(t);
}

2.2.2  實體管理器的經常使用方法

對應於實體的四種狀態,實體管理器有四種經常使用的方法,分別是:persist / merge / clear / remove,結合狀態圖,能夠判斷,對於不一樣狀態下的實體,各個方法操做結果會有不一樣:fetch

對於不一樣狀態下的實體,persist 操做結果以下:ui

  • 新建狀態:實體狀態遷移到託管狀態
  • 託管狀態:實體狀態不發生改變,但會執行數據庫的insert操做
  • 遊離狀態:方法的調用將會拋出異常信息
  • 刪除狀態:實體將重返託管狀態

對於不一樣狀態下的實體,merge 操做結果以下:spa

  • 新建狀態:系統會執行數據庫insert操做,同時返回一個託管狀態的實體
  • 託管狀態:實體狀態不發生改變
  • 遊離狀態:系統將實體的修改保存到數據庫,同時返會一個託管狀態的實體
  • 刪除狀態:方法調用將拋出異常

對於不一樣狀態下的實體,refresh 操做結果以下:hibernate

  • 新建狀態:系統會執行數據庫insert操做,同時返回一個託管狀態的實體
  • 託管狀態:實體狀態不發生改變,但會執行數據庫的update操做
  • 遊離狀態:實體狀態將返回託管狀態
  • 刪除狀態:方法調用將拋出異常

對於不一樣狀態下的實體,remove 操做結果以下:code

  • 新建狀態:方法調用將拋出異常
  • 託管狀態:實體狀態變成刪除狀態
  • 分離狀態:方法調用將拋出異常
  • 刪除狀態:不發生任何操做

2.2.3 利用實體管理器管理實體(實現實體的CURD)

public class UserRepositoryImpl {

    @PersistenceContext
    private EntityManager entityManager;
    
    @Transactional
    public void add(User user) {
        entityManager.persist(user);

    }
    @Transactional
    public User update(User user) {
        User userUpdate = entityManager.find(User.class, user.getId());
        userUpdate.setAddress(user.getAddress());
        userUpdate.setName(user.getName());
        userUpdate.setPhone(user.getPhone());    
        return userUpdate;
    }
    @Transactional
    public User addOrUpdate(User user) {    
        return entityManager.merge(user);
    }
    @Transactional
    public void delete(User user) {
        entityManager.remove(user);
    }

    public User findOne(Integer id) {   
        return entityManager.find(User.class, id);
    }

    public List<User> findAll() {
        String queryString = "select u from User u";
        Query query = entityManager.createQuery(queryString);
        return query.getResultList();
    }
}

3. Spring Data JPA實體基礎映射

3.1 表映射

@Entity //表示該類爲JPA實體類
@Table(name="t_user")   //對應數據庫中哪張表
public class User {

@Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")  //對應數據庫表中哪一個列字段及對該字段的自定義
    private String phone;

3.2 主鍵映射

@Id   //標明主鍵
@GeneratedValue   //主鍵生成策略
@Column(name="id_")
private Integer id;    

更多的主鍵生成策略,詳見3.6 的整體代碼

3.3 字段映射和約束條件

@Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")  //對應數據庫中哪一個列及對該字段的自定義
    private String phone;

3.4 單實體多表格存儲

一般一個實體對應於一個表格,即表格中的全部的實體屬性都存放於一張表,若是將實體的屬性分配到多個表格存放,就涉及到單實體多表格存儲

@Entity
@Table(name="t_user",catalog="",schema="")
@SecondaryTables({    //指明存放的第二張表
    @SecondaryTable(name = "t_address",pkJoinColumns=@PrimaryKeyJoinColumn(name="address_id")) 
 })
public class User {
    
    @Column(name="name_", length=60, nullable=false,unique=true,insertable=false)
    private String name;
    
    //分表存儲
    @Column(table = "t_address", name="street_", length = 100) 
    private String street;

3.5 內嵌實體

在定義實體時可能須要將某幾個的屬性剝離出放到另一個實體中,以使程序更有層次感,而且當其餘實體也須要這幾個屬性時,咱們也不須要再定義這幾個屬性,把存放這幾個屬性的實體從新引用便可,操做方法以下:

@Embeddable   //標識該實體可嵌入到其餘實體中
public class Comment {
@Column(name="title_",length=100)
String title;
@Column(name="content_")
String content;
/* //被剝離出的屬性
@Column(name="title_",length=100)
    String title;
@Column(name="content_")
    String content;   
*/    
@Embedded   //引入該實體
@AttributeOverrides({   //羅列出全部須要從新命名的屬性
    @AttributeOverride(name = "title", column = @Column(name = "user_title")),
    @AttributeOverride(name = "content", column = @Column(name = "user_content"))
  })
    private Comment comment; 

內嵌實體在數據庫中不會一點單獨的表格存放,而是跟數組實體存放於同一表格中。

3.6 實體類代碼

import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;

@Entity
@Table(name="t_user",catalog="",schema="")
@SecondaryTables({ 
    @SecondaryTable(name = "t_address",pkJoinColumns=@PrimaryKeyJoinColumn(name="address_id")) 
 })
public class User {

    @Id   //標明主鍵
    @GeneratedValue   //主鍵生成策略
    @Column(name="id_")
    private Integer id;    
    
/*    @Id
    @GeneratedValue(generator="uuidGenerator")
    @GenericGenerator(name="uuidGenerator",strategy="uuid")
    @Column(name="id_",length=32)
    private String id;*/    
    
/*    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id_")
    private Integer id;*/    
        
    /*
     *     
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_")
    private Integer id;*/
    
    /*    
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idGenerator")
    @SequenceGenerator(name = "idGenerator",sequenceName="mySeq",allocationSize=1)
    @Column(name="id_")
    private Integer id;*/    
    
    /*    
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "userGenerator")
    @TableGenerator(name = "userGenerator",table="pk_generator",
                    pkColumnName="gen_name",
                    valueColumnName="gen_value",
                    pkColumnValue="user_pk",
                    initialValue=0,
                    allocationSize=1)
    @Column(name="id_")
    private Integer id;    
    */
    
    @Column(name="name_", length=60, nullable=false,unique=true,insertable=false)
    private String name;
    
    @Column(name="address_", length=60, nullable=false)
    private String address;
    
    @Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")
    private String phone;
    
    @Column(name="inCome_", precision=12, scale=2)
    private BigDecimal inCome;
    
    @Temporal(TemporalType.DATE)
    private Date birthday;
    //Date 日期型,精確到年月日,例如「2008-08-08」
    //Time 時間型,精確到時分秒,例如「20:00:00」
    //Timestamp 時間戳,精確到納秒,例如「2008-08-08 20:00:00.000000001」
    
    @Lob
    @Column(name="pic_")
    @Basic(fetch=FetchType.LAZY)
    private byte[] pic;
    
    @Lob
    @Column(name="note_")
    @Basic(fetch=FetchType.LAZY)
    private String note;
        

    //分表存儲
    @Column(table = "t_address", name="street_", length = 100) 
    private String street;
    //分表存儲
    @Column(table = "t_address", name="city_") 
    private String city; 
    //分表存儲
    @Column(table = "t_address", name="conutry_",length = 20) 
    private String conutry; 
    
    @Column(name="title_",length=100)
    String title;
    @Column(name="content_")
    String content;
    
    /*    
    @Embedded   //引入該實體
    @AttributeOverrides({   //羅列出全部須要從新命名的屬性
        @AttributeOverride(name = "title", column = @Column(name = "user_title")),
        @AttributeOverride(name = "content", column = @Column(name = "user_content"))
        })
    private Comment comment; 
    
    */
      
//省略get/set方法
}

4. Spring Data JPA實體高級映射

4.1 一對一實體映射的概念和實現方法

以下例,人員表(person)和地址表(adddress),person表是關係的擁有者,表中的address_id字段關聯着address表的主鍵id。

@Entity
public class Person {
  //
  @OneToOne   @JoinColumn(name="address_id",referencedColumnName="aid")//name:主表的外鍵字段; referencedColumnName:從表的主鍵   //若是關聯的字段有多個,採用以下註解   //@JoinColumns(value={@JoinColumn(name="address_id",referencedColumnName="aid"),@JoinColumn(name="address_id2",referencedColumnName="aid2")})    private Address address;

4.2 一對多實體映射的概念和實現方法

部門表(depart)和員工表(employee),一個部門能夠有多個員工,一對多關係能夠採用以下兩種實現方法。

4.2.1 中間表方式

建立中間表(depart_employee),表中存放兩個表的主鍵。經過部門id可查詢關聯員工的id,三張表存在兩個主外鍵關係。

@Entity
public class Depart {
    //

   @OneToMany
    @JoinTable(name = "depart_employee", //name:關聯表
        joinColumns = @JoinColumn(name = "depart_id",referencedColumnName="did"),  //joinColumns:關係的擁有者與關聯表的關係
        inverseJoinColumns = @JoinColumn(name = "employee_id",referencedColumnName="eid"))//inverseJoinColumns:關係的被擁有者與關聯表的關係
    private List<Employee> employees;

4.2.2 從表增長外鍵方式

在員工表(employee2)中添加一個depart_id字段,它做爲外鍵關聯部門表(depart2)的主鍵id。

@Entity
public class Depart2 {
  //
    
    @OneToMany
    @JoinColumn(name="depart_id",referencedColumnName="id")
    private List<Employee2> employee2s;

4.3 多對多實體映射的概念的實現方法

多對多的實現也是經過中間表,方法同一對多的中間表實現方式。

 

@Entity
public class Teacher {
//

    @ManyToMany 
    @JoinTable(name = "teacher_student", 
        joinColumns = @JoinColumn(name = "teacher_id",referencedColumnName="tid"), 
        inverseJoinColumns = @JoinColumn(name = "student_id",referencedColumnName="sid"))
    private List<Student> students;
    
@Entity
public class Student {
//
    
    @ManyToMany(mappedBy = "students") 
    private List<Teacher> teachers; 

4.4 級聯策略和懶加載

以@OneToOne爲例,當我但願刪除人員信息時,也將其地址信息刪除,則可以使用級聯策略;當我想要查詢人員信息(主實體)時,並不想同時查詢出其地址信息(子實體),能夠設置懶加載。

@Entity
public class Person {
@OneToOne(cascade={CascadeType.REFRESH,CascadeType.REMOVE},fetch=FetchType.LAZY)
//@JoinColumn(name="address_id",referencedColumnName="aid")
  private Address address;   

5. Spring Data JPA實體繼承

5.1 實體繼承的概念

繼承[extends]想必已不陌生,對於JPA來講,咱們不但要考慮如何實現Java端的繼承關係,還要考慮如何持久化到數據庫中。JPA爲此提供了三種策略,以下:

5.2 實體繼承策略

繼承關係如圖,繼承策略的註解主要應用於父類Item。

5.2.1 繼承策略之單一表策略

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
public class Item {

執行單一表策略會將全部實體的信息存放於一張表中,它的優勢是信息存放於一張表,查詢效率較高,缺點是大量字段爲空,浪費存儲空間。

若是類名過長或須要更改鑑別字段的名稱,可對鑑別字段及可選值自定義:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name="ITYPE",discriminatorType=discriminatorType.CHAR) //聲明鑑別字段的字段名,類型
@DiscriminatorValue("I") //該表在鑑別字段列顯示的值
public class Item {
@Entity
@DiscriminatorValue("P") 
public class Phone extends Item {
@Entity
@DiscriminatorValue("B") 
public class Book extends Item {

效果以下

5.2.2 繼承策略之鏈接表策略

@Entity
@Inheritance(strategy = InheritanceType.JOINED) 
public class Item {

 鏈接表策略會生成三張表,經過共享主鍵彼此關聯。

 這種策略避免了空字段的浪費,但因爲採用表關聯查詢,當數據量過大時,查詢效率較低。

5.2.3 繼承策略之每一個類策略

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 
public class Item {
/*    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) */
  
  @Id    
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "ItemGenerator")
  @TableGenerator(name = "ItemGenerator",table="pk_generator",
                    pkColumnName="gen_name",
                    valueColumnName="gen_value",
                    pkColumnValue="item_pk",
                    initialValue=0,
                    allocationSize=1)
    private Long id; 

每一個類策略其實是每一個類一個表策略,這種策略要求主鍵不能使用自增的方式,如上面的代碼,採用表中獲取的方式。

三張表各自存放本身的完整信息,表之間沒有任何的關聯關係。雖然他們各自存放各自的數據,但主鍵是連續的。即三個表共用一套主鍵生成策略(三個表的主鍵都從另外一個表中獲取)。

這種策略查詢效率高,同時也不存在大量空字段的浪費。

相關文章
相關標籤/搜索