标签:
”双向一对多关联关系“等同于”双向多对一关联关系“:1的一方有对n的一方的集合的引用,同时n的一方有对1的一方的引用。
还是用客户Customer和订单Order来解释:
”一对多“的物理意义:一个客户可以有多个订单,某个订单只能归宿于一个客户。
”双向“的物理意义:客户知道自己有哪些订单,订单也知道自己归宿于哪个客户。也就是说,通过客户对象可以检索到其拥有哪些订单;同时,通过订单也可以查找到其对应的客户信息。这是符合我们业务逻辑需求。
到现在为止(结合前面两节的阐述)我们可以很深刻的理解”双向“、”单向“、”一对多“、”多对一“这四个词语了:
①、”一对多“讲的是一个实体类中是否包含有对另外一个实体类的集合的引用。
②、”多对一“包含两层含义:a、一个实体类Ea是否包含有对另外一个实体类Eb的引用;b、是否允许实体类Ea的多个对象{ea1, ea2, ea3,...}同时对实体类Eb的某个对象eb有引用关系(如果不允许”多个对一个“,那么就是后面要讲的”一对一关联关系“),如下Figure_1所示:
③、”双向“包含两个缺一不可的层面:a、1的一方有对n的一方的集合的引用;b、同时,n的一方也有对1的一方的对象的引用;也就是同时满足①、②两点。
④、”单向“就是③中阐述的两个层面只出现一个。也就是只满足①、②两点中的一点。
注:我们上面①~④所讲的集合是实体类中的集合,同时集合之上还要有映射注解(或映射配置文件)进行相关的关联映射。如果单单只是一个集合,却没有表示映射关系的注解或配置文件,那么这个集合就不是我们映射层面上的”集合“。实体类中的对象也是一样的道理。
下面我们通过Customer和Order的关系来印证我们上面的这种理解:
1 @Table(name="t_double_one2many_customer") 2 @Entity 3 public class Customer2 { 4 5 private Integer id; 6 private String lastName; 7 8 private String email; 9 private int age; 10 11 private Date birthday; 12 13 private Date createdTime; 14 15 // 有对Order2的集合的引用 16 //(这个引用还要被注解表示为一种映射关系才行) 17 private Set<Order2> orders = new HashSet<Order2>(); 18 19 // 省略getter、setter方法 20 }
1 @Table(name="t_double_one2many_order") 2 @Entity 3 public class Order2 { 4 5 private Integer id; 6 private String orderName; 7 8 //n的一方有对1的一方的对象的引用 9 //①要有映射注解表明为映射;②、允许多对一,否则就是一对一 10 private Customer2 customer; 11 12 // 省略getter、setter方法 13 }
从Customer实体类和Order实体类的属性定义我们可以得出下面的东西:
1、满足上面③的要求,所以是”双向“。
2、同时还满足①、②两点要求(这里也可以推导出”双向“)
从以上可以看出,Customer和Order是”双向一对多“或”双向多对一“关联关系。
双向关联关系的默认策略和两个单项是一致的:
1、对1的一端的集合引用的检索采用采用延迟加载方式;对n的一端的对象引用的检索采用立即加载方式;可以通过设置@ManyToOne或@OneToMany的fetch属性来修改默认策略。
2、可以自由的删除n的一方的某个对象;但是,对1的一方的对象而言,如果还有n的一方的某个对象引用它,那么就不能够删除1的一方的该对象。可以通过设置@ManyToOne或@OneToMany的cascade属性来修改默认的删除策略;
配置双向多对一关联关系的具体操作:
1、如果双边都维护关联关系:
①n的一端做如下配置
1 @ManyToOne 2 @JoinColumn(name="CUSTOMER_ID") 3 public Customer2 getCustomer() { 4 return customer; 5 }
②、1的一端做如下配置
1 @OneToMany 2 @JoinColumn(name="CUSTOMER_ID") 3 public Set<Order2> getOrders() { 4 return orders; 5 }
要注意的是,两边的@JoinColumn的name属性要一致(这里都是CUSTOMER_ID)。最后建立得到的数据表效果就是在n的一方对应的数据表中有一列外键,而1的一方没有外键列(可以想象,1的一方无法放置外键列)
这种双边都维护关联关系的时候,保存对象不可避免的要发送多余的update语句,和单向一对多关联关系一样。无论你是先保存1的一端,还是先保存n的一端都无法避免update语句的发送。
2、只靠n的一方维护关联关系(推荐使用):
在双向多对一关联关系中,1的一方没有必要维护关联关系,只靠n的一方维护就够了。这样做的好处就是:保存对象的时候先保存1的一端,后保存n的一端,可以避免发送update语句(和单向多对一关联关系一样)。
具体的配置方法就是:a、n的一方配置如 "List_3. n的一方的配置" 一样; b、1的一方配置不能使用@JoinColumn(name="CUSTOMER_ID")注解(如果有此注解,则会报错),同时在@OneToMany中配置mappedBy="customer"属性指定由n的一方的哪个属性来维护关联关系(注意,这里的customer是n一端的对象引用的名称),如下List_5:
1 /** 2 * 1、双向1-n关联的时候,一般1的一方放弃维护关联关系,而由n的一方维护关联关系。 3 * 这样做的好处就是:先保存1的一端,再保存n的一端的时候不会有多余的update sql语句 4 * 2、使用@OneToMany(mappedBy="customer")来指明由n的一方的哪个属性来维护关联关系 5 * 3、有一个值得注意的地方:如果指明了mappedBy="customer",那么就不能够再使用@JoinColumn注解了。10 * 11 */ 12 // @JoinColumn(name="CUSTOMER_ID") 13 @OneToMany(mappedBy="customer") 14 public Set<Order2> getOrders() { 15 return orders; 16 }
说明:就创建的两张数据表而言,“双边维护关联关系”与“单边维护关联关系”所创建的数据表没有区别,都是由n的一方对应的数据表有一个外键参照列,而1的一方没有任何外键列。两张数据表如下:
注意这里的“CUSTOMER_ID”是@JoinColumn(name="CUSTOMER_ID")中指定的CUSTOMER_ID。注意这里的Order实体表中有外键列,而Customer实体表中没有外键列。
下面是Customer和Order实体类:
1 package com.magicode.jpa.doubl.many2one; 2 3 import java.util.Date; 4 import java.util.HashSet; 5 import java.util.Set; 6 7 import javax.persistence.Column; 8 import javax.persistence.Entity; 9 import javax.persistence.GeneratedValue; 10 import javax.persistence.GenerationType; 11 import javax.persistence.Id; 12 import javax.persistence.OneToMany; 13 import javax.persistence.Table; 14 import javax.persistence.TableGenerator; 15 import javax.persistence.Temporal; 16 import javax.persistence.TemporalType; 17 import javax.persistence.Transient; 18 19 /** 20 * @Entity 用于注明该类是一个实体类 21 * @Table(name="t_customer") 表明该实体类映射到数据库的 t_customer 表 22 */ 23 @Table(name="t_double_one2many_customer") 24 @Entity 25 public class Customer2 { 26 27 private Integer id; 28 private String lastName; 29 30 private String email; 31 private int age; 32 33 private Date birthday; 34 35 private Date createdTime; 36 37 private Set<Order2> orders = new HashSet<Order2>(); 38 39 @TableGenerator(name="ID_GENERATOR_2", 40 table="t_id_generator", 41 pkColumnName="PK_NAME", 42 pkColumnValue="seedId_t_customer2", 43 valueColumnName="PK_VALUE", 44 allocationSize=20, 45 initialValue=10 46 ) 47 @GeneratedValue(strategy=GenerationType.TABLE, generator="ID_GENERATOR_2") 48 @Id 49 @Column(name="ID") 50 public Integer getId() { 51 return id; 52 } 53 54 /** 55 * 1、双向1-n关联的时候,一般1的一方放弃维护关联关系,而由n的一方维护关联关系。 56 * 这样做的好处就是:先保存1的一端,再保存n的一端的时候不会有多余的update sql语句 57 * 2、使用@OneToMany(mappedBy="customer")来指明由n的一方的哪个属性来维护关联关系 58 * 3、有一个值得注意的地方:如果指明了mappedBy="customer",那么久不能够再使用@JoinColumn注解了。 59 */ 60 //@JoinColumn(name="CUSTOMER_ID") 61 @OneToMany(mappedBy="customer") 62 public Set<Order2> getOrders() { 63 return orders; 64 } 65 66 public void setOrders(Set<Order2> orders) { 67 this.orders = orders; 68 } 69 70 @Column(name="LAST_NAME", length=50, nullable=false) 71 public String getLastName() { 72 return lastName; 73 } 74 75 @Column(name="BIRTHDAY") 76 @Temporal(TemporalType.DATE) 77 public Date getBirthday() { 78 return birthday; 79 } 80 81 @Column(name="CREATED_TIME", columnDefinition="DATE") 82 public Date getCreatedTime() { 83 return createdTime; 84 } 85 86 @Column(name="EMAIL",columnDefinition="TEXT") 87 public String getEmail() { 88 return email; 89 } 90 91 /* 92 * 工具方法,不需要映射为数据表的一列 93 */ 94 @Transient 95 public String getInfo(){ 96 return "lastName: " + lastName + " email: " + email; 97 } 98 99 @Column(name="AGE") 100 public int getAge() { 101 return age; 102 } 103 104 @SuppressWarnings("unused") 105 private void setId(Integer id) { 106 this.id = id; 107 } 108 109 public void setLastName(String lastName) { 110 this.lastName = lastName; 111 } 112 113 public void setEmail(String email) { 114 this.email = email; 115 } 116 117 public void setAge(int age) { 118 this.age = age; 119 } 120 121 public void setBirthday(Date birthday) { 122 this.birthday = birthday; 123 } 124 125 public void setCreatedTime(Date createdTime) { 126 this.createdTime = createdTime; 127 } 128 129 }
1 package com.magicode.jpa.doubl.many2one; 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.JoinColumn; 9 import javax.persistence.ManyToOne; 10 import javax.persistence.Table; 11 import javax.persistence.TableGenerator; 12 13 @Table(name="t_double_one2many_order") 14 @Entity 15 public class Order2 { 16 17 private Integer id; 18 private String orderName; 19 20 private Customer2 customer; 21 22 @TableGenerator(name="order_id_generator_2", 23 table="t_id_generator", 24 pkColumnName="PK_NAME", 25 pkColumnValue="seedId_t_order2", 26 valueColumnName="PK_VALUE", 27 initialValue=0, 28 allocationSize=20) 29 @GeneratedValue(generator="order_id_generator_2", strategy=GenerationType.TABLE) 30 @Id 31 @Column(name="ID") 32 public Integer getId() { 33 return id; 34 } 35 36 /** 37 * 这里的name="CUSTOMER_ID"会作为Order对象存放的数据库表的一个外键列 38 * 用于维护关联关系 39 */ 40 @ManyToOne 41 @JoinColumn(name="CUSTOMER_ID") 42 public Customer2 getCustomer() { 43 return customer; 44 } 45 46 public void setCustomer(Customer2 customer) { 47 this.customer = customer; 48 } 49 50 @Column(name="ORDER_NAME") 51 public String getOrderName() { 52 return orderName; 53 } 54 55 @SuppressWarnings("unused") 56 private void setId(Integer id) { 57 this.id = id; 58 } 59 60 public void setOrderName(String orderName) { 61 this.orderName = orderName; 62 } 63 64 }
1 package com.magicode.jpa.doubl.many2one; 2 3 import java.util.Date; 4 5 import javax.persistence.EntityManager; 6 import javax.persistence.EntityManagerFactory; 7 import javax.persistence.EntityTransaction; 8 import javax.persistence.Persistence; 9 10 import org.junit.After; 11 import org.junit.Before; 12 import org.junit.Test; 13 14 15 16 public class DoubleMany2OneTest { 17 18 EntityManagerFactory emf = null; 19 EntityManager em = null; 20 EntityTransaction transaction = null; 21 22 @Before 23 public void before(){ 24 emf = Persistence.createEntityManagerFactory("jpa-1"); 25 em = emf.createEntityManager(); 26 transaction = em.getTransaction(); 27 transaction.begin(); 28 } 29 30 @After 31 public void after(){ 32 transaction.commit(); 33 em.close(); 34 emf.close(); 35 } 36 37 @Test 38 public void testPersist(){ 39 40 int i = 1; 41 42 char c = (char) (‘A‘ + i); 43 String strName = (" " + c + c).trim(); 44 int age = 25 + i; 45 46 Customer2 customer = new Customer2(); 47 customer.setAge(age); 48 customer.setEmail(strName + "@163.com"); 49 customer.setLastName(strName); 50 customer.setBirthday(new Date()); 51 customer.setCreatedTime(new Date()); 52 53 Order2 order1 = new Order2(); 54 order1.setOrderName("O-" + strName + "-1"); 55 56 Order2 order2 = new Order2(); 57 order2.setOrderName("O-" + strName + "-2"); 58 59 //设置关联关系 60 customer.getOrders().add(order1); 61 customer.getOrders().add(order2); 62 63 order1.setCustomer(customer); 64 order2.setCustomer(customer); 65 66 //持久化操作 67 /** 68 * 双向1-n的关联关系中,1的一方放弃维护关联关系,由n的一方维护关联关系。 69 * 建议“先保存1的一端,再保存n的一端”,这样就不会有多余的update sql语句 70 */ 71 em.persist(customer); 72 em.persist(order1); 73 em.persist(order2); 74 } 75 76 @Test 77 public void testFind(){ 78 /** 79 * 双向多对一在查询时默认的策略如下: 80 * 1、检索1的一方的时候,其包含的对n的集合属性的检索默认采用延迟加载, 81 * 可以设置@OneToMany(fetch=FetchType.EAGER)来修改为立即加载策略; 82 * 83 * 2、检索n的一方的时候,对其包含的1的一方默认采用立即加载策略, 84 * 可以设置@ManyToOne(fetch=FetchType.LAZY)来修改为延迟加载策略; 85 * 86 * 很容易记混淆。但是我们可以深入思考一下,这样做是有道理的: 87 * ①、1的一方包含了对n的一方的集合属性,在检索的时候集合中到底有多少个元素我们根本 88 * 就不知道,可能是几个,也可能是1000000个呢!!!如果默认采用立即检索策略,可以想 89 * 象后果有多严重。 90 * ②、n的一方包含了1的一方的一个对象,这和一个Integer或者是String类型的对象 91 * 没有区别。也不会像集合那样可能占用巨大的内存资源。 92 * 93 */ 94 Customer2 customer = em.find(Customer2.class, 11); 95 96 System.out.println("---------"); 97 System.out.println(customer.getOrders().iterator().next().getOrderName()); 98 99 System.out.println("---------"); 100 Order2 order = em.find(Order2.class, 1); 101 System.out.println("---------"); 102 System.out.println(order.getCustomer().getEmail()); 103 } 104 105 @Test 106 public void testRemove(){ 107 /** 108 * 双向n-1关联关系,在默认的情况下,如果1的一方集合中还保存有n的一方的引用,那么是无法删除1的一方的; 109 * 但是可以任意删除n的一方。 110 * 可以设置@OneToMany(cascade={CascadeType.REMOVE})来进行级联删除:删除1的同时,把其 111 * 关联的n的一方同时删除; 112 */ 113 // Customer2 customer = em.find(Customer2.class, 11); 114 // em.remove(customer); 115 116 Order2 order = em.find(Order2.class, 1); 117 em.remove(order); 118 } 119 }
8、双向一对多的关联关系(等同于双向多对一。1的一方有对n的一方的集合的引用,同时n的一方有对1的一方的引用)
标签:
原文地址:http://www.cnblogs.com/lj95801/p/5008519.html