Hibernate 5.3(四)[註解]

前言

本篇文章主要講解有關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 這個註解 用來指定主鍵生成策略:有下面四種
  1. GenerationType.TABLE 使用一個特定的數據庫表格來保存主鍵,持久化引擎通過關係數據庫的一張特定的表格來生成主鍵。該策略一般與另外一個註解一起使用@TableGenerator,@TableGenerator註解指定了生成主鍵的表(可以在實體類上指定也可以在主鍵字段或屬性上指定),然後JPA將會根據註解內容自動生成一張表作爲序列表(或使用現有的序列表)。如果不指定序列表,則會生成一張默認的序列表,表中的列名也是自動生成.
  2. GenerationType.SEQUENCE 在某些數據庫中,不支持主鍵自增長,比如Oracle,其提供了一種叫做"序列(sequence)"的機制生成主鍵。此時,GenerationType.SEQUENCE就可以作爲主鍵生成策略。
  3. GenerationType.IDENTITY 此種主鍵生成策略就是通常所說的主鍵自增長,數據庫在插入數據時,會自動給主鍵賦值,比如MYSQL可以在創建表時聲明"auto_increment" 來指定主鍵自增長
  4. 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;
	}

參考學習
參考學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章