6、JPA_映射单向多对一的关联关系(n的一方有1的引用,1的一方没有n的集合属性)
2015-11-29 21:03 宏愿。 阅读(928) 评论(0) 编辑 收藏 举报
单向多对一的关联关系
具体体现:n的一方有1的引用,1的一方没有n的集合属性
举个例子:订单Order对顾客Customer是一个单向多对一的关联关系。Order是n的一方,有对Customer的引用;而Customer作为1的一方却没有Order的集合属性。
“多对一”的物理意义就是:一个客户可以有多个订单,而一个订单只能归属于一个客户。
“单向”的物理意义就是:订单知道它属于哪个客户,而客户却不知道自己有哪些订单。(多么反人类的逻辑!!!)
下面看看客户和订单的实体类的属性,很清楚“单向多对一”这种关联关系:
Order实体的属性(Order中有对Customer的引用):
1 @Table(name="t_order") 2 @Entity 3 public class Order { 4 5 private Integer id; 6 private String orderName; 7 8 // n 的一方有对 1 的一方的引用 9 private Customer customer; 10 11 // 省略getter、setter方法... 12 }
Customer实体的属性(Customer中没有对Order的集合的引用):
1 @Table(name="t_customer") 2 @Entity 3 public class Customer { 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 //省略getter、setter方法 16 }
映射方法:主要是对n的一方使用@ManyToOne和@JoinColumn注解。而1的一方不需要做任何映射性的标注。具体的映射方法:
1、采用@ManyToOne注解映射多对一的关联关系。检索n的时候,对其包含的1的一方的引用在默认情况下采用“左外连接”的方式来进行加载。可以通过配置@ManyToOne(fetch=FetchType.LAZY)的来设定为延迟加载(延迟加载要注意懒加载异常的出现)。
2、通过@JoinColumn注解来映射外键,其name属性用来指定外键列的列名。外键列在n的一方对应的数据表中。
对应于Customer和Order而言:在检索Order的时候,对Order.customer采用左外连接的方式进行检索(立即加载)。为什么要这样呢??
在实体类中,属性可以分为两种:1、集合属性; 2、非集合属性;
一个大体的原则就是:1、对集合属性默认采用懒加载策略;2、对非集合属性默认采用立即检索策略;
这种默认检索策略是有道理的:1、检索的时候我们并不知道集合中到底包含了多少条记录,可能是几条,也可能是几十亿条记录。如果对一个庞大的集合属性采用立即检索策略,那么很有可能直接将内存全部占用了(比如说,集合中包含了100亿条记录),系统直接崩溃。2、对一个非集合属性而言,即便是一个其它实体类的引用(该引用中的集合依然会采用延迟检索)所占资源也是十分有限,不会像检索集合那样直接脱离我们的掌控。所以,对于非集合属性默认采用立即检索策略。
在持久化多对一的时候有一个可以优化的地方值得注意一下:先保存1的一方,后保存n的一方,按这个顺序进行保存就不需要发送update语句。
下面列出清单:
List_1. Customer作为1的一方,没有Order的集合属性
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.magicode.jpa.helloworld; 2 3 import java.util.Date; 4 5 import javax.persistence.Column; 6 import javax.persistence.Entity; 7 import javax.persistence.GeneratedValue; 8 import javax.persistence.GenerationType; 9 import javax.persistence.Id; 10 import javax.persistence.Table; 11 import javax.persistence.TableGenerator; 12 //import javax.persistence.TableGenerator; 13 import javax.persistence.Temporal; 14 import javax.persistence.TemporalType; 15 import javax.persistence.Transient; 16 17 /** 18 * @Entity 用于注明该类是一个实体类 19 * @Table(name="t_customer") 表明该实体类映射到数据库的 t_customer 表 20 */ 21 @Table(name="t_customer") 22 @Entity 23 public class Customer { 24 25 private Integer id; 26 private String lastName; 27 28 private String email; 29 private int age; 30 31 private Date birthday; 32 33 private Date createdTime; 34 35 /** 36 * @TableGenerator 标签的属性解释: 37 * 38 * ①、allocationSize 属性需要赋一个整数值。表示了bucket的容量。其默认值为50。 39 * ②、table 属性用于指定辅助表的表名。这里指定为t_id_generator数据表 40 * 41 * 其基本思想就是:从table指定的辅助表中读取一个bucket段id号范围内的第一个数值,记为first_id。在后面持久化过程中的id号是从first_id开始依次递增1得到 42 * 当递增到first_id + allocationSize 的时候,就会再一次从辅助表中读取一个first_id开始新一轮的id生成过程。 43 * 44 * 我们知道,要从数据库中确定一个值,则必须确定其“行”和“列”。JPA自动产生的t_id_generator只有两列。当然,如果该表 45 * 为n个表产生id,则会在t_id_generator表中保存“n行2列”。 46 * 那么,如何从数据表t_id_generator中确定出seed_id用于为Customer实体计算id呢??JPA会依据Customer实体的 47 * @TableGenerator 属性值来依据下面的规则的到seed_id: 48 * ③、valueColumnName 属性指定了seed_id的列名。valueColumnName="PK_VALUE"也就是指定了 49 * seed_id位于PK_VALUE列中。同时,规定了这一列必须是数值型(int,long等)。 50 * 剩下的任务就是如何从n行中确定出是哪一行?? 51 * ④、pkColumnName="PK_NAME",pkColumnValue="seed_t_customer_id" 两个一起来确定具体的行: 52 * 在PK_NAME列中,值为seed_t_customer_id的那一行。 53 * ⑤、由上面③和④中确定出来的“行”和“列”就可以得到一个int型的整数值。这个值就是first_id。 54 * 55 * 注意:我们的数据库中可以没有t_id_generator这张表,JPA会自动帮助我们完成该表的创建工作。自动创建的表只有两列: 56 * PK_NAME(VARCHAR)和PK_VALUE(int)。同时会自动添加一条记录(seed_t_customer_id, 51) 依据优化策略的不同,辅助表中记录的数值有区别 57 */ 58 @TableGenerator(name="ID_GENERATOR", 59 table="t_id_generator", 60 pkColumnName="PK_NAME", 61 pkColumnValue="seedId_t_customer", 62 valueColumnName="PK_VALUE", 63 allocationSize=20, 64 initialValue=10 65 ) 66 @GeneratedValue(strategy=GenerationType.TABLE, generator="ID_GENERATOR") 67 @Id 68 @Column(name="ID") 69 public Integer getId() { 70 return id; 71 } 72 73 /** 74 * @Column 指明lastName属性映射到表的 LAST_NAME 列中 75 * 同时还可以指定其长度、能否为null等数据限定条件 76 */ 77 @Column(name="LAST_NAME", length=50, nullable=false) 78 public String getLastName() { 79 return lastName; 80 } 81 82 /** 83 * 利用 @Temporal 来限定birthday为DATE型 84 */ 85 @Column(name="BIRTHDAY") 86 @Temporal(TemporalType.DATE) 87 public Date getBirthday() { 88 return birthday; 89 } 90 91 /* 92 * 通过 @Column 的 columnDefinition 属性将CREATED_TIME列 93 * 映射为“DATE”类型 94 */ 95 @Column(name="CREATED_TIME", columnDefinition="DATE") 96 public Date getCreatedTime() { 97 return createdTime; 98 } 99 100 /* 101 * 通过 @Column 的 columnDefinition 属性将email列 102 * 映射为“TEXT”类型 103 */ 104 @Column(name="EMAIL",columnDefinition="TEXT") 105 public String getEmail() { 106 return email; 107 } 108 109 /* 110 * 工具方法,不需要映射为数据表的一列 111 */ 112 @Transient 113 public String getInfo(){ 114 return "lastName: " + lastName + " email: " + email; 115 } 116 117 @Column(name="AGE") 118 public int getAge() { 119 return age; 120 } 121 122 @SuppressWarnings("unused") 123 private void setId(Integer id) { 124 this.id = id; 125 } 126 127 public void setLastName(String lastName) { 128 this.lastName = lastName; 129 } 130 131 public void setEmail(String email) { 132 this.email = email; 133 } 134 135 public void setAge(int age) { 136 this.age = age; 137 } 138 139 public void setBirthday(Date birthday) { 140 this.birthday = birthday; 141 } 142 143 public void setCreatedTime(Date createdTime) { 144 this.createdTime = createdTime; 145 } 146 147 }
List_2. Order作为n的一方,有对Customer的引用,需要配置关联关系
1 package com.magicode.jpa.many2one; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.FetchType; 6 import javax.persistence.GeneratedValue; 7 import javax.persistence.GenerationType; 8 import javax.persistence.Id; 9 import javax.persistence.JoinColumn; 10 import javax.persistence.ManyToOne; 11 import javax.persistence.Table; 12 import javax.persistence.TableGenerator; 13 14 import com.magicode.jpa.helloworld.Customer; 15 16 @Table(name="t_order") 17 @Entity 18 public class Order { 19 20 private Integer id; 21 private String orderName; 22 23 private Customer customer; // n 的一方有对 1 的一方的引用24 25 @TableGenerator(name="order_id_generator", 26 table="t_id_generator", 27 pkColumnName="PK_NAME", 28 pkColumnValue="seedId_t_order", 29 valueColumnName="PK_VALUE", 30 initialValue=0, 31 allocationSize=20) 32 @GeneratedValue(generator="order_id_generator", strategy=GenerationType.TABLE) 33 @Id 34 @Column(name="ID") 35 public Integer getId() { 36 return id; 37 } 38 39 @Column(name="ORDER_NAME") 40 public String getOrderName() { 41 return orderName; 42 } 43 44 /** 45 * 1、通过 @ManyToOne 来配置单向多对一的关联关系,同时可以配置fetch=FetchType.LAZY 46 * 来指定懒加载查询策略; 47 * 2、@JoinColumn来映射外键 48 */ 49 //@ManyToOne(fetch=FetchType.LAZY)
@ManyToOne 50 @JoinColumn(name="CUSTOMER_ID") 51 public Customer getCustomer() { 52 return customer; 53 } 54 55 public void setCustomer(Customer customer) { 56 this.customer = customer; 57 } 58 59 @SuppressWarnings("unused") 60 private void setId(Integer id) { 61 this.id = id; 62 } 63 64 public void setOrderName(String orderName) { 65 this.orderName = orderName; 66 } 67 68 }
List_3. persistence.xml的配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <persistence version="2.0" 3 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 5 6 <!-- 注意这里的 persistence-unit标签的name属性值,main函数中会用到它 --> 7 <persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL"> 8 9 <!-- 配置使用什么样的ORM产品作为JPA的实现 10 1、实际上配置的是 javax.persistence.spi.PersistenceProvider 接口的实现类 11 2、若JPA项目中只有一个JPA的实现产品,则可以不配置provider节点 12 --> 13 <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> 14 15 <!-- 添加持久化类 --> 16 <class>com.magicode.jpa.helloworld.Customer</class> 17 <class>com.magicode.jpa.many2one.Order</class> 18 19 <properties> 20 <!-- 连接数据库的基本信息 --> 21 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> 22 <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa" /> 23 <property name="javax.persistence.jdbc.user" value="root" /> 24 <property name="javax.persistence.jdbc.password" value="tiger123" /> 25 26 <!-- 配置JPA实现产品的基本属性:配置Hibernate的基本属性 --> 27 <property name="hibernate.format_sql" value="true" /> 28 <property name="hibernate.show_sql" value="true" /> 29 <property name="hibernate.hbm2ddl.auto" value="update" /> 30 <!-- 31 Setting is relevant when using @GeneratedValue. 32 It indicates whether or not the new IdentifierGenerator implementations 33 are used for javax.persistence.GenerationType.AUTO, 34 javax.persistence.GenerationType.TABLE and 35 javax.persistence.GenerationType.SEQUENCE. 36 Default to false to keep backward compatibility. 37 --> 38 <property name="hibernate.id.new_generator_mappings" value="true"/> 39 40 </properties> 41 42 </persistence-unit> 43 </persistence>
List_4. 测试方法分别测试了增、删、改、查
1 package com.magicode.jpa.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 import com.magicode.jpa.helloworld.Customer; 15 16 public class Many2OneTest { 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 /** 38 * 先持久化 1 的一方,后持久化 n 的一方会少发送3条update SQL语句 39 */ 40 @Test 41 public void testPersist(){ 42 43 for(int i = 0; i < 3; i++){ 44 char c = (char) ('A' + i); 45 String strName = (" " + c + c).trim(); 46 int age = 25 + i; 47 48 Customer customer = new Customer(); 49 customer.setAge(age); 50 customer.setEmail(strName + "@163.com"); 51 customer.setLastName(strName); 52 customer.setBirthday(new Date()); 53 customer.setCreatedTime(new Date()); 54 55 Order order1 = new Order(); 56 order1.setOrderName("O-" + strName + "-1"); 57 58 Order order2 = new Order(); 59 order2.setOrderName("O-" + strName + "-2"); 60 61 //设置关联关系 62 order1.setCustomer(customer); 63 order2.setCustomer(customer); 64 65 //持久化操作 66 em.persist(customer); 67 em.persist(order1); 68 em.persist(order2); 69 } 70 } 71 72 /** 73 * 查询单向多对一关联关系的时候,默认情况下采用的是left outer join策略。 74 * 可以通过 @ManyToOne(fetch=FetchType.LAZY) 来配置为懒加载查询策略 75 */ 76 @Test 77 public void testFind(){ 78 Order order = em.find(Order.class, 1); 79 80 System.out.println("-----1------"); 81 System.out.println("orderName: " + order.getOrderName()); 82 83 System.out.println("-----2------"); 84 System.out.println("order.customer.email: " + order.getCustomer().getEmail()); 85 } 86 87 @Test 88 public void testRemove(){ 89 //删除多的一端可以随便删除 90 // Order order = em.find(Order.class, 1); 91 // em.remove(order); 92 93 //删除1的一端的时候,如果还有多的一端引用它,则会删除失败 94 Customer customer = em.find(Customer.class, 11); 95 em.remove(customer); 96 } 97 98 @Test 99 public void testUpdate(){ 100 Customer customer = em.find(Customer.class, 12); 101 customer.setAge(1000); 102 103 Order order = em.find(Order.class, 5); 104 order.setOrderName("OOOO"); 105 order.getCustomer().setAge(1000); 106 } 107 }