前言
本篇文章主要講解有關Hibernate的註解,由於時間有限,我只能講解一些比較常用,還有一些,如果以後用到,會陸續補充進來的。
題外知識
說時間JPA 、EJB 這些都是啥呀,經常看到有些人掛在嘴邊。
- JPA: Java 持久層的API,主要爲我們提供了ORM對象的處理。Hibernate 就是在這個基礎上編寫的。
- EJB:是以前一個老的,裏面的實體Bean,已經逐漸被JPA替代,JPA的使用範圍更廣。
javax persistence or org hibernate annotations
相信使用過Hibernate 註解,自動提示的時候,會出現這兩種註解,那麼我們該使用哪種呢,看了上面的介紹,你應該瞭解實際上,Hibernate是基於JPA,org hibernate annotations 實際上在原先的註解上進行的修改成自己,但是我們使用的話一般還是使用javax persistence 的多,本文的介紹也是基於這個。
Hibernate 註解文檔哪裏找
如果你使用的是Hibernate 註解,那麼打開你當時下載的Hibernate的資料,裏面有一個D:\hibernate\hibernate-release-5.3.0.Beta1\documentation\javadocs\org\hibernate\annotations(自己對應去找)
如果是使用的javax persistence 註解,那就直接找到javaee 的api 文檔,即可看到。
註解放在字段ORget方法
在對一個類進行註解時,你可以選擇對它的的屬性或者方法進行註解,根據你的選擇,Hibernate的訪問類型分別爲 field或property. EJ3規範要求在需要訪問的元素上進行註解聲明,例如,如果訪問類型爲 property就要在getter方法上進行註解聲明, 如果訪問類型爲 field就要在字段上進行註解聲明.應該儘量避免混合使用這兩種訪問類型. Hibernate根據@Id 或 @EmbeddedId的位置來判斷訪問類型。
註解例子
爲避免大量代碼,我們註解就註解在字段上,沒有提供相應的set、get方法,需要讀者自行添加。
繼承
該模塊是繼承相關的註解
@MappedSuperclass
@MappedSuperclass
//申明成超類,不可以和@entity一起使用
public class Account {
@Column(name="balance")
private BigDecimal balance;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(name="interest_rate")
private BigDecimal interestRate;
}
@Entity(name="creditaccount")
public class CredittAccount extends Account {
@Column(name="limitcredit")
private BigDecimal limitCredit;
}
該註解主要用來進行繼承關係的實體映射,用來修飾父類
Account 是父類,該實體映射是不會成一張表,CredittAccount 是子類,映射的表包含父類的所有屬性和自身的。
該繼承關係未在數據庫進行鏡像,無法進行多態查詢,也就是說無法從父類獲取所有的子類。
單表繼承
所有子類只繼承於一個父類,所有的子類的屬性都存放在父類的表中。
@Entity(name="account")
@DiscriminatorValue("a")
@DiscriminatorColumn(name="type")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)//該註解策略,默認就是單表繼承
public class Account {
@Column(name="balance")
private BigDecimal balance;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(name="interest_rate")
private BigDecimal interestRate;
}
@Entity(name="creditaccount")
@DiscriminatorValue("ca")
//他們的id 都是繼承於account
public class CredittAccount extends Account {
@Column(name="limitcredit")
private BigDecimal limitCredit;
}
@Entity(name="DebitAccount")
@DiscriminatorValue("da")
@Table(name="debitaccount")
public class DebitAccount extends Account {
@Column(name="overdraftFee")
private BigDecimal overdraftFee;
}
DebitAccount da = new DebitAccount();
da.setBalance(new BigDecimal(11.2));
da.setInterestRate(new BigDecimal(11.9));
da.setOverdraftFee(new BigDecimal(23.4));
ss.save(da);
CredittAccount ca = new CredittAccount();
ca.setBalance(new BigDecimal(11.2));
ca.setInterestRate(new BigDecimal(11.9));
ca.setLimitCredit(new BigDecimal(35.6));
ss.save(ca);
Account account = new Account();
ca.setBalance(new BigDecimal(11.4));
ca.setInterestRate(new BigDecimal(15.9));
ss.save(account);
我們可以看到這種配置,父類也是可以映射成一張表,這點和之前的不一樣。子類的主鍵都是繼承於父類的,不需要單獨的定義。同時基於這種繼承關係,還會爲底層的數據表中添加一個DTYPE 的列,該列用於存放鑑別值的,這種帶有鑑別值列,是基於SINGLE_TABLE and JOINED 這兩種繼承策略。discriminator列包含標記值,用於告知持久層爲特定子類進行持久化操作。鑑別值不需要手動插入,在保存某一個實體的時候,會自動插入。
- @DiscriminatorColumn(name=“type”)
一般用於在父類(其實放子類也ok),定義鑑別值列的名稱。如果沒有設置,默認就是DTYPE。 - @DiscriminatorValue(“ca”)
指定某一個實體的鑑別值,一般用字符串居多。如果沒有設置,默認就是該實體@entity 裏面的name 。
List<DebitAccount> list = ss.createQuery("select a from account a where a.id=:id1").setParameter("id1", 2L).list();
//基於註解實體類的hql 查詢,需要注意 ,不在像映射文件配置 ,直接用類名,而是用你@entity 的name
for(DebitAccount da:list){
System.out.println(da.getOverdraftFee());
}
使用多態查詢,只要查詢一個父類表,即可返回子類的所有實例(子類所有字段均可以獲取)。
基於這種單表繼承的策略是比較好,所有的子類都在父類表中,所以屬性不在強制not null。
Joined table
每一個子類都將映射成一張表。
@Entity(name="account")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Account {
@Column(name="balance")
private BigDecimal balance;
@Id
private long id;
//這裏id 的生成策略不可以設置爲自動生成
@Column(name="interest_rate")
private BigDecimal interestRate;
}
@Entity(name="creditaccount")
@PrimaryKeyJoinColumn(name="account_id")
public class CredittAccount extends Account {
@Column(name="limitcredit")
private BigDecimal limitCredit;
}
@Entity(name="DebitAccount")
@Table(name="debitaccount")
@PrimaryKeyJoinColumn(name="account_id")
public class DebitAccount extends Account {
@Column(name="overdraftFee")
private BigDecimal overdraftFee;
}
採用這種映射關係,需要注意,子類的主鍵是依賴於父類主鍵,所以,父類id 不建議設置自動遞增,設置有異常,同時,子類,可以使用@PrimaryKeyJoinColumn 來設置子類的主鍵名稱,該主鍵是默認關聯父類的主鍵。如果,沒有設置該註解,子類的主鍵名默認和父類主鍵名一致。
添加操作:
Account account = new Account();
account.setBalance(new BigDecimal(11.4));
account.setInterestRate(new BigDecimal(15.9));
account.setId(1L);
ss.save(account);
DebitAccount da = new DebitAccount();
da.setBalance(new BigDecimal(1123.3));
da.setInterestRate(new BigDecimal(34.5));
da.setOverdraftFee(new BigDecimal(23.4));
da.setId(2L);//這裏不能是1,因爲每一個子表save ,都會先save 父表,父表的主鍵不就和上面重複了。
ss.save(da);
CredittAccount ca = new CredittAccount();
ca.setBalance(new BigDecimal(1138.3));
ca.setInterestRate(new BigDecimal(39.5));
ca.setLimitCredit(new BigDecimal(35.6));
ca.setId(3L);
ss.save(ca);
查看數據庫,我們發現每一個子類插入的時候,父類的屬性放在父表中(沒有的話,屬性爲NULL),子類的屬性放在子表中(這裏子類的屬性包含其他子類的屬性,不是該子類的屬性,是爲NULL),子表的主鍵關聯父表主鍵。
List<DebitAccount> list = ss.createQuery("select a from account a where a.id=:id1").setParameter("id1", 2L).list();
//基於註解實體類的hql 查詢,需要注意 ,不在像映射文件配置 ,直接用類名,而是用你@entity 的name
for(DebitAccount da:list){
System.out.println(da.getOverdraftFee());
}
通過打印sql,發現利用多態查詢,採用這種繼承策略,會通過left join 去多表查詢,採用join ,性能有所消耗。
Table per class
@Entity(name="account")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Account {
@Column(name="balance")
private BigDecimal balance;
@Id
private long id;
@Column(name="interest_rate")
private BigDecimal interestRate;
}
@Entity(name="creditaccount")
public class CredittAccount extends Account {
@Column(name="limitcredit")
private BigDecimal limitCredit;
}
@Entity(name="DebitAccount")
@Table(name="debitaccount")
public class DebitAccount extends Account {
@Column(name="overdraftFee")
private BigDecimal overdraftFee;
}
查看錶結構,你會發現,所有子表都有父類屬性,包含自己特有屬性。
添加操作:
Account account = new Account();
account.setBalance(new BigDecimal(11.4));
account.setInterestRate(new BigDecimal(15.9));
account.setId(1L);
ss.save(account);
DebitAccount da = new DebitAccount();
da.setBalance(new BigDecimal(1123.3));
da.setInterestRate(new BigDecimal(34.5));
da.setOverdraftFee(new BigDecimal(23.4));
da.setId(2L);// 雖然子表save ,父表的所有屬性都在子表中,父表沒有記錄,但是實際上有一個引用的(比如下面這個clazz_ 這個字段,我估摸着應該和這個有關,如果有明白的,可以告知我),所以不可以爲1。
select
/*select
account0_.id as id1_0_0_,
account0_.balance as balance2_0_0_,
account0_.interest_rate as interest3_0_0_,
account0_.overdraftFee as overdraf1_2_0_,
account0_.limitcredit as limitcre1_1_0_,
account0_.clazz_ as clazz_0_*/
ss.save(da);
CredittAccount ca = new CredittAccount();
ca.setBalance(new BigDecimal(1138.3));
ca.setInterestRate(new BigDecimal(39.5));
ca.setLimitCredit(new BigDecimal(35.6));
ca.setId(3L);
ss.save(ca);
多態查詢:
List<DebitAccount> list = ss.createQuery("select a from account a where a.id=:id1").setParameter("id1", 2L).list();
//基於註解實體類的hql 查詢,需要注意 ,不在像映射文件配置 ,直接用類名,而是用你@entity 的name
for(DebitAccount da:list){
System.out.println(da.getOverdraftFee());
}
從查詢sql ,我們可以看到,它是通過union,嵌套子查詢所有子表、父表。和上面的繼承關係相比,它沒有關聯父表的主鍵,但是我查詢的時候,卻能關聯子表,從查詢語句,我看到class_,這個字段,而我並沒有配置,所以,可能是靠該字段,還望其他知道人,賜教!
接口多態查詢
public interface TestInterface {
}
@Entity(name = "BookNew")
@Table(name = "book_new")
public class BookNew implements TestInterface {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="book_name")
private String name;
}
@Entity(name = "Blog")
@Table(name = "blog")
@Polymorphism(type=PolymorphismType.EXPLICIT)
public class Blog implements TestInterface {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "blog_site")
private String site;
}
基本註解映射關係,很簡單,就不做太多說明了。
List list = ss.createQuery("select a from com.example.test.bean.TestInterface a").list();
注意,在hql 中,查詢是面向對象的,由於TestInterface 沒有映射成表,所以在這裏需要寫接口的全限名,纔可以找到。對於繼承多態查詢,查詢父類一定會查詢所有子類。但是接口多態查詢,可以通過@Polymorphism(type=PolymorphismType.EXPLICIT),配置在實現類中,多態查詢就無法獲得該實體。
二級緩存
配置二級緩存
org.hibernate.cache.spi.RegionFactory 這個類提供了hibernate 關於緩存的。所以在hibernate 配置文件的屬性hibernate.cache.region.factory_class,要提供對應緩存的實現類,hibernate 支持主要的緩存JChache、Ehchache。
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<property name="hibernate.cache.use_second_level_cache" >true</property> //設置啓用hibernate 的二級緩存,如果沒有設置上面的,則默認區域工廠爲NoCachingRegionFactory,一樣二級緩存不起作用。
<property name="hibernate.cache.use_query_cache">false</property>//設置二級緩存的查詢緩存
其他還需要配置ehcache.xml,導入相應的jar,具體參看添加鏈接描述
經過上述的配置,就可以針對相應的實體,進行設置緩存:
@Entity(name = "Phone")
@Table(name = "phone")
@Cacheable
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Phone {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "phone_number", unique = true)
}
但是需要,注意的是,在hibernate 5.3 這樣的配置,會出現空指針異常,之前的版本,我看網上說沒啥問題,之後的版本我也沒去試,大家咋使用的時候注意一下,經過google,也是每找不到解決辦法,找到一個類似的問題,每找到解決辦法。添加鏈接描述,還望知道的人,賜教。
@NaturalId
自然Id,自然id不能成爲一個好的主鍵(代理鍵通常是首選),我們仍然可以像加載主鍵,獲取某一實體。
NaturalId Mapping
註解普通屬性
@Entity(name="Book")
@Table(name="book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="book_name")
private String bookname;
@NaturalId
private String isbn;
}
查看數據庫的表,發現通過該註解的屬性被映射成一個唯一性的索引。
註解組件屬性
@Entity(name="Book")
@Table(name="book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="book_name")
private String bookname;
@NaturalId
private Isbn isbn;
@Embeddable
static class Isbn implements Serializable{
private String isbn1;
private String isbn2;
}
}
NaturalId 怎麼也算Id,所以組件類需要實現序列化接口,同時重寫hashcode、equals方法。Book 表中多了組件類的兩個屬性,但是此時唯一性索引是isbn1和isbn2的組合。
註解多個屬性
@Entity(name="Book")
@Table(name="book")
public class Book1 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NaturalId
private String isbn;
@NaturalId
private String name;
}
多個被註解的屬性,聯合組成唯一性索引。
根據NaturalId去獲取實體
Book book = ss.byNaturalId(Book.class).using("isbn",new Book.Isbn("aa","bb")).load();
Book book1 = ss.bySimpleNaturalId(Book.class).load(new Book.Isbn("aa","bb"));
關聯關係
該模塊是涉及關聯關係的相關注解
many to one
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
//一個有多個電話
private List<Phone> phones = new ArrayList<Phone>();
}
public void addPhone(Phone phone){
//這個地方,你必須要設置的級聯,不然,它不會自動幫你save phone,不save
//就沒有持久化的狀態,所以這時你的add,根據就不不是插入數據庫中。
phones.add(phone);
phone.setPerson(this);//交給phone 去控制,這個地方如果不設置,那麼
//personid 將爲空
}
public void removePhone(Phone phone){
phones.remove(phone);
phone.setPerson(null);//交給phone 將關係清除
}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number")
private String number;
@ManyToOne()
private Person person;
}
Person p = new Person();
ss.save(p);
Phone p1 = new Phone();
p1.setNumber("123");
p.addPhone(p1);
ss.flush();//剛插入的,你也要等他flush 到數據庫,你在查
Phone p2 = ss.load(Phone.class, 1L);
p.removePhone(p2);//這邊刪除的實體必須要是持久化的狀態
//這邊刪除只是將personid 置爲null,設置orphanRemoval,就會主動幫你刪除phone 的條目,而不是講外鍵設置爲空。
Person p = ss.load(Person.class, 1L);
ss.delete(p);
有人會想下面的這個刪除和上面的刪除的區別在哪,上面的刪除是將滿足的person 和phone 關係刪除,通過orphanRemoval 來指定沒有外鍵引用(也就是外鍵爲空)的條目自動刪除,而且該屬性應該設置位置在person 實體中針對集合屬性。而且,你發現上面的刪除,都是基於面向對象的方法,但是下面的刪除方法是基於數據庫操作,下面的刪除會刪除所有外鍵依賴於該person 的phone 對象,因爲設置級聯cascade=CascadeType.ALL。
@many to one 註解主要映射實體之間多對一的關係,需要配合@JoinColumn的使用,因爲在實體關係中,多方會產生一個外鍵。而@JoinColumn 的name 是用來指定添加的外鍵列的列名,默認是關聯實體的主鍵,可以通過 referencedColumnName去指定。而foreignKey 的name 外鍵名字。(該註解可以不配置,就採用默認的)
在雙向關係中,我們在xml 中通過配置inverse 去設置控制方,在註解方式,我們可以設置mappedBy 去達到一樣的效果。
one to one
@Entity(name="Address")
@Table(name="address")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="detail")
private String detail;
}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number")
private String number;
@OneToOne(cascade=CascadeType.ALL)
//這裏指定級聯,就是一個人刪除了,地址也跟着沒有
@JoinColumn(name="address_id")
private Address address;
}
一對一關係,還是相對比較簡單的,不存在雙向啦,在某個實體中添加該註解,同時配合@JoinColumn 指定關聯實體的主鍵,這裏不會產生,不需要使用foreignKey 。
many to many
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@ManyToMany(mappedBy="persons",cascade=CascadeType.ALL)
//一個人有多個地址,一個地址可以對應多個人
private Set<Address> addresses = new HashSet<Address>();
}
@Entity(name="Address")
@Table(name="address")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="detail")
private String detail;
}
多對多插入操作:
Person person = new Person();
person.setName("laohe");
Address address = new Address();
address.setDetail("dawanglu");
person.getAddress().add(address);//控制權給person
ss.save(person);//設置級聯操作,直接保存person 即可。
多對多關係映射,會產生一箇中間表,包含兩個實體對應的主鍵。在多對多關係中,刪除某個實體,如果沒有設置級聯設置,不可以直接刪除,違反外鍵約束。
Person person1 = entityManager.find( Person.class, personId );
entityManager.remove( person1 );//這種刪除會引發異常
如果設置級聯操作,進行如下操作,對應的person、address、person_address 相應的記錄均會被刪除。
Person p = ss.get(Person.class,1L);
ss.delete(p)
雙向多對多關係
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@ManyToMany(mappedBy="persons")//將控制權交給address
//一個人有多個地址,一個地址可以對應多個人
private Set<Address> addresses = new HashSet<Address>();
}
@Entity(name="Address")
@Table(name="address")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="detail")
private String detail;
@ManyToMany()
private Set<Person> addresses = new HashSet<Person>();
}
在雙向的多對多關係中,我們在Phone 實體配,mappedBy ,將關係維護權交給了person 實體。這裏必須要設置一個mappedBy ,否則它會根據兩個many to many 的標籤去自動生成兩個中間表,這裏只需要一個。
在上面的配置中,我們如果執行下面的刪除:
Address address = ss.get(Address.class, 1L);
address.getPersons().clear();//因爲主控制權在address ,
執行之後,我們發現刪除是中間表,也就是刪除多對多之間的關係。
雙向的多對多的關係,我們還可以使用雙向的一對多的關係來表示,但是需要一張中間表去維護關係(這張表就不是自動生成的)。
@Entity(name = "Person")
@Table(name = "person")
public class Person implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "person",orphanRemoval=true)
// 一個人有多個地址,一個地址可以對應多個人
private Set<PersonAddress> addresses = new HashSet<PersonAddress>();
@Column(name = "person_name")
private String name;
}
@Entity(name = "Address")
@Table(name = "address")
public class Address implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "detail")
private String detail;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "address",orphanRemoval=true)
private Set<PersonAddress> persons = new HashSet<PersonAddress>();
}
@Entity(name = "PersonAddress")
@Table(name = "person_address")
/* 具有複合主鍵的實體,必須要實現序列化接口 */
public class PersonAddress implements Serializable {
@ManyToOne
@JoinColumn(name = "personid")
@Id
private Person person;
@ManyToOne
@Id
@JoinColumn(name = "addressid")
private Address address;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((address == null) ? 0 : address.hashCode());
result = prime * result + ((person == null) ? 0 : person.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PersonAddress other = (PersonAddress) obj;
if (address == null) {
if (other.address != null)
return false;
} else if (!address.equals(other.address))
return false;
if (person == null) {
if (other.person != null)
return false;
} else if (!person.equals(other.person))
return false;
return true;
}
}
PersonAddress pa = new PersonAddress();
Person p = new Person();
p.setId(1L);
Address a = new Address();
a.setId(2L);
pa.setPerson(p);
pa.setAddress(a);
PersonAddress pa1 = ss.load(PersonAddress.class, pa);//如何通過複合主鍵去查找某一實體
Person person = ss.load(Person.class,1L);
Address address1 = ss.load(Address.class, 2L);
//person.getAddresses().remove(pa1);//兩者選擇任意一個,均可以刪除滿足條件的PersonAddress 的關係。
//address1.getPersons().remove(pa1);
組件
對於符合對象,Hibernate 稱之爲components,JPA 稱之爲embeddables。
這部分的註解,包含3個:@Embeddable,@Embedded、@EmbeddedId
@Embeddable、@Embedded
@Entity(name="Book")
@Table(name="book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="book_name")
private String bookname;
private Publisher publisher;
@Embeddable
public static class Publisher{
private Location location;
}
@Embeddable
public static class Location{
@Column(name="publisher_city")
private String city;
}
}
上述數據庫執行之後,所有的字段將在一個表中,對於組件類是不需要定義主鍵的。對於組件類,我們建立將它定義在父實體類中,作爲靜態嵌套類存在。
組件類屬性的重寫
有時候,我們需要在一個父類實體中,多次引用該組件類,如果這時按照組件類默認的屬性name來,就會衝突,所以需要重寫。
@Entity(name="Book")
@Table(name="book")
@AttributeOverrides({
@AttributeOverride(name="epublisher.name",column=@Column(name="epublisher_name")),//epublisher 必須是你定義的屬性名
@AttributeOverride(name="ppublisher.name",column=@Column(name="ppublisher_name")),
})
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="book_name")
private String bookname;
private Publisher epublisher;//電子出版社
private Publisher ppublisher;//紙質出版社
@Embeddable
public static class Publisher{
@Column(name="publisher_name")
private String name;
}
}
如果涉及到組建類的關係(比如一對多等)的重寫,則需要使用@AssociationOverride。
另外,最好把屬性、關係的重寫的註解,放到class 上。
如果,你嫌這樣重寫屬性麻煩,那麼,還有一種簡單的方法:
直接應用命名衝突的策略,讓Hibernate 爲你解決,不用你自己麻煩。
@embeddedId
@Embeddable
public class CompitedId implements Serializable {
private String title;
private String name;
}
@Entity(name = "People")
@Table(name = "people")
public class People {
@EmbeddedId
private CompitedId id;
@Column(name="topic")
private String topic;
}
People p = new People();
CompitedId cId = new CompitedId("liu","lao");
p.setId(cId);
p.setTopic("ki");
ss.save(p);
ss.flush();
tt.commit();
People people =ss.load(People.class,new CompitedId("liu","lao"));
System.out.println(people.getTopic());
同樣的,對於複合主鍵類,重寫hashcode、equals,實現序列化接口,這些都是必須的。
在表中通過@EmbeddedId 去引用一個組件類型作爲複合主鍵。注意查詢的時候,還是要構造一個複合的主鍵去查找。
如果是採用的是HQL 查詢語句:
List list = ss.createQuery("select p from People p where p.id.name=:name1 and p.id.title=:title1").setParameter("name1","lao").setParameter("title1","liu").list();
雖然複合主鍵的屬性都是在person 表中,但是對於複合主鍵的屬性引用,不能直接p.name ,會提示找不到。
該複合主鍵可以和下面@IdClasss 對比着看。
Collection
下面的註解大部分和集合相關。
在hibernate 中,集合是不允許在嵌套集合類型。
常見的集合類型接口:java.util.Collection, java.util.List, java.util.Set, java.util.Map, java.util.SortedSet, java.util.SortedMap
@ElementCollection
下面直接演示集合的類型是組件類型,基礎類型在這就不做說明了。
@Entity(name = "Person")
@Table(name = "person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
List<Phone> List = new ArrayList<Phone>();
@Column(name = "person_name")
private String name;
@Embeddable
public static class Phone{
@Column(name="phone_name")
private String name;
}
}
之前,我們說過@Embeddable這個註解的class 是嵌套在主實體中,而在集合中是不一樣的,這時會新建一張表來存放phone 的屬性和主表person 的主鍵。
上面說過,集合中可以存放組件類型,或者是基本類型,但是我們更多是集合中存放的是某個實體,從而形成關聯關係。
Bags
是用來表示List 類型的集合接口,特點就是沒有順序。
這部分的例子,參看上面one to many 映射關係的例子,即可。
Order List
Bags 用來表示Java 的List 接口,但是它裏面不能保留有序的元素,所以我們提供下面註解,可以解決該問題。
@OrderBy 可以用於查詢集合子實體,按照子實體的某一個屬性,進行排序顯示。
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OrderBy("number")
@OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
//一個有多個電話
private List<Phone> phones = new ArrayList<Phone>();
}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number")
private String number;
@ManyToOne()
private Person person;
}
該註解查詢的時候,就相當於數據庫order by 關鍵字。@orderby 後面是指定集合子實體的屬性名。可以指定多個集合字實體的屬性名( @OrderBy(“name ASC, type DESC”))
@OrderColumn 用來指定某一個列作爲排序列
將@OrderBy 註解去除,修改如下,其他均不變:
@OrderColumn(name="order_id")
我們發現在Phone 表中,多出了該列,但是在查詢的語句中,我們可以看出,並沒有在數據庫層面上排序,而是加載集合對象,在內存中,進行排序,這點是和上面排序不同的地方。
我們在插入的Phone 表的時候,hibernate 會自動幫我們填充order_id 的值,該值是從0 開始遞增的。我們如果自己在數據庫設置值,也必須從0 開始,否則會報錯。
Set
Set 是集合但是不允許出現重複的條目。
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
//一個有多個電話
private Set<Phone> phones = new HashSet<Phone>();
}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number")
private String number;
@ManyToOne
private Person person;
}
集合中使用Set 接口,一定要注意,重寫子實體屬性(普通屬性即可)的hashcode和equals 方法,必須要保證不可以重複
Sorted sets
對於排序的Set,常使用Java 的SortedSet。對於SortedSet 類型的集合實體,必須要實現Comparable ,提供排序方法。
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
//一個有多個電話
@SortNatural
private SortedSet<Phone> phones = new TreeSet<Phone>();
}
@Entity(name="Phone")
@Table(name="phone")
public class Phone implements Comparable<Phone> {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number")
private String number;
@ManyToOne
private Person person;
@Override
public int compareTo(Phone o) {
// TODO Auto-generated method stub
return number.compareTo(o.number);
}
}
對於排序的集合set,必須用 @SortNatural 指定,同時集合實體實現Comparable 接口,如果想自己自定義排序方法,可以通過 @SortComparator(Xxxx.class),該類實現Comparator接口。
Map
主要對應於Java 的Map 接口
- Map 的鍵爲組件類,值爲普通值
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="person_name")
private String name;
@Temporal(TemporalType.TIMESTAMP)//用來指定Date 類型對應是時間戳
@CollectionTable(name="person_phone_date")//這個註解會把Map 的鍵值映射成一張表,可以沒有,就採用默認的,默認是關聯person 實體的主鍵
@ElementCollection//申明該屬性是集合類型
@Column(name="since")//這個column 映射的是map 的值
private Map<Phone,Date> map = new HashMap<Phone, Date>();
}
public void addSince(Phone phone,Date date){
map.put(phone, date);//這裏由於phone 作爲組件,所以直接設置值,它會跟着person 的持久化,(如果是實體,就要單獨持久化)一起到數據庫的。
}
public void removeSince(Phone phone){
map.remove(phone);
}
@Embeddable
public class Phone{
@Column(name="phone_number",unique=true)
private String number;
}
這裏的Phone 在集合中作爲組件類使用,這裏由於用了Phone 組建類作爲Map 的鍵,需要重寫hashcode、equals。
Person person = new Person();
person.setName("laotie");
Phone phone = new Phone();
phone.setNumber("122223455");
person.addSince(phone, new Date());//因爲上面將Date類型的值,映射成時間戳,那麼你這裏傳Date類型,會自動轉成時間戳。
ss.save(person);
查看數據庫表,可以看出person_phone_date 裏面包含person 的id 引用,phone 作爲組件類,所有的屬性也映射到表中,map 的value,since 也在該表中。
- 定製自己的map的鍵
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="person_name")
private String name;
@CollectionTable(name="person_phone_date",joinColumns=@JoinColumn(name="person_id1"))//這個註解會把Map 的鍵值映射成一張表
@ElementCollection
@MapKey(name="map_key")//用來指定map 鍵的列名
@Column(name="phone_number")//這個column 映射的是map 的值
private Map<Date,Integer> map = new HashMap<Date,Integer>();
這時你看到自己map 鍵的類型是Date 因爲沒有指定 @Temporal,這裏影響不大,hibernate會採用默認的Date 類型,這裏如果想重寫一個類作爲鍵的類型(這裏和鍵的類型是組件類,不是一個概念,不要亂),就需要@MapKeyType ,由於用的比較少,這裏就不演示了。
- Map 的鍵是接口類型
public interface PhoneInterface {
}
@Embeddable
public class MobilePhone implements PhoneInterface {
@Column(name="mobile_phone_number")
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="person_name")
private String name;
//一個有多個電話
@CollectionTable(name="person_mobile_phone",joinColumns=@JoinColumn(name="person_id"))//這個註解會把Map 的鍵值映射成一張表
@ElementCollection
@MapKeyClass(MobilePhone.class)//雖然下面是map 的鍵是接口類型,但是在這申明接口的實現類,注意MobilePhone需要使用 @Embeddable 註解。**由於是作爲map 鍵,所以最好重寫hashcode和equals方法**。
@Column(name="phone__mobile_number")//這個column 映射的是map 的值
private Map<PhoneInterface,String> map = new HashMap<PhoneInterface, String>();//因爲該map 的鍵是組件類(由多個屬性組成),顧不需要設置@mapkeycolumn
}
和上面一樣person_mobile_phone 包含person的主鍵 還有MobilePhone 的鍵的字段屬性,組件類的形式嵌套在該表中,以及map 的value 屬性。
添加查詢操作:
Person p = new Person();
p.setName("laotie");
MobilePhone mp = new MobilePhone();
mp.setNumber("12334");
p.addMobileNumber(mp,"smallliu");
ss.save(p);
Person p1 = ss.load(Person.class,1L);
Map<PhoneInterface, String> map = p1.getMap();
Set<PhoneInterface> set = map.keySet();
Iterator<PhoneInterface> iterator = set.iterator();
while(iterator.hasNext()){
MobilePhone mb = (MobilePhone) iterator.next();
System.out.println(mb.getNumber()+" "+map.get(mb));
}
- Map 的單向關係
請注意,當出現實體的時候,這時就會形成關係。
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="person_name")
private String name;
//這裏的一對多,不是那麼簡單,對應多個實體,而是一個人有多個map,這樣的關係,map 裏面包含實體
@OneToMany(cascade=CascadeType.ALL,orphanRemoval=true)
**//我們都知道一1對多的關係,無需設置中間表,這裏爲啥要設置一箇中間表不明白,還望他人指點。**
@JoinTable(
name="person_map_phone",
joinColumns=@JoinColumn(name="person_id"),//joinColumns操作外鍵關聯主表的主鍵,正是配置是單向關係,所以才知道person 是主表
inverseJoinColumns=@JoinColumn(name="phone_id")//操作外鍵關聯副表(兩個實中的另一個實體)的主鍵
)
@MapKeyColumn(name="since")
//實際上這裏map value 是一個實體,無法保存在中間表,所以,我們保存phone 主鍵
//因爲map 的value 是一個實體,無需爲它設置@column 設置value的列名
@MapKeyTemporal(TemporalType.TIMESTAMP)//將map 的key 的date 映射成時間戳
private Map<Date,Phone> map = new HashMap<Date,Phone>();
@Entity(name="Phone")
@Table(name="phone")
public class Phone{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="phone_number",unique=true)
private String number;
}
添加操作
在person 補充如下的方法:
public void addPhone(Phone p,Date date){
map.put(date,p);
//因爲設置map 的級聯屬性,保存person,會save phone 實體的
}
在這,我要說明一下的,就是@MapKeyColumn和@MapKey
@MapKeyColumn 對於基本類型(組件類型排除在外)用來指定map 的key 的列名
@MapKey 這個用來map 的key 是map value 裏面實體的某個屬性,如果map value 沒有該屬性名,則報錯。
- map 的雙向關係
由於是雙向關係,就比上面的配置容易很多,就不需要設置一箇中間表。(這也就是我不明白,上面爲啥要搞一箇中間表)
@Entity(name="Person")
@Table(name="person")
public class Person{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="person_name")
private String name;
@MapKeyEnumerated//用來指定map 的key 爲枚舉類型
@OneToMany(cascade=CascadeType.ALL,orphanRemoval=true,mappedBy="person")
@MapKeyColumn(name="enum_phone")//指定map key 對於的列名
private Map<EnumType,Phone> map = new HashMap<EnumType, Phone>();
public void addPhone(Phone p,EnumType e){
p.setPerson(this);//設置控制關係,控制權在phone
map.put(e,p);
}
}
@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "phone_number", unique = true)
private String number;
@ManyToOne
private Person person;
}
public enum EnumType {
RED,
YEELLO
}
通過上述的設置,可以不通過使用中間表,將map 的key 和value 都存放在phone 表中。
數組
hibernate 不支持本地數據庫的數組類型,支持Java 類型的數組,由於數組不具備類似集合的懶加載,從性能上看,不是很支持使用。
@Entity(name="Person11")
@Table(name="person11")
public class Person11 {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String[] phones;
}
查看數據庫,mysql底層給數組類型,用tinyblob tinyblob。
零散的一些註解
- @table 用來指定註解實體對應的表,這裏的name是可選,如果你不指定, 那麼數據庫的表名,就是你自己的實體類名(小寫)。該註解中有一個屬性uniqueConstraints,因爲一個表 可以有多個唯一約束,可以通過@UniqueConstraint ,來指定每一個唯一約束由哪些字段組成。這裏列名,就是你字段對應屬性配置的@column的name。
- @Transient 用來指定某個字段不需要持久化。
- @Assess Hibernate使用的訪問類型將是字段或屬性。即註解如果添加在getter方法,使用PROPERTY訪問,必須提供set、get方法;如果註解添加在字段上,則爲AccessType.FIELD,則對set、get方法無要求。 應避免在兩個字段和方法中混合註釋。 Hibernate將從@Id或@EmbeddedId的位置猜測訪問類型。 推薦使用PROPERTY。建議整個項目應該使用一致的訪問策略,避免引來不必要的麻煩。
- @Temporal 用來映射字段類型java.util.Date and java.util.Time(不要去定義java.sql包下的),裏面有三種類型,可以去映射,Date 含年月日,Time含時分秒,TIMESTAMP含年月日時分秒。
- @Id 用於指定某一個字段映射成主鍵,如果某個鍵需要申明成主鍵,只要用該屬性設置即可,無法在使用@column,這個是映射普通列。
- @GeneratedValue 這個註解 用來指定主鍵生成策略:有下面四種
- GenerationType.TABLE 使用一個特定的數據庫表格來保存主鍵,持久化引擎通過關係數據庫的一張特定的表格來生成主鍵。該策略一般與另外一個註解一起使用@TableGenerator,@TableGenerator註解指定了生成主鍵的表(可以在實體類上指定也可以在主鍵字段或屬性上指定),然後JPA將會根據註解內容自動生成一張表作爲序列表(或使用現有的序列表)。如果不指定序列表,則會生成一張默認的序列表,表中的列名也是自動生成.
- GenerationType.SEQUENCE 在某些數據庫中,不支持主鍵自增長,比如Oracle,其提供了一種叫做"序列(sequence)"的機制生成主鍵。此時,GenerationType.SEQUENCE就可以作爲主鍵生成策略。
- GenerationType.IDENTITY 此種主鍵生成策略就是通常所說的主鍵自增長,數據庫在插入數據時,會自動給主鍵賦值,比如MYSQL可以在創建表時聲明"auto_increment" 來指定主鍵自增長
- GenerationType.AUTO 把主鍵生成策略交給持久化引擎(persistence engine),持久化引擎會根據數據庫在以上三種主鍵生成策略中選擇其中一種。
該註解有一個屬性generator 是用來指定主鍵生成的東東,SequenceGenerator or TableGenerator 屬性的值,就是這些生產者name的字符串。
@TableGenerator 之前在講解主鍵生成的,有一種是通過數據庫表格,就是該註解去處理。裏面有的屬性即使用參看下圖:
生成器的表中只會有一個數據,這個數據的值會根據你的設置,每次自增的。
@SequenceGenerator 和@TableGenerator 很類似,就是它是針對oracle的主鍵的序列
-
@Entity 主要標識該類爲實體類,裏面有一個name,使用該註解,在hql可以識別,你想想hql 怎麼從查詢語句,知道某個對象、屬性的,就是靠它。這個每一個實體類還必須要添加該註解,否則,Hibernate找不到。,並不是所有的都要改註解,在一些組件類,是不需要,該註解只是針對實體類。如果不顯示添加name 屬性,默認就是不帶包名的該類名。
-
@Basic 主要是用於字段和column的基本映射,fetch 主要設置是否懶加載(Hibernate在創建一個實體Bean的實例時,不會即時將這個屬性的值從數據庫中讀出. 只有在該實體Bean的這個屬性第一次被調用時,Hibernate纔會去獲取對應的值),加載模式默認不是懶加載,optional 字段是否可以爲空。
-
@Enumerated 指定某個字段持久化成枚舉類型 ,你需要自己先定義枚舉類,它有兩種策略,EnumType.STRING 在持久化值的必須根據枚舉類的名字去插入,EnumType.ORDINAL 在持久化值的可以根據枚舉類屬性值定義的順序去插入。
-
@Lob 將數據庫字段映射成大的對象類型,大文本類型名稱爲clob,大二進制類型名稱爲blob。該註解既可以
映射二進制,也可以映射字符,看你字段的類型。(常用某個描述、自我介紹、文件、圖片),讀取該屬性需要使用inputstream。 -
@Column 將字段和列對應 。常用屬性:
name 可選,列名(默認值是屬性名)
unique 可選,是否在該列上設置唯一約束(默認值false)
nullable 可選,是否設置該列的值可以爲空(默認值false)
insertable 可選,該列是否作爲生成的insert語句中的一個列(默認值true)
updatable 可選,該列是否作爲生成的update語句中的一個列(默認值true)
columnDefinition 可選: 爲這個特定列覆蓋SQL DDL片段 (這可能導致無法在不同數據庫間移植)
table 可選,定義對應的表(默認爲主表)
length 可選,列長度(默認值255)
precision 可選,列十進制精度(decimal precision)(默認值0)
scale 可選,如果列十進制數值範圍(decimal scale)可用,在此設置(默認值0) -
@IdClass 用來指定一個複合主鍵。
public class CompitedId implements Serializable {
private String title;
private String name;
public CompitedId(String title, String name) {
this.title = title;
this.name = name;
}
public CompitedId() {
super();
// TODO Auto-generated constructor stub
}
@Entity(name = "People")
@Table(name = "people")
@IdClass(CompitedId.class)
public class People {
@Id
private String name;
@Id
private String title;
@Column(name="topic")
private String topic;
public People(String name, String title) {
this.name = name;
this.title = title;
}
}
People p = new People("liu","lao");
p.setTopic("ti");
ss.save(p);
ss.flush();
People p1 = ss.load(People.class,new CompitedId("lao","liu"));//注意對應後構造器參數的一致
//對於複合主鍵的記錄查詢,必須要構造一個複合主鍵對象
System.out.println(p1.getName());
如果是基於HQL查詢:
List list = ss.createQuery("select p from People p where p.name=:name1 and p.title=:title1").setParameter("name1","lao").setParameter("title1","liu").list();
System.out.println(list.size());
//這裏和@EmbeddedId,就有所不同,因爲不是嵌套組件對象,所以,可以直接引用。
在具體的表中通過@IdClass 來指定複合主鍵的類,通過還要重寫在這裏定義複合主鍵的屬性,必須和複合主鍵類的屬性一致,同時用@Id,分別去註解。
複合主鍵要保證唯一,必須要實現hashcode、equals方法,同時實現序列化接口
注意設置複合主鍵,就不可以設置主鍵遞增,必須是自己手動賦值。
注意複合主鍵類必須要提供默認的構造器,否則會報異常。
- @Index
該註解用於在表中,根據某些字段去產生相應的索引。
@Entity(name = "People")
@Table(name = "people",indexes={//一個表中可以有多個索引。
@Index(unique=false,columnList="first_name,last_name",name="name_index")//columnList 裏面的值應該是你對應屬性配置對應的列,一個索引可以參考多個列,所以可以有多個列值,用逗號區分,unique 用於指定該索引是否唯一,name 是指定索引的名字。
})
public class People {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="first_name")
private String firstname;
@Column(name="last_name")
private String lastname;
}