1. 程式人生 > 實用技巧 >hibernate基於註解的維護權反轉:@OneToMany(mappedBy=)

hibernate基於註解的維護權反轉:@OneToMany(mappedBy=)

mappedBy 對應 XML中的inverse

先貼出最初的程式碼:一些基本的註解,在一對多的關係上沒有使用JoinColumn和mappedBy屬性

部門類:主要是第33、34行

 1 package com.lizhou.entity.test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import javax.persistence.Column;
 7 import javax.persistence.Entity;
 8 import javax.persistence.GeneratedValue;
 9 import javax.persistence.GenerationType;
10 import javax.persistence.Id;
11 import javax.persistence.OneToMany;
12 import javax.persistence.Table;
13 
14 import org.hibernate.annotations.GenericGenerator;
15 
16 /**
17  * 部門:與員工一對多關係
18  * @author bojiangzhou
19  *
20  */
21 @Entity
22 @Table(name="department")
23 public class Department {
24     
25     @Id
26     @GeneratedValue(generator="_native")
27     @GenericGenerator(name="_native", strategy="native")
28     private int id; //ID
29     
30     @Column(length=20)
31     private String dname; //部門名稱
32     
33     @OneToMany
34     private List<Employee> employeeList = new ArrayList<>(); //部門下的員工集合
35 
36     // get/set方法59     
60 }

員工類:主要是第32、33行

 1 package com.lizhou.entity.test;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.Id;
 7 import javax.persistence.ManyToOne;
 8 import javax.persistence.Table;
 9 
10 import org.hibernate.annotations.GenericGenerator;
11 
12 /**
13  * 員工:與部門多對一關係
14  * @author bojiangzhou
15  *
16  */
17 @Entity
18 @Table(name="employee")
19 public class Employee {
20 
21     @Id
22     @GeneratedValue(generator="_native")
23     @GenericGenerator(name="_native", strategy="native")
24     private int id; //ID
25     
26     @Column(length=20)
27     private String ename; //員工姓名
28     
29     @Column(length=20)
30     private String phone; //電話
31     
32     @ManyToOne
33     private Department department; //所屬部門
34     
35     
36     //get/set方法67     
68 }

最初的註解配置裡,在一對多的關係上,即employeeList和department沒有使用JoinColumn。

看下圖,employee表會自動新增一個外來鍵列department_id,雖然關係對映上是正確了,但是有一個問題,資料庫裡多了一張表出來,這不是想要的結果。

解決方法:在employeeList和department欄位上加上@JoinColumn註解

1 @OneToMany
2 @JoinColumn(name="departmentId")
3 private List<Employee> employeeList = new ArrayList<>(); //部門下的員工集合

1 @ManyToOne//
2 @JoinColumn(name="departmentId")//
3 private Department department; //所屬部門

這樣一來的話就只有兩張表了,所以在一對多或者一對一的關係下,需要加上@JoinColumn來指定外來鍵列,避免生成一張中間表。

而且經試驗,多的一方(Employee)裡的department必須加上@JoinColumn,Department裡不加不會影響表的結構,不知道會不會有其它影響;

但是如果Employee屬於多的一方,如果沒有指定外來鍵列,還是會自動生成一個department_id外來鍵列。

接下來討論mappedBy屬性:mappedBy屬性主要是針對外來鍵而言。與之相對應的是xml中的inverse屬性。

如下是測試類程式碼:此時還沒有設定mappedBy屬性,對映時,預設是都由自身維護關聯關係。

 1 package com.lizhou.action.test;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 import org.junit.Test;
 8 import org.springframework.context.ApplicationContext;
 9 import org.springframework.context.support.ClassPathXmlApplicationContext;
10 
11 import com.lizhou.entity.test.Department;
12 import com.lizhou.entity.test.Employee;
13 
14 /**
15  * 測試類
16  * @author bojiangzhou
17  *
18  */
19 
20 public class TestAction {
21     
22     private static SessionFactory sessionFactory = null;
23     
24     static {
25         //讀取classpath中applicationContext.xml配置檔案
26         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
27         //獲取session中配置的sessionFactory物件
28         sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
29     }
30     
31     @Test
32     public void testSave(){
33         //建立一個部門物件
34         Department d1 = new Department();
35         d1.setDname("研發部");
36         
37         //建立兩個員工物件
38         Employee e1 = new Employee();
39         e1.setEname("張三");
40         e1.setPhone("13111111111");
41         Employee e2 = new Employee();
42         e2.setEname("李四");
43         e2.setPhone("18523222222");
44         
45         //設定物件關聯
46         d1.getEmployeeList().add(e1);
47         d1.getEmployeeList().add(e2);
48         e1.setDepartment(d1);
49         e2.setDepartment(d1);
50         
51         //獲取Session
52         Session session = sessionFactory.openSession();
53         //開始事務
54         Transaction t = session.beginTransaction();
55         try {
56             //新增資料
57             session.save(d1);
58             session.save(e1);
59             session.save(e2);
60             //提交事務
61             t.commit();
62         } catch (RuntimeException e) {
63             //有異常則回滾事務
64             t.rollback();
65             e.printStackTrace();
66         } finally {
67             //關閉session
68             session.close();
69         }
70     }
71     
72     
73 }

執行testSave後,控制檯列印如下語句:

1 Hibernate: insert into department (dname) values (?)
2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
4 Hibernate: update employee set departmentId=? where id=?
5 Hibernate: update employee set departmentId=? where id=?

可以看到多了兩條update語句,這是因為兩邊都維護關係,先插入的部門,再插入員工,插入員工時,已經設定好外來鍵了,但部門方也維護關係,會再執行一次更新操作,為員工設定外來鍵,這樣就導致多出了兩條update語句,這裡是有效能損耗的。

一種解決辦法是:將第46、47行去掉,即物件上部門不關聯員工

 1 package com.lizhou.action.test;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 import org.junit.Test;
 8 import org.springframework.context.ApplicationContext;
 9 import org.springframework.context.support.ClassPathXmlApplicationContext;
10 
11 import com.lizhou.entity.test.Department;
12 import com.lizhou.entity.test.Employee;
13 
14 /**
15  * 測試類
16  * @author bojiangzhou
17  *
18  */
19 
20 public class TestAction {
21     
22     private static SessionFactory sessionFactory = null;
23     
24     static {
25         //讀取classpath中applicationContext.xml配置檔案
26         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
27         //獲取session中配置的sessionFactory物件
28         sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
29     }
30     
31     @Test
32     public void testSave(){
33         //建立一個部門物件
34         Department d1 = new Department();
35         d1.setDname("研發部");
36         
37         //建立兩個員工物件
38         Employee e1 = new Employee();
39         e1.setEname("張三");
40         e1.setPhone("13111111111");
41         Employee e2 = new Employee();
42         e2.setEname("李四");
43         e2.setPhone("18523222222");
44         
45         //設定物件關聯
46 //        d1.getEmployeeList().add(e1);
47 //        d1.getEmployeeList().add(e2);
48         e1.setDepartment(d1);
49         e2.setDepartment(d1);
50         
51         //獲取Session
52         Session session = sessionFactory.openSession();
53         //開始事務
54         Transaction t = session.beginTransaction();
55         try {
56             //新增資料
57             session.save(d1);
58             session.save(e1);
59             session.save(e2);
60             //提交事務
61             t.commit();
62         } catch (RuntimeException e) {
63             //有異常則回滾事務
64             t.rollback();
65             e.printStackTrace();
66         } finally {
67             //關閉session
68             session.close();
69         }
70     }
71     
72     
73 }
1 Hibernate: insert into department (dname) values (?)
2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)

這樣部門方就不會去維護外來鍵關係了。但是有一個問題,物件上就沒有關聯了,我們要做的是物件上要互相關聯,資料庫方面只讓一方去維護關係即可。

物件上如果不關聯,因為部門和員工新增到資料庫後,是持久化狀態,存在於session快取中,那session操作快取中這幾個物件時,部門就沒有關聯員工了,那麼就還得再查詢一次資料庫,這不是想要的結果。

這時就要用到mappedBy屬性了。

在一的一方配置@OneToMany(mappedBy="department"),將維護權交由多的一方來維護;

那為什麼不讓多的一方交出維護權,讓一的一方來維護呢?上面的實驗也表明瞭如果讓一的一方來維護,始終都會多出兩條update語句,因為外來鍵是在多的這一方的,所以維護權應該交由多的一方。

部門類的配置:第36行和第37行的配置,部門部門交出維護權利,讓對方來維護

 1 package com.lizhou.entity.test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.FetchType;
10 import javax.persistence.GeneratedValue;
11 import javax.persistence.GenerationType;
12 import javax.persistence.Id;
13 import javax.persistence.JoinColumn;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 import org.hibernate.annotations.GenericGenerator;
18 
19 /**
20  * 部門:與員工一對多關係
21  * @author bojiangzhou
22  *
23  */
24 @Entity
25 @Table(name="department")
26 public class Department {
27     
28     @Id
29     @GeneratedValue(generator="_native")
30     @GenericGenerator(name="_native", strategy="native")
31     private int id; //ID
32     
33     @Column(length=20)
34     private String dname; //部門名稱
35     
36     @OneToMany(mappedBy="department")
37     private List<Employee> employeeList = new ArrayList<>(); //部門下的員工集合
38 
39     // get/set方法62     
63 }

員工類的配置不變。

呼叫testSave時,部門和員工再物件上依然是關聯的:第46-49行

 1 package com.lizhou.action.test;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 import org.junit.Test;
 8 import org.springframework.context.ApplicationContext;
 9 import org.springframework.context.support.ClassPathXmlApplicationContext;
10 
11 import com.lizhou.entity.test.Department;
12 import com.lizhou.entity.test.Employee;
13 
14 /**
15  * 測試類
16  * @author bojiangzhou
17  *
18  */
19 
20 public class TestAction {
21     
22     private static SessionFactory sessionFactory = null;
23     
24     static {
25         //讀取classpath中applicationContext.xml配置檔案
26         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
27         //獲取session中配置的sessionFactory物件
28         sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
29     }
30     
31     @Test
32     public void testSave(){
33         //建立一個部門物件
34         Department d1 = new Department();
35         d1.setDname("研發部");
36         
37         //建立兩個員工物件
38         Employee e1 = new Employee();
39         e1.setEname("張三");
40         e1.setPhone("13111111111");
41         Employee e2 = new Employee();
42         e2.setEname("李四");
43         e2.setPhone("18523222222");
44         
45         //設定物件關聯
46         d1.getEmployeeList().add(e1);
47         d1.getEmployeeList().add(e2);
48         e1.setDepartment(d1);
49         e2.setDepartment(d1);
50         
51         //獲取Session
52         Session session = sessionFactory.openSession();
53         //開始事務
54         Transaction t = session.beginTransaction();
55         try {
56             //新增資料
57             session.save(d1);
58             session.save(e1);
59             session.save(e2);
60             //提交事務
61             t.commit();
62         } catch (RuntimeException e) {
63             //有異常則回滾事務
64             t.rollback();
65             e.printStackTrace();
66         } finally {
67             //關閉session
68             session.close();
69         }
70     }
71     
72     
73 }

控制檯列印的語句:只有三條插入語句,沒有更新語句了

1 Hibernate: insert into department (dname) values (?)
2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)

這裡遇到一個問題:如果配置mappedBy屬性的同時加上@JoinColumn會丟擲異常,所以不能同時使用@JoinColumn和mappedBy;因為@JoinColumn本身就是自己來維護外來鍵,和mappedBy衝突了。--->>>不知道這樣理解正確否!!^_^

 1 package com.lizhou.entity.test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.FetchType;
10 import javax.persistence.GeneratedValue;
11 import javax.persistence.GenerationType;
12 import javax.persistence.Id;
13 import javax.persistence.JoinColumn;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 import org.hibernate.annotations.GenericGenerator;
18 
19 /**
20  * 部門:與員工一對多關係
21  * @author bojiangzhou
22  *
23  */
24 @Entity
25 @Table(name="department")
26 public class Department {
27     
28     @Id
29     @GeneratedValue(generator="_native")
30     @GenericGenerator(name="_native", strategy="native")
31     private int id; //ID
32     
33     @Column(length=20)
34     private String dname; //部門名稱
35     
36     @OneToMany(mappedBy="department")
37     @JoinColumn(name="departmentId")
38     private List<Employee> employeeList = new ArrayList<>(); //部門下的員工集合
39 
40     // set/get 方法63     
64 }

丟擲如下異常:

 1 java.lang.ExceptionInInitializerError
 2     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 3     at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
 4     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
 5     at java.lang.reflect.Constructor.newInstance(Unknown Source)
 6     at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:217)
 7     at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:266)
 8     at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
 9     at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:263)
10     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
11     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
12     at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
13     at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
14     at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
15     at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
16     at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
17     at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
18     at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
19     at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
20     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
21     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
22     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
23     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
24 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: com.lizhou.entity.test.Department.employeeList
25     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553)
26     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
27     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
28     at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
29     at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
30     at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
31     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
32     at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:684)
33     at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
34     at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
35     at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
36     at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
37     at com.lizhou.action.test.TestAction.<clinit>(TestAction.java:26)
38     ... 22 more
39 Caused by: org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: com.lizhou.entity.test.Department.employeeList
40     at org.hibernate.cfg.annotations.CollectionBinder.bind(CollectionBinder.java:493)
41     at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:2156)
42     at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:963)
43     at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:796)
44     at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3788)
45     at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3742)
46     at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1410)
47     at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
48     at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1928)
49     at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:343)
50     at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:431)
51     at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:416)
52     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
53     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
54     ... 34 more

還有一點說明下:

如果將第57行程式碼移到第59行後面,即先儲存員工,再儲存部門,會多出四條update語句

 1 package com.lizhou.action.test;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 import org.junit.Test;
 8 import org.springframework.context.ApplicationContext;
 9 import org.springframework.context.support.ClassPathXmlApplicationContext;
10 
11 import com.lizhou.entity.test.Department;
12 import com.lizhou.entity.test.Employee;
13 
14 /**
15  * 測試類
16  * @author bojiangzhou
17  *
18  */
19 
20 public class TestAction {
21     
22     private static SessionFactory sessionFactory = null;
23     
24     static {
25         //讀取classpath中applicationContext.xml配置檔案
26         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
27         //獲取session中配置的sessionFactory物件
28         sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
29     }
30     
31     @Test
32     public void testSave(){
33         //建立一個部門物件
34         Department d1 = new Department();
35         d1.setDname("研發部");
36         
37         //建立兩個員工物件
38         Employee e1 = new Employee();
39         e1.setEname("張三");
40         e1.setPhone("13111111111");
41         Employee e2 = new Employee();
42         e2.setEname("李四");
43         e2.setPhone("18523222222");
44         
45         //設定物件關聯
46         d1.getEmployeeList().add(e1);
47         d1.getEmployeeList().add(e2);
48         e1.setDepartment(d1);
49         e2.setDepartment(d1);
50         
51         //獲取Session
52         Session session = sessionFactory.openSession();
53         //開始事務
54         Transaction t = session.beginTransaction();
55         try {
56             //新增資料
57             session.save(e1);
58             session.save(e2);
59             session.save(d1);
60             //提交事務
61             t.commit();
62         } catch (RuntimeException e) {
63             //有異常則回滾事務
64             t.rollback();
65             e.printStackTrace();
66         } finally {
67             //關閉session
68             session.close();
69         }
70     }
71     
72     
73 }
1 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
3 Hibernate: insert into department (dname) values (?)
4 Hibernate: update employee set departmentId=?, ename=?, phone=? where id=?
5 Hibernate: update employee set departmentId=?, ename=?, phone=? where id=?
6 Hibernate: update employee set departmentId=? where id=?
7 Hibernate: update employee set departmentId=? where id=?

很明顯,在插入員工時,還沒有部門的資訊,等插入部門的時候,員工方會維護外來鍵關係,更新外來鍵;而部門方也會維護一次,所以多了四條語句。所以在新增資料的時候先儲存一的一方,再儲存多的一方。

總結:mappedBy屬性跟xml配置檔案裡的inverse一樣。在一對多或一對一的關係對映中,如果不表明mappedBy屬性,預設是由本方維護外來鍵。但如果兩方都由本方來維護的話,會多出一些update語句,效能有一定的損耗。

解決的辦法就是在一的一方配置上mappedBy屬性,將維護權交給多的一方來維護,就不會有update語句了。

至於為何要將維護權交給多的一方,可以這樣考慮:要想一個國家的領導人記住所有人民的名字是不可能的,但可以讓所有人民記住領導人的名字!

注意,配了mappedBy屬性後,不要再有@JoinColumn,會衝突!

https://www.cnblogs.com/chiangchou/p/mappedBy.html