在领域模型中, 类与类之间最普遍的关系就是关联关系.
在 UML 中, 关联是有方向的。
以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。
【1】Customer与Order
关联关系映射为Order:Customer=N:1,Order中有Customer属性,Customer中没有Order属性。
映射单向 n-1的关联关系核心:
- 使用@ManyToOne来映射多对一的关联关系
- 使用@JoinColumn 来映射外键
- 使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略
Order如下:
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Table(name="JPA_ORDERS") @Entity public class Order { private Integer id; private String orderName; private Customer customer; @GeneratedValue @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(name="ORDER_NAME") public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } //映射单向 n-1的关联关系 //使用@ManyToOne来映射多对一的关联关系 //使用@JoinColumn 来映射外键 //可使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略 @JoinColumn(name="CUSTOMER_ID") // @ManyToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.EAGER) public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } }
Customer如下:
单向多对一中,一的一端(Customer)并不需要保持多的一端(Order)。
import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; @NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?") @Cacheable(true) @Table(name="JPA_CUTOMERS") @Entity public class Customer { private Integer id; private String lastName; private String email; private int age; private Date createdTime; private Date birth; public Customer() { } public Customer(String lastName, int age) { super(); this.lastName = lastName; this.age = age; } // @TableGenerator(name="ID_GENERATOR", // table="jpa_id_generators", // pkColumnName="PK_NAME", // pkColumnValue="CUSTOMER_ID", // valueColumnName="PK_VALUE", // allocationSize=100) // @GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GENERATOR") @GeneratedValue(strategy=GenerationType.AUTO) @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(name="LAST_NAME",length=50,nullable=false) public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //定义日期格式 @Temporal(TemporalType.TIMESTAMP) public Date getCreatedTime() { return createdTime; } public void setCreatedTime(Date createdTime) { this.createdTime = createdTime; } @Temporal(TemporalType.DATE) public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } @Transient public String getInfo(){ return "lastName: " + lastName + ", email: " + email; } @Override public String toString() { return "Customer [id=" + id + ", lastName=" + lastName + ", email=" + email + ", age=" + age + ", createdTime=" + createdTime + ", birth=" + birth + "]"; } }
【2】多对一的持久化操作
保存多对一时,建议先保存1的一端,后保存n的一端,这样不会多出额外的update语句。
实例代码如下:
@Test public void testManyToOnePersist(){ Customer customer = new Customer(); customer.setAge(18); customer.setBirth(new Date()); customer.setCreatedTime(new Date()); customer.setEmail("gg@163.com"); customer.setLastName("GG"); Order order1 = new Order(); order1.setOrderName("G-GG-1"); Order order2 = new Order(); order2.setOrderName("G-GG-2"); //设置关联关系 order1.setCustomer(customer); order2.setCustomer(customer); //ִ执行保存操作 entityManager.persist(customer); entityManager.persist(order1); entityManager.persist(order2); }
控制台输出如下:
Hibernate: insert into JPA_CUTOMERS (age, birth, createdTime, email, LAST_NAME) values (?, ?, ?, ?, ?) Hibernate: insert into JPA_ORDERS (CUSTOMER_ID, ORDER_NAME) values (?, ?) Hibernate: insert into JPA_ORDERS (CUSTOMER_ID, ORDER_NAME) values (?, ?)
即,连续三条插入语句。
如果先保存Order呢?将会多出Update语句
如下所示:
//ִ执行保存操作 entityManager.persist(order1); entityManager.persist(order2); entityManager.persist(customer);
控制台输出如下:
Hibernate: insert into JPA_ORDERS (CUSTOMER_ID, ORDER_NAME) values (?, ?) Hibernate: insert into JPA_ORDERS (CUSTOMER_ID, ORDER_NAME) values (?, ?) Hibernate: insert into JPA_CUTOMERS (age, birth, createdTime, email, LAST_NAME) values (?, ?, ?, ?, ?) Hibernate: update JPA_ORDERS set CUSTOMER_ID=?, ORDER_NAME=? where id=? Hibernate: update JPA_ORDERS set CUSTOMER_ID=?, ORDER_NAME=? where id=?
总结如下:
保存多对一时,建议先保存1的一端,后保存n的一端,这样不会多出额外的update语句。
【3】多对一的获取操作
默认情况下,使用左外连接的方式来获取n的一端的对象和其关联的1的一端的对象。可使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略。
实例如下:
@Test public void testManyToOneFind(){ Order order = entityManager.find(Order.class, 1); System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getLastName()); }
此时Order实体类中fetch为eager(默认值):
@JoinColumn(name="CUSTOMER_ID") @ManyToOne(fetch=FetchType.EAGER) public Customer getCustomer() { return customer; }
即,默认使用左外连接方式来获取n的一端的对象和其关联的1的一端的对象。
如果将feteh属性改为lazy:
@JoinColumn(name="CUSTOMER_ID") @ManyToOne(fetch=FetchType.LAZEY) public Customer getCustomer() { return customer; }
其控制台输出如下:
【4】多对一的删除操作
① 可以任意删除n的一端,但是不能随意删除1的一端,因为有外键约束。
示例如下:
//不能直接删除1的一端,因为有外键约束 @Test public void testManyToOneRemove(){ Order order = entityManager.find(Order.class, 6); entityManager.remove(order); }
② 如果删除1的一端将会抛出异常:
//不能直接删除1的一端,因为有外键约束 @Test public void testManyToOneRemove(){ Customer customer = entityManager.find(Customer.class, 4); entityManager.remove(customer); }
③ 如果外键约束不存在,则可以删除1的一端:
④ 删除一个不存在的实体,同样会抛出异常:
【5】多对一的更新操作
示例如下:
@Test public void testManyToOneUpdate(){ Order order = entityManager.find(Order.class, 2); order.getCustomer().setLastName("FFF"); }
控制台输出如下:
Hibernate: select order0_.id as id1_1_1_, order0_.CUSTOMER_ID as CUSTOMER3_1_1_, order0_.ORDER_NAME as ORDER_NA2_1_1_, customer1_.id as id1_0_0_, customer1_.age as age2_0_0_, customer1_.birth as birth3_0_0_, customer1_.createdTime as createdT4_0_0_, customer1_.email as email5_0_0_, customer1_.LAST_NAME as LAST_NAM6_0_0_ from JPA_ORDERS order0_ left outer join JPA_CUTOMERS customer1_ on order0_.CUSTOMER_ID=customer1_.id where order0_.id=? Hibernate: update JPA_CUTOMERS set age=?, birth=?, createdTime=?, email=?, LAST_NAME=? where id=?