Hibernate_5 在hibernate 领域模型中单向多对一(n - 1)和双向一对多(1 - n)

单向多对一(n - 1)

单向 n-1 关联只需从 n 的一端可以访问 1 的一端

下面介绍 n - 1 时,以 Order (订单)和用户(Customer) 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户. 从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。

步骤

  1. 在Order 中添加 Customer 的关联关系

  2. 在Order 的映射文件中配置映射多对一的关联关系。
    代码如下:下面是两种书写方式,效果是相同的

    <!-- 
        映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 
        name: 多这一端关联的一那一端的属性的名字
        class: 一那一端的属性对应的类名
        column: 一那一端在多的一端对应的数据表中的外键的名字
    -->
    <!--    <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer" column="CUSTOMER_ID"/> -->  
    <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer">
        <column name="CUSTOMER_ID"/>
    </many-to-one>
    

<many-to-one> 属性说明

属性 描述
name 属性名。
column (可选) 外间字段名。它也可以通过嵌套的 <column> 元素指定。
class (可选 ) 默认是通过反射得到属性类型): 关联的类的名字。
cascade(级联) (可选) 指明哪些操作会从父对象级联到关联的对象。
fetch (可选 - 默认为 select ) 在外连接抓取(outer-join fetching)和序列选择抓取(sequential select fetching)两者中选择其一。
update, insert (可选 - defaults to true ) 指定对应的字段是否包含在用于UPDATE 和/或 INSERT 的SQL语句中。如果二者都是false ,则这是一个纯粹的 “外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到 或者通过trigger(触发器)、或其他程序。
property-ref (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。 如果没有指定,会使用对方关联类的主键。
access (可选 - 默认是 property ) Hibernate用来访问属性的策略。
unique (可选) 使用DDL为外键字段生成一个唯一约束。此外, 这也可以用作property-ref 的目标属性。这使关联同时具有 一对一的效果。
not-null (可选) 使用DDL为外键字段生成一个非空约束。
optimistic-lock (可选 - 默认为 true ) 指定这个属性在做更新时是否需要获得乐观锁定(optimistic lock)。 换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。
lazy (可选 - 默认为 proxy ) 默认情况下,单点关联是经过代理的。lazy="true" 指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetche lazily)(需要运行时字节码的增强)。lazy="false" 指定此关联总是被预先抓取。
not-found (可选 - 默认为 exception ) 指定外键引用的数据不存在时如何处理: ignore 会将数据不存在作为关联到一个空对象(null)处理。
entity-name (optional) 被关联的类的实体名。

完整示例代码

public class Order {
    
    private Integer orderId;
    private String orderName;
    private Customer customer;
    
    //对应变量的get 和set 方法省略
}

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-10-19 10:20:11 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
    <class name="com.cfox.hibernate.n21.Order" table="ORDERS">
        <id name="orderId" type="java.lang.Integer">
            <column name="ORDERID" />
            <generator class="native" />
        </id>
        <property name="orderName" type="java.lang.String">
            <column name="ORDER_NAME" />
        </property>
        
        <!-- 
            映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 
            name: 多这一端关联的一那一端的属性的名字
            class: 一那一端的属性对应的类名
            column: 一那一端在多的一端对应的数据表中的外键的名字
        -->
<!--    <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer" column="CUSTOMER_ID"/> -->  
        <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer">
            <column name="CUSTOMER_ID"/>
        </many-to-one>
  </class>
</hibernate-mapping>
public class Customer {
    private Integer customerId;
    private String name;
    
    //对应变量的get 和set 方法省略
}

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-10-19 10:20:11 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
    <class name="com.cfox.hibernate.n21.Customer" table="CUSTOMERS">
        <id name="customerId" type="java.lang.Integer">
            <column name="CUSTOMERID" />
            <generator class="native" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
    </class>
</hibernate-mapping>

双向一对多(1 - n)

双向一对多喝双向多对一是完全相同的,从多的一端可以访问多的一端,从一的一端也可以访问多的一端。

下面还是使用 Order 和Customer 来介绍双向一对多

先看示例代码:

实体bean

public class Customer {
    private Integer customerId;
    private String customerName;
    private Set<Order> orders = new HashSet<Order>();
    public Customer() {
    }
    public Customer(String customerName) {
        this.customerName = customerName;
    }
    // 省略set 和 get 方法
}

public class Order {
    private Integer orderId;
    private String orderName;
    private Customer customer;
    public Order() {
    }
    public Order(String orderName) {
        this.orderName = orderName;
    }
 // 省略set 和 get 方法
}

映射文件

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.cfox.hibernate.Order" table="ORDERS">
        <id name="orderId" type="java.lang.Integer">
            <column name="ORDER_ID" />
            <generator class="native" />
        </id>
        <property name="orderName" type="java.lang.String">
            <column name="ORDER_NAME" />
        </property>
        <many-to-one name="customer" class="com.cfox.hibernate.Customer">
            <column name="CUSTOMER_ID"/>
        </many-to-one>
    </class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
    <class name="com.cfox.hibernate.Customer" table="CUSTOMER">
        <id name="customerId" type="java.lang.Integer">
            <column name="CUSTOMER_ID" />
            <generator class="native" />
        </id>
        <property name="customerName" type="java.lang.String">
            <column name="CUSTOMER_NAME" />
        </property>
        <set name="orders" table="ORDERS" inverse="true" order-by="ORDER_NAME DESC">
            <!-- 设定与所关联的持久化类对应的表的外键
                    column: 指定关联表的外键名
             -->
            <key column="CUSTOMER_ID"></key>
            <!-- 设定集合属性中所关联的持久化类
                    class: 指定关联的持久化类的类名
             -->
            <one-to-many class="com.cfox.hibernate.Order"/>
        </set>
    </class>
</hibernate-mapping>

介绍一下上面的几个元素:

  • <set>: 映射持久化类的,也就是一的一端存放多的一端的映射
    • name :设定待映射的持久化类的属性的
  • <key>:设定与所关联的持久化类对应的表的外键
    • column: 指定关联表的外键名
映射关系图
image
下面介绍<set> 元素中的 inverse 属性
  • 在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
  • 在没有设置 inverse=true 的情况下,父子两边都维护父子
    关系
  • 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
  • 在 1-N 关系中,若将 1 方设为主控方
    • 会额外多出 update 语句。
    • 插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
下面介绍<set> 元素中的 order-by 属性
  • 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序
  • order-by 属性中还可以加入 SQL 函数

在操作中注意

  • 保存操作

        public void testMany2OneSave(){
            Customer customer = new Customer();
            customer.setCustomerName("AA");
            
            Order order1 = new Order();
            order1.setOrderName("ORDER-1");
            
            Order order2 = new Order();
            order2.setOrderName("ORDER-2");
            
            //设定关联关系
            order1.setCustomer(customer);
            order2.setCustomer(customer);
            
            customer.getOrders().add(order1);
            customer.getOrders().add(order2);
            
            //执行  save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT, 2 条 UPDATE
            //因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE
            //可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系!
            //建议设定 set 的 inverse=true, 建议先插入 1 的一端, 后插入多的一端
            //好处是不会多出 UPDATE 语句
            session.save(customer);
            
    //      session.save(order1);
    //      session.save(order2);
            
            //先插入 Order, 再插入 Cusomer, 3 条 INSERT, 4 条 UPDATE
    //      session.save(order1);
    //      session.save(order2);
    //      
    //      session.save(customer);
        }
    
  • 查询操作
    通过多的一端查询 1 的一端

    public void testMany2OneGet(){
            //1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的 1 的那一端的对象!
    
            Order order = (Order) session.get(Order.class, 1);
            System.out.println(order.getOrderName()); 
            
            System.out.println(order.getCustomer().getClass().getName());
            
            //session.close();
            
            //2. 在需要使用到关联的对象时, 才发送对应的 SQL 语句. 
            Customer customer = order.getCustomer();
            System.out.println(customer.getCustomerName()); 
            
            //3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时, 
            //若此时 session 已被关闭, 则默认情况下
            //会发生 LazyInitializationException 异常
            
            //4. 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!
        }
    

    通过 1 的一端查询多的一端

    public void testOne2ManyGet(){
            //1. 对 n 的一端的集合使用延迟加载
            Customer customer = (Customer) session.get(Customer.class, 7);
            System.out.println(customer.getCustomerName()); 
            //2. 返回的多的一端的集合时 Hibernate 内置的集合类型. 
            //该类型具有延迟加载和存放代理对象的功能. 
            System.out.println(customer.getOrders().getClass()); 
            
            //session.close();
            //3. 可能会抛出 LazyInitializationException 异常 
            
            System.out.println(customer.getOrders().size()); 
            
            //4. 再需要使用集合中元素的时候进行初始化. 
        }
    
  • 删除操作

        public void testDelete(){
            //在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象
            Customer customer = (Customer) session.get(Customer.class, 1);
            session.delete(customer); 
        }
    
  • 修改操作

        public void testCascade(){
            Customer customer = (Customer) session.get(Customer.class, 3);
            customer.getOrders().clear();
        }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容