SpringBoot-2-Jpa

2017年8月21日 我原本只想簡(jiǎn)單記錄一下springboot中應(yīng)用Jpa的簡(jiǎn)單操作。不想由于hibernate忘記太多了,不停的在查找記錄,于是就有了本文的復(fù)雜版本,感覺要寫清楚的東西太多了,都想分幾篇文章來寫清楚。限于表達(dá),至少最后我自己能看明白。

SpringBoot 使用Jpa是官方推薦,一般使用hibernate實(shí)現(xiàn)的Jpa。配置步驟如下

pom配置

<!-- jpa -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

application.properties

#jpa
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true

簡(jiǎn)單配置這些就夠了,其他的是屬于數(shù)據(jù)源的配置,這里也記錄下來,方便使用吧

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot-demo?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=xxxxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

書寫Entity

Entity是Jpa實(shí)現(xiàn)的一個(gè)比較重要的部分,詳細(xì)說明如下

Entity基本格式

借用hibernate實(shí)現(xiàn),所以注釋基本上與hibernate相同。@Entity標(biāo)簽表面是個(gè)Jpa識(shí)別的Entity,@Table 表明對(duì)應(yīng)的表
字段,如果與數(shù)據(jù)庫字段一致,則不用增加任何標(biāo)記。如果不一致,增加@Column標(biāo)簽

這里注意:jpa默認(rèn)的是@Column標(biāo)簽的名字與字段名字一樣,然后解析這個(gè)名字的時(shí)候,按照駝峰命名法去解析,既userName 會(huì)給解析成 user_name,如果在數(shù)據(jù)庫中的字段就是userName,則@Column(name = "username"),

這里可以考慮策略,還可以在屬性文件中這樣定義,

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
@Entity
@Table(name = "sys_user")
public class SysUser  implements Serializable {
    private static final long serialVersionUID = -3962204755050776389L;
    @Id
    private Integer id;
    private String password;
    private String phone;
    private String email;
    @Column(name = "enterprise_style")
    private String enterpriseStyle;
  
//省略 get set
}

常用的一些標(biāo)簽

@JsonIgnore

@JsonIgnore 的作用是json序列化時(shí)將java bean中的一些屬性忽略掉,序列化和反序列化都受影響。當(dāng)表間有One2Many或Many2One時(shí),會(huì)發(fā)生無限循環(huán)的場(chǎng)景,如何破?只要在Set方法前增加以下注解即可

@JsonIgnore  
    public Set xxxs() {
        return this.xxxYyyy;
    }

@Transient

@Transient表示該屬性并非一個(gè)到數(shù)據(jù)庫表的字段的映射,ORM框架將忽略該屬性;
如果一個(gè)屬性并非數(shù)據(jù)庫表的字段映射,就務(wù)必將其標(biāo)示為@Transient,否則ORM框架默認(rèn)其注解為@Basic;

表示該字段在數(shù)據(jù)庫表中沒有,但是一定要標(biāo)記在getXXX方法上。特別是bean可以修改數(shù)據(jù)庫表的時(shí)候。

@Transient
public int getAge() {
 return 1+1;
}

@JsonIgnoreProperties

@JsonIgnoreProperties 此注解是類注解,作用是json序列化時(shí)將java bean中的一些屬性忽略掉,序列化和反序列化都受影響。

@JsonIgnore

此注解用于屬性或者方法上(最好是屬性上),作用和上面的@JsonIgnoreProperties一樣。

@JsonFormat

此注解用于屬性或者方法上(最好是屬性上),可以方便的把Date類型直接轉(zhuǎn)化為我們想要的模式,比如@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")

@JsonSerialize

此注解用于屬性或者getter方法上,用于在序列化時(shí)嵌入我們自定義的代碼,比如序列化一個(gè)double時(shí)在其后面限制兩位小數(shù)點(diǎn)。

@JsonDeserialize

此注解用于屬性或者setter方法上,用于在反序列化時(shí)可以嵌入我們自定義的代碼,類似于上面的@JsonSerialize

這一部分可以參見 https://www.cnblogs.com/softidea/p/5668697.html

Id 策略

@GeneratedValue:主鍵的產(chǎn)生策略,通過strategy屬性指定
主鍵產(chǎn)生策略通過GenerationType來指定。GenerationType是一個(gè)枚舉,它定義了主鍵產(chǎn)生策略的類型。

1、AUTO 自動(dòng)選擇一個(gè)最適合底層數(shù)據(jù)庫的主鍵生成策略。如MySQL會(huì)自動(dòng)對(duì)應(yīng)auto increment。這個(gè)是默認(rèn)選項(xiàng),即如果只寫@GeneratedValue,等價(jià)于@GeneratedValue(strategy=GenerationType.AUTO)。

2、IDENTITY 表自增長字段,Oracle不支持這種方式。

3、SEQUENCE 通過序列產(chǎn)生主鍵,MySQL不支持這種方式。

4、TABLE 通過表產(chǎn)生主鍵,框架借由表模擬序列產(chǎn)生主鍵,使用該策略可以使應(yīng)用更易于數(shù)據(jù)庫移植。不同的JPA實(shí)現(xiàn)商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表,Hibernate生成一個(gè)hibernate_sequences表,而TopLink則生成sequence表。這些表都具有一個(gè)序列名和對(duì)應(yīng)值兩個(gè)字段,如SEQ_NAME和SEQ_COUNT。
如果使用Hibernate對(duì)JPA的實(shí)現(xiàn),可以使用Hibernate對(duì)主鍵生成策略的擴(kuò)展,通過Hibernate的@GenericGenerator實(shí)現(xiàn)。

@GenericGenerator(name = “system-uuid”, strategy = “uuid”) 聲明一個(gè)策略通用生成器,name為”system-uuid”,策略strategy為”uuid”。

@GeneratedValue(generator = “system-uuid”) 用generator屬性指定要使用的策略生成器。

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
 
@Entity
@Table(name="csq_test")
public class Test {
    private Long id;
    @Id
    @GenericGenerator(name="idGenerator", strategy="uuid") //這個(gè)是hibernate的注解/生成32位UUID
    @GeneratedValue(generator="idGenerator")
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}

映射

舉例說明,在公司權(quán)限中會(huì)存在,公司,部門,員工,其中一個(gè)部門中有多個(gè)員工,公司有多個(gè)部門,類似于這種。
這些映射中都可以使用下面的參數(shù)

fetch=FetchType.LAZY為默認(rèn)的數(shù)據(jù)延遲加載,fetch=FetchType.EAGER為急加載

cascade={CascadeType.PERSIST,CascadeType.MERGE,
CascadeType.REFRESH,CascadeType.REMOVE}
其中:
CascadeType.PERSIST級(jí)聯(lián)新增(又稱級(jí)聯(lián)保存);
CascadeType.MERGE:級(jí)聯(lián)合并(級(jí)聯(lián)更新);
CascadeType.REMOVE:級(jí)聯(lián)刪除;
CascadeType.REFRESH:級(jí)聯(lián)刷新
CascadeType.ALL:以上四種都是;
一般采用CascadeType.MERGE:級(jí)聯(lián)合并(級(jí)聯(lián)更新)即可。默認(rèn)值是均不進(jìn)行關(guān)聯(lián)。

增加默認(rèn)的查詢條件可以在Set上增加@Where(clause="status = 1")

OneToOne

在一對(duì)一的關(guān)系中,只需在主控方內(nèi)注明@OneToOne,而被控方只是作為外鍵,不需任何特殊標(biāo)記

@OneToOne  
@JoinColumn(name="ACCOUNT_ID")  
private AccountEntity account;  

對(duì)應(yīng)的員工表上,存儲(chǔ)賬戶表的外鍵就可以了

image.png

在另一端如果想使用的話可以

@OneToOne(mappedBy="account")  
private EmployeeEntity employee;  

這樣維護(hù)的工作就都由employee來完成了。即保存employee時(shí),會(huì)同時(shí)保存account

OneToMany && ManyToOne

公司(組織)表相對(duì)于部門表是被控方,只需在被控方寫mappedBy,其值為主控方中引用的外鍵對(duì)象的名稱

@Entity
@Table(name = "costume_organization")
public class Organization extends AbstractEntity {
    private static final long serialVersionUID = 1L;

    @Column(nullable = false, length = 50)
    private String name; // 組織名稱

    @OneToMany(mappedBy = "organization")
    private Set<Department> departmentSet; // 部門集合
}

部門表相對(duì)于公司(組織)表是主控方,在主控方只需寫@ManyToOne即可,其對(duì)象名為被控表中mappedBy中的值。如果需要將公司表對(duì)象或者部門表對(duì)象轉(zhuǎn)為JSON數(shù)據(jù)的時(shí)候,為了防止出現(xiàn)無限循環(huán)包含對(duì)方,需要在部門表(多的一方)的引用公司表(少的一方)對(duì)象的set方法上寫上注解@JsonBackReference(或者,在對(duì)應(yīng)的一方的bean上,寫上@JsonIgnore)

@Entity
@Table(name = "costume_department")
public class Department extends AbstractEntity {
    private static final long serialVersionUID = 1L;

    @Column(nullable = false, length = 50)
    private String name; // 部門名稱

    
    private Organization organization; // 組織外鍵
@ManyToOne(optional = false)//這個(gè)放get上,放字段上有時(shí)候不好用
    getOrganization(){
}
    @ManyToMany
    private Set<Member> memberSet; // 用戶表外鍵

    public Organization getOrganization() {
        return organization;
    }

    @JsonBackReference
    public void setOrganization(Organization organization) {
        this.organization = organization;
    }
}

ManyToMany

@ManyToMany 注釋:表示此類是多對(duì)多關(guān)系的一邊,mappedBy 屬性定義了此類為雙向關(guān)系的維護(hù)端,注意:mappedBy 屬性的值為此關(guān)系的另一端的屬性名。
例如,在Student類中有如下方法:
被控方:

@ManyToMany(fetch = FetchType.LAZY, mappedBy = "students")
public Set<Teacher> getTeachers() {
return teachers;
}

那么這里的“students”就是Teachers的一個(gè)屬性,通常應(yīng)該是這樣的:
Set<Student> students;
另一端的getStudents方法如下所示:
主控方:

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")},
inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName ="studentid")})
public Set<Student> getStudents() {
return students;
}

@ManyToMany 注釋表示Teacher 是多對(duì)多關(guān)系的一端。@JoinTable 描述了多對(duì)多關(guān)系的數(shù)據(jù)表關(guān)系。name 屬性指定中間表名稱,joinColumns 定義中間表與Teacher 表的外鍵關(guān)系。上面的代碼中,中間表Teacher_Student的Teacher_ID 列是Teacher 表的主鍵列對(duì)應(yīng)的外鍵列,inverseJoinColumns 屬性定義了中間表與另外一端(Student)的外鍵關(guān)系。

可以通過上面的定義看到有三個(gè)表學(xué)生表--老師表--老師學(xué)生中間表
以上提到主控方和被控方。。有人不贊同這種寫法:
理由是:
1.既然是多對(duì)多關(guān)系。。為什么還要分主動(dòng)方和被動(dòng)方? 2.為什么需要?jiǎng)h除老師后才級(jí)聯(lián)中間表。。。請(qǐng)注意:以上定義方法時(shí),刪除學(xué)生是無法級(jí)聯(lián)刪除中間表的。
正確的寫法應(yīng)該是兩邊都用主控方的寫法:只是joinColumns和inverseJoinColumns屬性的地方互換就可以了。

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName = "studentid")},
inverseJoinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName ="teacherid")})
public Set<Teacher> getTeachers() {
return teachers;
}

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")},
inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName ="studentid")})
public Set<Student> getStudents() {
return students;
}

注意

這個(gè)可以不使用mappyBy,但是一定是一方是fetch = FetchType.LAZY,如果兩方都是 fetch = FetchType.EAGER 系統(tǒng)不能啟動(dòng),會(huì)報(bào)錯(cuò),特別說明一下。

Jpa對(duì)象的繼承映射策略

在企業(yè)級(jí)開發(fā)中,經(jīng)常會(huì)碰到的一種對(duì)象是這樣的,實(shí)體公司、部門、人員,都會(huì)有ID、code、name等公共屬性,本著面向?qū)ο蟮奶幚矸椒?,在java世界里,一般都會(huì)抽象出一層來,比如稱為 BaseBean,或者 BaseEntity,這樣第一可以通過base來統(tǒng)一處理通用的CURD,第二,通過 extends BaseEntity 可以少寫很多代碼。
這樣的邏輯在Jpa這邊如何處理呢?其實(shí)也可以轉(zhuǎn)換一下,這種關(guān)系如何反應(yīng)到數(shù)據(jù)庫上?
這個(gè)問題從頭說,一般來說實(shí)體和表的映射關(guān)系分為3種

  • Single-table :這是繼承映射中的缺省策略,在不特別指明的情況下,系統(tǒng)默認(rèn)就是采用這種映射策略進(jìn)行映射的。這個(gè)策略的映射原則就是父類包括子類中新添加的屬性全部映射到一張數(shù)據(jù)庫表中,數(shù)據(jù)庫表中有一個(gè)自動(dòng)生成的字段用來存儲(chǔ)區(qū)分不同的子類的信息
  • Joined-subclass:這種映射策略中,繼承關(guān)系中的每一個(gè)實(shí)體類,無論是具體類 (concrete entity) 或者抽象類 (abstract entity),數(shù)據(jù)庫中都有一個(gè)單獨(dú)的表與他對(duì)應(yīng)。子實(shí)體對(duì)應(yīng)的表中不含有從根實(shí)體繼承而來的屬性,它們之間通過共享主鍵的方式進(jìn)行關(guān)聯(lián)。
  • Table-per-concrete-class :這個(gè)策略就是將繼承關(guān)系中的每一個(gè)實(shí)體映射到數(shù)據(jù)庫中的一個(gè)單獨(dú)的表中,與“Joined”策略不同的是,子實(shí)體對(duì)應(yīng)的表中含有從根實(shí)體繼承而來的屬性。

這一部分是抄來的,就抄到底吧(臉紅一下下),因?yàn)樵膶懙膶?shí)在是好,這樣省得有誤解,來源已經(jīng)標(biāo)明在文章的最后。

下文舉例種會(huì)應(yīng)用的例子的UML圖


image.png

Single Table

單表(Single-Table)映射是繼承映射中的缺省映射策略,在不加說明的情況下,也就是不在根實(shí)體 (root entity) 中指定映射策略的時(shí)候默認(rèn)就是使用的這種映射策略。在本例中根實(shí)體 (root entity) 指的是實(shí)體 Item 類。
Item 實(shí)體類沒有用 @Inheritance 這個(gè)注解 (annotation) 進(jìn)行注釋,說明在這個(gè)繼承關(guān)系中使用的是單表映射策略。同時(shí) Item 實(shí)體類是 Phone 實(shí)體類和 Magazine 實(shí)體類的根實(shí)體 (root entity),它們繼承了 Item 的屬性并且擁有自己的獨(dú)有的屬性。

@Entity 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
  // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}

對(duì)應(yīng)的ER圖

image.png

對(duì)應(yīng)的數(shù)據(jù)

image.png

單表映射中除了將所有子類中的屬性和父類集中在同一個(gè)表中之外,還多添加了一個(gè) DTYPE 的列,這個(gè)列主要是為了區(qū)別子類的,他的缺省屬性是 String 類型,缺省值就是子實(shí)體 (entity) 類的類名。數(shù)據(jù)庫中記錄的一個(gè)片斷,DTYPE 列的值默認(rèn)就是類的名字,它是用來區(qū)分本行的記錄屬于繼承關(guān)系的中的哪個(gè)類的。DTYPE 的值是 Item 的行,就是由根實(shí)體持久化而來的,以此類推。
用來區(qū)分子類的字段也是可以自定義的,如下:

@Entity 
@DiscriminatorColumn(name="DISC",discriminatorType=DiscriminatorType.CHAR) 
@DiscriminatorValue("I") 
public class Item implements Serializable { 
 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
 
    // Getters and Setters 
 
}
@Entity 
@DiscriminatorValue("M") 
public class Magazine extends Item { 
 
   private String isbn; 
   private String publisher; 
 
   // Getters and Setters 
 
}
@Entity 
@DiscriminatorValue("P") 
public class Phone extends Item { 
 
   private String factory; 
   private Float DurationTime; 
 
    // Getters and Setters 
 
}

這樣定義后,數(shù)據(jù)庫實(shí)際如下

image.png

這種策略對(duì)實(shí)體之間的多種關(guān)聯(lián)關(guān)系能提供很好的支持,同時(shí)在查詢方面也有很好的效率。但是這種映射策略在數(shù)據(jù)庫表中會(huì)有很多的空字段的存在。這樣勢(shì)必會(huì)造成數(shù)據(jù)庫資源的大量浪費(fèi),同時(shí)這個(gè)映射策略也要求子類中的所有屬性也必須是可空 (null able) 的。

Joined-subclass

這種映射策略,繼承結(jié)構(gòu)中的每一個(gè)實(shí)體 (entity) 類都會(huì)映射到數(shù)據(jù)庫中一個(gè)單獨(dú)的表中,也就是說每個(gè)實(shí)體 (entity) 都會(huì)被映射到數(shù)據(jù)庫中,一個(gè)實(shí)體 (entity) 類對(duì)應(yīng)數(shù)據(jù)庫中的一個(gè)表。其中根實(shí)體 (root entity) 對(duì)應(yīng)的表中定義了主鍵 (primary key),所有的子類對(duì)應(yīng)的數(shù)據(jù)庫表都要共同使用這個(gè)主鍵,同時(shí)這個(gè)表中和單表映射策略一樣還定義了區(qū)分列 (DTYPE)。

@Entity 
@Inheritance(strategy=InheritanceType.JOINED) 
@Table(name="Item_Joined") 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
}

對(duì)應(yīng)ER圖

image.png

這種映射策略和缺省的單表映射策略唯一的區(qū)別就是在根實(shí)體中使用 @Inheritance 注解 (annotation) 并指定使用 Joined 映射策略。在繼承比較多時(shí),查尋起來效率就會(huì)差一些,因?yàn)樵诓樵兊倪^程中需要多表的連接,連接的表數(shù)越多,查詢效率越低下。DType關(guān)鍵字同單表策略

Table-per-concrete-class

這種映射策略和連接映射策略很類似,不同的是子類對(duì)應(yīng)的表中要繼承根實(shí)體 (root entity) 中的屬性,根實(shí)體 (root entity) 對(duì)應(yīng)的表中也不需要區(qū)分子類的列,表之間沒有共享的表,也沒有共享的列

@Entity 
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) 
@Table(name="Item_TPC") 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
 
}

對(duì)應(yīng)的ER圖

image.png

按上面配置的話,每個(gè)子表對(duì)應(yīng)的根屬性都是一樣的,如果要修改數(shù)據(jù)庫中的字段名稱可以進(jìn)行重寫,如下例子

@Entity 
@AttributeOverrides({ 
   @AttributeOverride(name="id", 
                     column=@Column(name="Phone_id")), 
   @AttributeOverride(name="title", 
                     column=@Column(name="Phone_title")), 
   @AttributeOverride(name="description", 
                     column=@Column(name="Phone_desc")), 
}) 
public class Phone extends Item { 
 
   private String factory; 
   private Float DurationTime; 
 
    // Getters and Setters 
 
}

抽象實(shí)體類 (Abstract entity)

上面的例子中所使用的 Item 是個(gè)具體類 (concrete class),并且使用 @Entity 注解 (annotation) 進(jìn)行了注釋。那么當(dāng) Item 類不是具體類 (concrete class),而是一個(gè)抽象類 (abstract class) 的時(shí)候,也就是當(dāng) Item 類的聲明中使用了 abstract 關(guān)鍵字的時(shí)候,是如何影射的呢?事實(shí)上根實(shí)體是否是抽象實(shí)體類,在數(shù)據(jù)庫中映射成的表沒有任何區(qū)別。也就是說上面的例子中如果根實(shí)體類 Item 是個(gè)抽象實(shí)體類,使用了 abstract 關(guān)鍵字的話,在數(shù)據(jù)庫中生成的表和上面的例子是相同的。唯一的區(qū)別就是,如果根實(shí)體是抽象實(shí)體類的話,就不能使用 new 關(guān)鍵字來生成這個(gè)實(shí)體類的對(duì)象了。他們的區(qū)別只是在 Java 語言語法上的區(qū)別,在持久化上沒有任何區(qū)別。

非實(shí)體類 (Nonentity)

非實(shí)體類 (Nonentity) 也叫瞬態(tài)類 (transient class),就是普通的 POJO 類,沒有使用 @Entity 注解 (annotation) 注釋,這種類在持久化的時(shí)候不會(huì)被映射到數(shù)據(jù)庫中,因?yàn)楦鶕?jù)之前介紹過的持久化的原則,一個(gè)類如果想被持久化到數(shù)據(jù)庫中,必須使用 @Entity 注解。

個(gè)人感覺,這種好像實(shí)際用途不大。

Mapped SupperClass

JPA 有一種特殊的類叫做 Mapped Supper class,這種類不是實(shí)體類,他與實(shí)體類的區(qū)別就是用 @MappedSuperclass 注解來替代 @Entity 注解,其他方面沒有變化。

@MappedSuperclass 
@Inheritance(strategy=InheritanceType.JOINED) 
public class Employee { 
   @Id 
   @GeneratedValue 
   private Long id; 
   private String name; 
   private String depart; 
 
   // Getters and Setters 
 
}

這樣在數(shù)據(jù)庫中不用存在Employee對(duì)應(yīng)的表。其他一切都可以正常使用

我比較喜歡這個(gè)類型

Repository接口

繼承JpaResposity的原理及基本用法

JpaResposity這個(gè)接口是一個(gè)核心的接口,直接繼承這個(gè)接口,就可以完成CURD的基本操作了。接口繼承的路徑如下圖:

Paste_Image.png

最高層的Repository<T,ID>是一個(gè)空接口,我們定義的數(shù)據(jù)訪問類只要實(shí)現(xiàn)這個(gè)接口,這個(gè)數(shù)據(jù)訪問類就可以被spring data所管理,就此可以使用spring為我們提供操作方法(在原來的spring data中我們需要配置很多和Spring Data Repository相關(guān)的設(shè)置,但是現(xiàn)在有了spring boot,全部都已經(jīng)自動(dòng)配置好了)。這個(gè)接口要實(shí)現(xiàn)有兩個(gè)泛型參數(shù),第一個(gè)T表示實(shí)體類,第二個(gè)表示主鍵的類型,
其中 JpaRepository 繼承了2個(gè)接口 PagingAndSortingRepository 和QueryByExampleExecutor

public interface StudentRepository extends JpaRepository<Student,Integer> {

    @Query("select s from Student s where s.id=?1")
    public Student loadById(int id);

    //根據(jù)地址和年齡進(jìn)行查詢
    public List<Student> findByAddressAndAge(String address, int age);
    //根據(jù)id獲取對(duì)象,即可返回對(duì)象,也可以返回列表
    public Student readById(int id);
    //根據(jù)id獲取列表,這里如果確定只有一個(gè)對(duì)象,也可以返回對(duì)象
    public List<Student> getById(int id);
    //根據(jù)id獲取一個(gè)對(duì)象,同樣也可以返回列表
    public Student findById(int id);
}

這個(gè)接口實(shí)現(xiàn)了JpaRepository接口,這里有兩種使用JpaRepository的基本方法

  • 第一種方法增加了一個(gè)@Query的annotation,通過這個(gè)聲明,Spring Data JPA就知道該使用什么HQL去查詢數(shù)據(jù),?1表示用方法中的第一個(gè)參數(shù)。
  • 第二種方法我們并沒有定義任何的Annotation,在Spring Data JPA中提供了一種衍生查詢,只要函數(shù)的聲明有findBy,getBy,readBy即可,findByAddressAnAge表示根據(jù)address和age進(jìn)行查詢,注意大小寫以及與對(duì)應(yīng)Bean的屬性對(duì)應(yīng),方法的第一個(gè)參數(shù)就是address,第二個(gè)參數(shù)就是age,這些方法的返回值可以是一個(gè)列表,也可以是一個(gè)對(duì)象,Spring Data JPA會(huì)自動(dòng)根據(jù)返回類型來進(jìn)行處理。我們不用寫實(shí)現(xiàn)類,Spring Data JPA會(huì)自動(dòng)幫助我們實(shí)現(xiàn)查詢。

Query 基本的使用方法

JPQL 與 Hibernate的HQL類似

  • @Query 注解,標(biāo)注在方法上,優(yōu)先于 @NameQuery,也優(yōu)先于在xml中定義的。
  • 最基本使用方法如下
 @Query("select u from User u where u.emailAddress = ?1")  
 User findByEmailAddress(String emailAddress); 
  • Like使用如下
 @Query("select u from User u where u.firstname like %?1")   
List<User> findByFirstnameEndsWith(String firstname);
  • 原生的SQL
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)   
User findByEmailAddress(String emailAddress);

原生SQL目前不支持動(dòng)態(tài)排序,如果使用分頁的查詢,可以考慮如下方式

@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",    
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",    
nativeQuery = true)   
Page<User> findByLastname(String lastname, Pageable pageable); 
  • @Query中使用排序
public interface UserRepository extends JpaRepository<User, Long> {
   @Query("select u from User u where u.lastname like ?1%")   
    List<User> findByAndSort(String lastname, Sort sort);
    @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")   
    List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
 }
repo.findByAndSort("lannister", new Sort("firstname"));
repo.findByAndSort("stark", new Sort("LENGTH(firstname)")); //這個(gè)是錯(cuò)誤示例,不能這么用,需要參考下一條
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); 
repo.findByAsArrayAndSort("bolton", new Sort("fn_len"));           

  • 使用參數(shù)名稱
 @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")   
User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname); 
  • Query 帶修改
@Modifying 
@Query("update User u set u.firstname = ?1 where u.lastname = ?2") 
int setFixedFirstnameFor(String firstname, String lastname);

Jpa根據(jù)名稱生成查詢

這個(gè)是spring-data-jpa最常見的部分了,不多解釋,留一個(gè)備份在,被查即可。

image.png
image.png

Repository為什么只有接口就可以直接使用

這個(gè)問題我解釋起來有些信心不足,不過可以肯定的是,我知道是什么,為什么還不是很清楚。
在Spring-data-Jpa中有個(gè)默認(rèn)的實(shí)現(xiàn)類org.springframework.data.jpa.repository.support.SimpleJpaRepository

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID extends Serializable>
        implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
//內(nèi)容省略
}

EntityManager 是Jpa中操作數(shù)據(jù)庫的類,在原生的Hibernate中叫做Session,在MyBatis中叫做SqlSession。
Spring-data通過JpaRepositoryFactory將SimpleJpaRepository注入。所以我們不需要寫實(shí)現(xiàn)類就可以了。

Jpa自定義查詢

自定義查詢,jpa已經(jīng)幫我們完成了很多,只要我們自己寫自己需要的條件就可以了,一般會(huì)寫一個(gè)匿名類,重寫toPredicate方法來完成。初步先記錄下來。

@Override
    public Page<Student> search(final Student student, PageInfo page) {
        return studentRepository.findAll(new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                
                Predicate stuNameLike = null;
                if(null != student && !StringUtils.isEmpty(student.getName())) {
                    stuNameLike = cb.like(root.<String> get("name"), "%" + student.getName() + "%");
                }
                
                Predicate clazzNameLike = null;
                if(null != student && null != student.getClazz() && !StringUtils.isEmpty(student.getClazz().getName())) {
                    clazzNameLike = cb.like(root.<String> get("clazz").<String> get("name"), "%" + student.getClazz().getName() + "%");
                }

                return cb.and(stuNameLike,clazzNameLike);
            }
        }, new PageRequest(page.getPage() - 1, page.getLimit(), new Sort(Direction.DESC, page.getSortName())));
    }

再來一個(gè)例子,(xx or xx)and (ss or tt)的樣子如何處理

public void testSpecificaiton2() {
//第一個(gè)Specification定義了兩個(gè)or的組合
Specification<Student> s1 = new Specification<Student>() {
    @Override
    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        Predicate p1 = criteriaBuilder.equal(root.get("id"),"2");
        Predicate p2 = criteriaBuilder.equal(root.get("id"),"3");
        return criteriaBuilder.or(p1,p2);
    }
};
//第二個(gè)Specification定義了兩個(gè)or的組合
Specification<Student> s2 = new Specification<Student>() {
    @Override
    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        Predicate p1 = criteriaBuilder.like(root.get("address"),"zt%");
        Predicate p2 = criteriaBuilder.like(root.get("name"),"foo%");
        return criteriaBuilder.or(p1,p2);
    }
};
//通過Specifications將兩個(gè)Specification連接起來,第一個(gè)條件加where,第二個(gè)是and
List<Student> stus = studentSpecificationRepository.findAll(Specifications.where(s1).and(s2));

    Assert.assertEquals(1,stus.size());
    Assert.assertEquals(3,stus.get(0).getId());
}

Jpa 分頁和排序

Spring-data-Jpa,幫我們實(shí)現(xiàn)了分頁和排序,原則和上面的查詢是一樣的,我們可以在接口中定義,Spring-data-jpa會(huì)有一個(gè)默認(rèn)的實(shí)現(xiàn)。

分頁

分頁主要有三個(gè)接口需要實(shí)現(xiàn)

  • PagingAndSortingRepository
  • Pageable
  • Page
    其中,如果XXRepository j繼承自 JpaRepository,那么就已經(jīng)繼承了 PagingAndSortingRepository。 Pageable 是需要傳入的參數(shù)的接口,它的實(shí)現(xiàn)類是PageRequest,PageRequest有3個(gè)構(gòu)造方法,如下
//這個(gè)構(gòu)造出來的分頁對(duì)象不具備排序功能
public PageRequest(int page, int size) {
    this(page, size, (Sort)null);
}
//Direction和properties用來做排序操作
public PageRequest(int page, int size, Direction direction, String... properties) {
    this(page, size, new Sort(direction, properties));
}
//自定義一個(gè)排序的操作
public PageRequest(int page, int size, Sort sort) {
    super(page, size);
    this.sort = sort;
}

在自定義的Repository中如下定義就可以實(shí)現(xiàn)分頁了

Page<Student> findByAge(int age, Pageable pageable);

排序

Sort

//可以輸入多個(gè)Sort.Order對(duì)象,在進(jìn)行多個(gè)值排序時(shí)有用
public Sort(Sort.Order... orders)
//和上面的方法一樣,無非把多個(gè)參數(shù)換成了一個(gè)List
public Sort(List<Sort.Order> orders)
//當(dāng)排序方向固定時(shí),使用這個(gè)比較方便,第一個(gè)參數(shù)是排序方向,第二個(gè)開始就是排序的字段,還有一個(gè)方法第二個(gè)參數(shù)是list,原理相同
public Sort(Sort.Direction direction, String... properties)

Jpa 自定義Repository和創(chuàng)建自己的BaseRepository

Jpa 自定義Repository

主要遵循Jpa的規(guī)范,接口命名為 StudentRepositoryCustom,實(shí)現(xiàn)類命名為StudentRepositoryImpl。目前覺得意義不大,暫時(shí)不詳細(xì)研究這一部分。

創(chuàng)建自己的BaseRepository

對(duì)于網(wǎng)上很多的文章來說,都要經(jīng)過下面的幾步

  • 創(chuàng)建BaseRepository接口 繼承 JpaRepository
  • 創(chuàng)建 實(shí)現(xiàn)類,繼承SimpleJpaRepository 并實(shí)現(xiàn)BaseRepository
  • 創(chuàng)建工廠,用來注入自己的實(shí)現(xiàn)類
  • 修改springboot的啟動(dòng)方法,或者修改配置文件,調(diào)用自己創(chuàng)建的工廠。
@NoRepositoryBean
public interface BaseRepository<T,ID extends Serializable> extends JpaRepository<T,ID> {
}

需要注意的是@NoRepositoryBean,這個(gè)表示該接口不會(huì)創(chuàng)建這個(gè)接口的實(shí)例(我們?cè)瓉矶x的StudentPageRepository這些,Spring Data JPA的基礎(chǔ)組件都會(huì)自動(dòng)為我們創(chuàng)建一個(gè)實(shí)例對(duì)象,加上這個(gè)annotation,spring data jpa的基礎(chǔ)組件就不會(huì)再為我們創(chuàng)建它的實(shí)例)。

但是,我目前個(gè)人感覺來說,不需要這么多步驟,我們只要實(shí)現(xiàn)第一步,再定義一些方法,基本可以由spring-data-jpa來實(shí)現(xiàn)就好了,再寫一個(gè)基礎(chǔ)的Service來實(shí)現(xiàn)一些通用的方法。

參考資料

http://blog.csdn.net/lyg_2012/article/details/70195062
http://www.cnblogs.com/dreamroute/p/5173896.html
https://www.ibm.com/developerworks/cn/java/j-lo-hibernatejpa/index.html
https://www.ibm.com/developerworks/cn/java/j-lo-jpaprimarykey/
http://blog.csdn.net/xiao_xuwen/article/details/53420835
http://blog.csdn.net/xiao_xuwen/article/details/53579353

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容