用hibernate+註解實現各種關係映射

本文轉自:http://blog.csdn.net/bigtree_3721/article/details/42343639

基本環境準備

    我們先來看看一個具體的JPA工程示例。要運行這個示例,我們需要如下的類庫和軟件安裝配置好:

    類庫: EclipseLink, mysql-connector-j

    數據庫: Mysql

    開發環境:eclipse

    因爲JPA是一個公開的規範,所以有不同的實現。我們需要一個JPA的類庫。這裏我們引用瞭如下類庫:EclipseLink  (可以讓eclipse 幫我們下載去)。

    在下載EclipseLink包後,解壓。我們需要將裏面jlib目錄下的eclipselink.jar以及jlib/jpa目錄裏的java.persistence.x.jar引入到工程中就可以。

後面我們定義代碼裏的映射關係對應的底層實現就是由這兩個包實現。 既然要實現ORM,肯定少不了數據庫。這裏我們用了比較傳統的mysql數據庫。所以少不了也需要引用mysql的jdbc驅動:mysql-connector-javax.jar

    準備好了這些之後,我們創建一個JPAProject的java工程。整體的項目結構如下圖:

    這裏爲了方便工程引用類庫,首先將這些庫文件拷貝到工程下面的一個lib目錄裏,再將這些庫引入到工程中。

persistence.xml

    在前面配置好基本的類庫之後我們還有一個需要關心的就是,既然我們是ORM,就有一個要映射到數據庫的地方。這裏是怎麼映射到數據庫的呢?具體又映射到哪個數據庫呢?這些都是在persistence.xml文件定義的。我們在創建好工程之後的src目錄裏建立一個META-INF的目錄,然後在這個目錄裏建立一個persistence.xml文件。程序連接數據庫和對象映射的配置信息就是通過讀取這個目錄下的文件來實現的。我們來看一個persistence.xml的示例:

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence   
  4.     http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"  
  5.     version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">  
  6.     <persistence-unit name="EmployeeService" transaction-type="RESOURCE_LOCAL">  
  7.         <class>model.PersonInformation</class>  
  8.         <properties>  
  9.             <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />  
  10.             <property name="javax.persistence.jdbc.url"  
  11.                 value="jdbc:mysql://localhost:3306/simpleDb" />  
  12.             <property name="javax.persistence.jdbc.user" value="root" />  
  13.             <property name="javax.persistence.jdbc.password" value="root" />  
  14.   
  15.             <!-- EclipseLink should create the database schema automatically -->  
  16.             <property name="eclipselink.ddl-generation" value="create-tables" />  
  17.             <property name="eclipselink.ddl-generation.output-mode"  
  18.                 value="database" />  
  19.         </properties>  
  20.     </persistence-unit>  
  21. </persistence>  

    這裏都是xml的配置項,看起來也很容易懂。在一個<persistence-unit>的單元裏定義了這個unit的名字以及詳細的數據庫連接驅動,數據庫用戶名,密碼。前面<class>裏面定義的是需要映射到數據庫的具體實體類。比如model.Employee就對應於我們在package model裏定義的Employee類。因爲我們要連的數據庫是mysql,這裏的javax.persistence.jdbc.driver值被設爲com.mysql.jdbc.Driver。而我們具體要連接的數據庫名字在javax.persistence.jdbc.url對應的值裏面定義了,爲simpleDb。爲了後面實際程序運行的時候能夠讀寫這個庫,我們需要事先在數據庫裏創建simpleDb。

    OK,有了這些基本的設置。我們就能連上數據庫進行實際的操作了。

實體對象定義

    現在,我們需要定義一個可以具體實例化到數據庫裏的對象。我們定義一個PersonInformation的類如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Entity;  
  4. import javax.persistence.Id;  
  5.   
  6. @Entity  
  7. public class PersonInformation {  
  8.     @Id  
  9.     private int id;  
  10.     private String name;  
  11.     private int age;  
  12.       
  13.     public int getId() {  
  14.         return id;  
  15.     }  
  16.     public void setId(int id) {  
  17.         this.id = id;  
  18.     }  
  19.     public String getName() {  
  20.         return name;  
  21.     }  
  22.     public void setName(String name) {  
  23.         this.name = name;  
  24.     }  
  25.     public int getAge() {  
  26.         return age;  
  27.     }  
  28.     public void setAge(int age) {  
  29.         this.age = age;  
  30.     }  
  31. }  

    這裏只是很簡單的一個entity bean對象定義。我們針對每個可以操作的對象定義了get, set方法。這裏比較有意思的地方是我們用了兩個annotation,一個是@Entity,一個是@Id。這裏的@Entity表示一個可以序列化映射的的對象。如果我們希望這個對象被映射到數據庫中的某個表,則必須要加上這個annotation。而@Id則表示對應表的主鍵。我們建一個表要求有對應的主鍵。這裏指定id爲主鍵。如果我們不指定主鍵的話則運行的時候會出錯。有興趣的可以嘗試一下。

    有了這些定義之後,我們就可以來使用他們了。這裏是通過他們訪問數據庫的代碼:

Java代碼  收藏代碼
  1. package main;  
  2.   
  3. import javax.persistence.EntityManager;  
  4. import javax.persistence.EntityManagerFactory;  
  5. import javax.persistence.Persistence;  
  6.   
  7. import model.PersonInformation;  
  8.   
  9. public class Main {  
  10.     private static final String PERSISTENCE_UNIT_NAME = "EmployeeService";  
  11.     private static EntityManagerFactory factory;  
  12.       
  13.     public static void main(String[] args) {  
  14.         factory = Persistence.createEntityManagerFactory(  
  15.                 PERSISTENCE_UNIT_NAME);  
  16.         EntityManager em = factory.createEntityManager();  
  17.         em.getTransaction().begin();  
  18.         PersonInformation person = new PersonInformation();  
  19.         person.setId(1);  
  20.         person.setAge(30);  
  21.         person.setName("fred");  
  22.         em.persist(person);  
  23.         em.getTransaction().commit();  
  24.         em.close();  
  25.     }  
  26. }  

    第一個需要創建的對象是EntityManagerFactory, 這裏通過Persistence.createEntityManagerFactory方法。而這個"EmployeeService"是哪裏來的呢?我們看前面的persistence.xml文件,那裏有<persistence-unit name="EmployeeService" transaction-type="RESOURCE_LOCAL">這個定義。EmployeeService就是對應到這裏一個persistence-unit的名字。他們必須一致。實際上,如果我們需要訪問多個庫的話,在配置文件裏也可以定義多個persistence-unit。有了這個factory之後我們再創建一個EntityManager對象。

    爲了使得對象的創建成爲一個事務來提交,我們通過em.getTransaction().begin(); em.getTransaction().commit();這兩個方法來完成整個數據插入的過程。

    現在運行程序,我們再去查看數據庫的話,則會發現如下的信息:

    回顧一下我們前面定義的PersonInformation對象的信息,我們定義的字段都是小寫格式的。映射到數據庫裏包括表名和表裏面字段名都是大寫格式的。前面我們指定了id字段爲主鍵之後,對應的表裏這個字段就有了primary key not null的限制。

修改

    從前面的運行結果,我們可以看到一些實體對象和數據庫表之間的映射關係。可是在實際情況中,有時候我們希望數據庫表名或者裏面的字段名能夠更加可以定製化一點。不是簡單的將對象裏的字段變成大寫的。這樣可以增加一點可讀性,那我們該怎麼修改呢?

    很顯然,我們需要指定對象應該對應哪個表名以及每個屬性應該對應什麼名字。這是修改後的代碼:

 

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Column;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.GeneratedValue;  
  6. import javax.persistence.GenerationType;  
  7. import javax.persistence.Id;  
  8. import javax.persistence.Table;  
  9.   
  10. @Entity  
  11. @Table(name="PersonInformation")  
  12. public class PersonInformation {  
  13.     @Id  
  14.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  15.     @Column(name="id")  
  16.     private int id;  
  17.       
  18.     @Column(name="name")  
  19.     private String name;  
  20.       
  21.     @Column(name="age")  
  22.     private int age;  
  23.       
  24.     public int getId() {  
  25.         return id;  
  26.     }  
  27.     public void setId(int id) {  
  28.         this.id = id;  
  29.     }  
  30.     public String getName() {  
  31.         return name;  
  32.     }  
  33.     public void setName(String name) {  
  34.         this.name = name;  
  35.     }  
  36.     public int getAge() {  
  37.         return age;  
  38.     }  
  39.     public void setAge(int age) {  
  40.         this.age = age;  
  41.     }  
  42. }  

    和前面的代碼比起來,我們這裏增加了幾個描述屬性。一個是@Table,這裏通過它來設定對應的數據庫表名字是什麼。@Column這個用來設定對應的數據庫字段名。還有一個比較有意思的地方就是我們在id字段增加了@GeneratedValue(strategy = GenerationType.IDENTITY)這個描述屬性。它表示什麼意思呢?它表示這個主鍵的值可以自動來生成,而後面的GenerationType.IDENTITY表明它的生成方式是自動增長,類似於auto increment.

    針對前面修改之後我們再來看數據庫的運行結果:

    這樣,經過前面這些步驟的描述,我們已經能夠完成一個簡單的JPA示例了。也因此知道了將對象如何映射到數據庫表中。

各種關係映射

    前面的示例是簡單的將一個實體對象映射到數據庫中。在很多實際的情況下,我們並不是簡單的一個對象映射,往往對象和對象之間還存在着一定的關聯關係。我們在做ORM的時候,也需要針對不同的情況進行考慮。這個就牽涉到數據庫中間表或者實體對象之間的關係。總的來說,實體對象之間的關係無非爲以下幾種:一對一,一對多,多對多。而對於多對一的情況來說,從另外一個角度來看也是一種一對多的關係。

一對一

    我們都知道,對於不管是哪種對應的關係,實際上他們在對應到數據庫的實現裏,我們是可以有幾種不同的映射實現方式的。我們以一對一的關係開始。假設我們有一個Employee的實體對象。它同時有一個屬性是Address。這個Address和它是一對一的映射關係。那麼在數據庫的實現裏,我們該怎麼考慮呢?

    我們實際上有幾種實現方式。一種很簡單,既然他們反正是一對一的關係,我們完全可以把他們當成一個表來使。也就是說這個Address雖然是Employee的屬性,但是他們所有的屬性放到一個表裏是完全沒問題的。還有一種就是我們可以定義兩個分離的表,他們之間通過外鍵關聯。當然,除了這種,我們也可以定義一個單獨的映射表來保存他們之間的映射關係。

    現在我們來看看以上各種方式的實現:

合併成一個表

    我們定義的Employee對象如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Embedded;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.Id;  
  6. import javax.persistence.Table;  
  7.   
  8. @Entity  
  9. @Table(name="Employee")  
  10. public class Employee {  
  11.     @Id  
  12.     private int id;  
  13.     private String name;  
  14.     private long salary;  
  15.       
  16.     @Embedded  
  17.     private Address address;  
  18.       
  19.     public Address getAddress() {  
  20.         return address;  
  21.     }  
  22.   
  23.     public void setAddress(Address address) {  
  24.         this.address = address;  
  25.     }  
  26.   
  27.     public Employee() {}  
  28.       
  29.     public Employee(int id) { this.id = id; }  
  30. // ...    
  31. }  

    這裏出於篇幅的限制,省略了那些屬性的get, set方法。

 

Address類的定義實現如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Access;  
  4. import javax.persistence.AccessType;  
  5. import javax.persistence.Column;  
  6. import javax.persistence.Embeddable;  
  7.   
  8. @Embeddable  
  9. public class Address {  
  10.     private String street;  
  11.     private String city;  
  12.     private String state;  
  13.       
  14.     @Column(name="Zip_Code")  
  15.     private String zip;  
  16. //...     
  17. }  

    我們來看代碼里加入的標註。這裏Employee有一個Address的屬性。我們在Employee裏對這個屬性增加了@Embedded標註。而定義Address的類裏增加了@Embeddable標註。其中@Embedded表示Address元素被嵌入到Employee表中間。爲了能夠嵌入到Employee表中,我們還需要將Address屬性增加@Embeddable,表示它是能夠被嵌入到其他對象裏面的。

    我們運行如下的代碼:

Java代碼  收藏代碼
  1. package main;  
  2.   
  3. import javax.persistence.EntityManager;  
  4. import javax.persistence.EntityManagerFactory;  
  5. import javax.persistence.EntityTransaction;  
  6. import javax.persistence.Persistence;  
  7.   
  8. import model.Address;  
  9. import model.Employee;  
  10.   
  11. public class EmployeeTest {  
  12.   
  13.     public static void main(String[] args) {  
  14.           
  15.         EntityManagerFactory entityManagerFactory =    
  16.                 Persistence.createEntityManagerFactory("EmployeeService");  
  17.         EntityManager em = entityManagerFactory.createEntityManager();  
  18.         EntityTransaction userTransaction = em.getTransaction();  
  19.           
  20.         userTransaction.begin();  
  21.           
  22.         Employee employee = new Employee();  
  23.         employee.setName("frank");  
  24.         employee.setSalary(2000);  
  25.           
  26.         Address address = new Address();  
  27.         address.setCity("Beijing");  
  28.         address.setState("BJ");  
  29.         address.setStreet("Shuangying");  
  30.         address.setZip("100000");  
  31.         employee.setAddress(address);  
  32.         em.persist(employee);  
  33.         userTransaction.commit();  
  34.         em.close();  
  35.         entityManagerFactory.close();  
  36.     }  
  37. }  

    我們現在來看數據庫的結果:

 

外鍵約束

    在採用這種外鍵約束的時候我們需要首先考慮一下。根據我們定義的邏輯關係,可以認爲是Employee裏有Address這麼一個字段。那麼就相當於Employee裏要引用到Address的信息。而按照外鍵的定義,是一個表引用另外一個表的主鍵。那麼,我們就需要給Address定義一個主鍵,同時我們也要標註一下在Employee裏引用的Address字段該是什麼名字。我們定義的Employee如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.CascadeType;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.GeneratedValue;  
  6. import javax.persistence.GenerationType;  
  7. import javax.persistence.Id;  
  8. import javax.persistence.JoinColumn;  
  9. import javax.persistence.OneToOne;  
  10. import javax.persistence.Table;  
  11.   
  12. @Entity  
  13. @Table(name="Employee")  
  14. public class Employee {  
  15.     @Id  
  16.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  17.     private int id;  
  18.     private String name;  
  19.     private long salary;  
  20.       
  21.       
  22.     @OneToOne(cascade=CascadeType.ALL)  
  23.     @JoinColumn(name="address_id")  
  24.     private Address address;  
  25.       
  26.     public Address getAddress() {  
  27.         return address;  
  28.     }  
  29.   
  30.     public void setAddress(Address address) {  
  31.         this.address = address;  
  32.     }  
  33.   
  34.     public Employee() {}  
  35.       
  36.     public Employee(int id) { this.id = id; }  
  37.   
  38. // ...  
  39. }  

    這裏,我們增加了一個標註@OneToOne(cascade=CascadeType.ALL)和@JoinColumn(name="address_id")。@OneToOne表示他們是一對一的關係,同時cascade表示他們的級聯關係。如果我們仔細觀察一下的話會發現前面應用代碼裏有一個比較有趣的地方,我們em.persist()只是保存了employee對象。而對應的Address對象只是設定爲employee對象的一個屬性。我們希望是employee對象被保存到數據庫裏的時候address對象也自動保存進去。那麼我們就需要設定這個cascade的級聯訪問屬性。否則我們就需要顯式的利用em.persist()來保存address對象。這也就是爲什麼我們要用一個cascade的屬性。

  @JoinColumn裏面指定了Employee表裏引用到Address時關聯的名字是什麼。

    和前面的定義比較起來,Address的定義如要增加了一個id:

Java代碼  收藏代碼
  1. @Id  
  2. @GeneratedValue(strategy = GenerationType.IDENTITY)  
  3. private int id;  

    在我們運行程序前需要將Address加入到persistence.xml文件裏。因爲我們將它映射到一個單獨的表裏。我們再運行前面的程序,發現生成如下的表結構:

 

單獨的映射表

    採用這種映射方式的實現很簡單,只需要在Employee裏面做一點如下的修改:

Java代碼  收藏代碼
  1. @OneToOne(cascade=CascadeType.ALL)  
  2. @JoinTable(name="employee_address",joinColumns=@JoinColumn(name="address_id"),inverseJoinColumns=@JoinColumn(name="employee_id"))  
  3. private Address address;  

 其他地方都不需要改變。這裏的@JoinTable描述了關聯表的名字,他們相互關聯的時候裏面包含的字段。一個爲address_id,一個爲employee_id。

    運行結果如下圖:

一對多

    討論完了前面的一對一映射,我們再來看看一對多的關係。其實和前面一對一的關係很類似,我們可以猜想得到,既然前面描述一對一的關係有@OneToOne,我們這裏應該有@OneToMany的映射關係。而且比較有意思的是,在JPA裏,除了一對多的關係,也存在着一個多對一的關係描述。那麼,在哪些情況下該使用一對多來描述,哪些情況下用多對一來描述呢?我覺得這主要還是取決於我們設計的對象邏輯關係。通過對象的邏輯關係來設置不同的選擇。

    我們來看一個示例。

Many to one

     假定我們有一個Employee類,然後還有一個Department類。對於每個Employee來說,他屬於且僅屬於一個Department。這樣對於Employee和Department來說,他們就構成一個多對一的關係。而從Department的角度來說,他們則是一個一對多的關係。假定我們要求Employee對象有一個方法來取得他所在的Department。這樣從面向對象的角度來說,我們可能希望Employee有一個指向Department的引用。於是按照這種思路,我們設計Employee的代碼如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.CascadeType;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.GeneratedValue;  
  6. import javax.persistence.GenerationType;  
  7. import javax.persistence.Id;  
  8. import javax.persistence.JoinColumn;  
  9. import javax.persistence.ManyToOne;  
  10. import javax.persistence.Table;  
  11.   
  12. @Entity  
  13. @Table(name="Employee")  
  14. public class Employee {  
  15.     @Id  
  16.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  17.     private int id;  
  18.     private String name;  
  19.     private long salary;  
  20.       
  21.     @ManyToOne(cascade=CascadeType.ALL)  
  22.     @JoinColumn(name="Dept_Id")  
  23.     private Department department;  
  24.   
  25.     public Employee() {}  
  26.       
  27.     public Employee(int id) { this.id = id; }  
  28. // ...    
  29. }  

    這裏我們省略了對所有屬性get, set方法。這裏用了一個@ManyToOne的標註。並設定了映射的名字爲“Dept_Id”。

    而Department的定義則如下:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Column;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.GeneratedValue;  
  6. import javax.persistence.GenerationType;  
  7. import javax.persistence.Id;  
  8. import javax.persistence.Table;  
  9.   
  10. @Entity  
  11. @Table(name="Department")  
  12. public class Department {  
  13.     @Id  
  14.     @Column(name="Dept_Id")  
  15.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  16.     private long id;  
  17.       
  18.     @Column(name="name")  
  19.     private String name;  
  20. // ...  
  21. }  

    這個類的定義則沒有什麼特殊的。和前面定義的差不多。

    運行完之後的數據庫情況如下:

 

One to many 

    還是前面那個示例,不過這次我們換一個角度來考慮一下。前面是我們希望Employee有一個取得所在Department的屬性的方式。這裏我們希望從Department對象得到裏面所有的員工。從面向對象的角度來說,我們可以在Department裏面定義一個集合類來保存一系列的Employee對象。那麼,這裏兩個類的定義形式則如下:

Employee:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import javax.persistence.Entity;  
  4. import javax.persistence.GeneratedValue;  
  5. import javax.persistence.GenerationType;  
  6. import javax.persistence.Id;  
  7. import javax.persistence.Table;  
  8.   
  9. @Entity  
  10. @Table(name="Employee")  
  11. public class Employee {  
  12.     @Id  
  13.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  14.     private int id;  
  15.     private String name;  
  16.     private long salary;  
  17.   
  18.     public Employee() {}  
  19.       
  20.     public Employee(int id) { this.id = id; }  
  21. // ...  
  22. }  

    Employee的定義基本上不需要做什麼特殊的關係映射定義。

Department:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import java.util.List;  
  4.   
  5. import javax.persistence.CascadeType;  
  6. import javax.persistence.Column;  
  7. import javax.persistence.Entity;  
  8. import javax.persistence.FetchType;  
  9. import javax.persistence.GeneratedValue;  
  10. import javax.persistence.GenerationType;  
  11. import javax.persistence.Id;  
  12. import javax.persistence.JoinColumn;  
  13. import javax.persistence.OneToMany;  
  14. import javax.persistence.Table;  
  15.   
  16. @Entity  
  17. @Table(name="Department")  
  18. public class Department {  
  19.     @Id  
  20.     @Column(name="Dept_Id")  
  21.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  22.     private long id;  
  23.       
  24.     @Column(name="name")  
  25.     private String name;  
  26.       
  27.     @OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)  
  28.     @JoinColumn(name="Dept_Id")  
  29.     private List<Employee> employees;  
  30.     // ...  
  31. }  

    這裏一個比較有意思的地方是在定義了employees的地方加了一個@OneToMany的標註。它同時也通過@JoinColumn說明了關聯的項。

    這裏我們運行的程序稍微修改了一下,代碼如下:

Java代碼  收藏代碼
  1. package main;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import javax.persistence.EntityManager;  
  7. import javax.persistence.EntityManagerFactory;  
  8. import javax.persistence.EntityTransaction;  
  9. import javax.persistence.Persistence;  
  10.   
  11. import model.Department;  
  12. import model.Employee;  
  13.   
  14. public class EmployeeTest {  
  15.   
  16.     public static void main(String[] args) {  
  17.           
  18.         EntityManagerFactory entityManagerFactory =    
  19.                 Persistence.createEntityManagerFactory("EmployeeService");  
  20.         EntityManager em = entityManagerFactory.createEntityManager();  
  21.         EntityTransaction userTransaction = em.getTransaction();  
  22.           
  23.         userTransaction.begin();  
  24.           
  25.         Employee employee = new Employee();  
  26.         Department dept = new Department();  
  27.         dept.setName("Information");  
  28.   
  29.         List<Employee> employees = new ArrayList<Employee>();  
  30.           
  31.         employee.setName("frank");  
  32.         employee.setSalary(2000);  
  33.         employees.add(employee);  
  34.           
  35.         employee = new Employee();  
  36.         employee.setName("fred");  
  37.         employee.setSalary(3000);  
  38.         employees.add(employee);  
  39.         dept.setEmployees(employees);  
  40.   
  41.         em.persist(dept);  
  42.         userTransaction.commit();  
  43.         em.close();  
  44.         entityManagerFactory.close();  
  45.     }  
  46. }  

    我們寫入了一個Employee的集合。所以最後運行結果生成的表和結果如下: 

    和我們前面一對一映射的關係類似。我們也可以將一對多或者多對一的關係用一箇中間關係表來保存。在這裏實現就很簡單,之需要將Department裏面的@JoinColumn去掉就可以了。因爲在JPA裏,如果我們設定了兩個對象之間的一對多屬性關係,它會默認生成一箇中間表,並且表名以兩個表的名字加一個下劃線拼接起來。

多對多

    對於多對多的場景,看起來它會顯得更復雜一些。實際上則未必。因爲當我們典型的針對這種關係來設計數據庫表的時候,肯定會考慮將他們拆分出一箇中間的映射表來。這樣的話,基本上要實現這樣關係的映射,肯定就只有拆分映射表這麼一條路。這裏定義了另外一個示例。假定Student和Department是一個多對多的關係。

這裏,我們定義了Student:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5.   
  6. import javax.persistence.Entity;  
  7. import javax.persistence.GeneratedValue;  
  8. import javax.persistence.GenerationType;  
  9. import javax.persistence.Id;  
  10. import javax.persistence.JoinColumn;  
  11. import javax.persistence.JoinTable;  
  12. import javax.persistence.ManyToMany;  
  13.   
  14. @Entity  
  15. public class Student {  
  16.   @Id @GeneratedValue(strategy=GenerationType.IDENTITY)  
  17.   private int id;  
  18.   private String name;  
  19.   
  20.   @ManyToMany   
  21.   @JoinTable(name="Student_Dept",   
  22.       joinColumns=@JoinColumn(name="Stut_ID"),  
  23.       inverseJoinColumns=@JoinColumn(name="DEPT_ID"))    
  24.   private Collection<Department> departments;  
  25.   public Student() {  
  26.     departments = new ArrayList<Department>();  
  27. }  
  28.   
  29.   public void addDepartment(Department department) {  
  30.     if (!getDepartments().contains(department)) {  
  31.         getDepartments().add(department);  
  32.     }  
  33.     if (!department.getStudents().contains(this)) {  
  34.       department.getStudents().add(this);  
  35.     }  
  36.   }  
  37.   
  38.   public String toString() {  
  39.     return "\n\nID:" + id + "\nName:" + name ;  
  40.   }  
  41. // ...  
  42. }  

    這裏省略了元素的get, set方法。爲了實現對象的雙向關聯,這裏定義的addDepartment需要將自己加入到目標Department對象的列表裏。另外,我們在對多的關係部分添加了一個@ManyToMany的標註,同時也指定@JoinTable的屬性。

    而對於Department:

Java代碼  收藏代碼
  1. package model;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5.   
  6. import javax.persistence.Entity;  
  7. import javax.persistence.GeneratedValue;  
  8. import javax.persistence.GenerationType;  
  9. import javax.persistence.Id;  
  10. import javax.persistence.ManyToMany;  
  11.   
  12. @Entity  
  13. public class Department {  
  14.     @Id @GeneratedValue(strategy=GenerationType.IDENTITY)  
  15.     private int id;  
  16.     private String name;  
  17.   
  18.     @ManyToMany(mappedBy="departments")  
  19.     private Collection<Student> students;  
  20.       
  21.     public Department(){  
  22.       students = new ArrayList<Student>();  
  23.     }  
  24.       
  25.     public void addStudent(Student student) {  
  26.       if (!getStudents().contains(student)) {  
  27.           getStudents().add(student);  
  28.       }  
  29.       if (!student.getDepartments().contains(this)) {  
  30.           student.getDepartments().add(this);  
  31.       }  
  32.     }  
  33.   
  34.     public String toString() {  
  35.         return "Department id: " + getId() +   
  36.                ", name: " + getName();  
  37.     }  
  38. // ...  
  39. }  

    這裏引用Student集合的地方也用了一個@ManyToMany的標註,同時,裏面還設置了一個mappedBy屬性。這個屬性有什麼用呢?它是指定我們這個students的集合是映射到Student類裏面的departments列表。這樣後面生成的對象才找到對應的映射一方。還有一個就是,既然是多對多的映射,在Student裏面定義了jointable的屬性,這和我們在Department裏面定義有什麼差別呢?

    這個問題在於我們想定義這個關係的擁有方。我們把jointable的定義放在哪個類這個類就成爲了擁有方。那麼這個擁有方來綁定和解除他們的關係。另外一方則不能用來綁定和解除他們的關係。

    我們調用的代碼實現如下:

Java代碼  收藏代碼
  1. package main;  
  2.   
  3. import java.util.List;  
  4.   
  5. import javax.persistence.EntityManager;  
  6. import javax.persistence.EntityManagerFactory;  
  7. import javax.persistence.Persistence;  
  8. import javax.persistence.Query;  
  9.   
  10. import model.Department;  
  11. import model.Student;  
  12.   
  13. public class Main {  
  14.   static EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");  
  15.   static EntityManager em = emf.createEntityManager();  
  16.   
  17.   public static void main(String[] a) throws Exception {  
  18.     em.getTransaction().begin();  
  19.       
  20.       
  21.     Student student = new Student();  
  22.     student.setName("Joe");  
  23.     em.persist(student);  
  24.       
  25.     Student student1 = new Student();  
  26.     student1.setName("Joe");  
  27.     em.persist(student1);  
  28.       
  29.     Department dept = new Department();  
  30.     dept.setName("dept name");  
  31.     dept.addStudent(student);  
  32.   
  33.     dept.addStudent(student1);  
  34.     em.persist(dept);  
  35.      
  36.     em.flush();  
  37.   
  38.     Query query = em.createQuery("SELECT e FROM Student e");  
  39.     List<Student> list = (List<Student>) query.getResultList();  
  40.     System.out.println(list);  
  41.       
  42.     query = em.createQuery("SELECT d FROM Department d");  
  43.     List<Department> dList = (List<Department>) query.getResultList();  
  44.     System.out.println(dList);  
  45.       
  46.     em.getTransaction().commit();  
  47.     em.close();  
  48.     emf.close();  
  49.       
  50.   }  
  51. }  

    這裏,我們添加了兩個Student對象到一個Department對象中。他們運行後的數據表結構如下:

    多對多關係對應的對象關係還有一個比較麻煩的地方在於。一旦建立了雙方的對象關係之後,就基本上形成了一個對象之間的循環引用。從面向對象的角度來說這不是一個合適的設計。另外,從內存管理的角度來看,這樣也容易導致內存泄漏。因爲我們一不小心就容易導致一些引用的對象沒有被釋放。在將這些關係映射到不同對象的時候,一定要非常的慎重。

總結

    以前使用一些語言和開發框架的時候用到ORM。這裏針對一個最基本的JPA示例一併探討了幾種典型的對象映射關係在JPA中的實現。有了這些對象關係的定義作爲基礎我們可以定義很多面向對象的方法而不用採取直接拼接sql字符串的方式與數據庫交互。由於要針對這些關係一一討論,本文的篇幅就會顯得長一些。

參考材料

pro jpa2

http://www.java2s.com/Tutorial/Java/0355__JPA/ManyToManyJoinTableJoinInverseJoinColumn.htm

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