zoukankan      html  css  js  c++  java
  • 6、JPA_映射单向多对一的关联关系(n的一方有1的引用,1的一方没有n的集合属性)

    单向多对一的关联关系

    具体体现: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的集合属性
      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 }
    Customer.java
    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 }
  • 相关阅读:
    zipkin启动报错(Caused by: java.lang.ClassNotFoundException: zipkin.Component)的解决方法
    Java中的long与double的区别
    redis使用笔记
    解决node编程频繁修改代码,需要重启服务器问题
    远程连接mysql要点 虚拟主机定义与分类
    详析静态网站与动态网站区别(服务器ip dns 端口)
    JavaEE-实验四 HTML与JSP基础编程
    JavaEE-实验三 Java数据库高级编程
    JavaEE-实验二 Java集合框架实验
    mysql中文乱码 常见编码问题解决方法分享
  • 原文地址:https://www.cnblogs.com/lj95801/p/5005440.html
Copyright © 2011-2022 走看看