Hibernate中的多表操作

原创 2018年04月16日 20:44:44

Hibernate中的多表操作

在实际开发中,我们不可能只是简简单单地去操作单表,绝大部分都是要对多表进行操作的。所以本文就来讲述Hibernate中的多表操作,讲之前,先复习一下使用SQL语句是如何进行多表操作的。提示:本文所有案例代码的编写都是建立在前文Hibernate检索方式概述的案例基础之上的!!!

SQL多表操作

SQL多表操作可分为如下几类:

  • 交叉连接(CROSS JOIN) 
    交叉连接其实是没有实际意义的,它会产生迪卡尔积。例如:

    SELECT * FROM t_customer CROSS JOIN t_order;
    • 1
  • 内连接(INNER JOIN ON) 
    使用内连接,它只能将有关联的数据得到。例如:

    SELECT * FROM t_customer AS c INNER JOIN t_order AS o ON c.id=o.c_customer_id; 
    • 1

    内连接还有一种隐式内连接,它使用”逗号”将表分开,使用WHERE来消除迪卡尔积。例如:

    SELECT * FROM t_customer AS c, t_order o WHERE c.id=o.c_customer_id;
    • 1
  • 外连接 
    外连接又分为:

    • 左外连接(LEFT OUTER JOIN):它是以左表为基准关联数据,说的大白话一点就是它展示的数据只是在左表中有的,右表中没有的不管。例如:

      SELECT * FROM t_customer c LEFT OUTER JOIN t_order o ON c.id=o.c_customer_id;
      • 1
    • 右外连接(RIGHT OUTER JOIN):它是以右表为基准关联数据,说的大白话一点就是它展示的数据只是在右表中有的,左表中没有的不管。例如:

      SELECT * FROM t_customer c RIGHT OUTER JOIN t_order o ON c.id=o.c_customer_id;
      • 1

    提示:OUTER可以省略。

HQL多表操作

HQL多表操作可分为下面几类:

  1. 交叉连接
  2. 内连接 
    • 显示内连接
    • 隐式内连接
    • 迫切内连接
  3. 外连接 
    • 左外连接
    • 迫切左外连接
    • 右外连接

注意:在Hibernate框架中有迫切连接的这一概念,而在SQL中是没有的

内连接

显示内连接

显示内连接使用的是inner join with。如果是在MySQL中使用显示内连接,那么我们就要这样书写SQL语句:

select * from t_customer(表名)inner join t_order(表名) on 条件
  • 1

但在Hibernate框架中,我们则要书写这样的hql语句:

from Order o inner join o.c
  • 1

为了便于测试,在cn.itheima.test包下编写一个HQLJoinTest单元测试类,并在该类中编写这样的一个测试方法:

public class HQLJoinTest {

    // 测试显示内连接
    @Test
    public void test1() {

        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Order o inner join o.c";

        Query query = session.createQuery(hql);
        List<Object[]> list = query.list(); // 结果是一个List<Object[]>集合,而Object[]中装入的是Customer和Order对象。

        for (Object[] objs : list) {
            for (Object obj : objs) {
                System.out.print(obj + "\t");
            }
            System.out.println();
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行以上test1方法,Eclipse控制台打印如下: 
这里写图片描述 
可得出结论:query.list()返回的结果是一个List集合,集合存放的是Object[],而Object[]中装入的无非是Customer和Order对象。 
在Hibernate框架中,我们也可书写这样的hql语句:

from Customer c inner join c.orders
  • 1

为了进行测试,将HQLJoinTest单元测试类中的test1方法改为:

public class HQLJoinTest {

    // 测试显示内连接
    @Test
    public void test1() {

        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Customer c inner join c.orders";

        Query query = session.createQuery(hql);
        List<Object[]> list = query.list(); // 结果是一个List<Object[]>集合,而Object[]中装入的是Customer和Order对象。

        for (Object[] objs : list) {
            for (Object obj : objs) {
                System.out.print(obj + "\t");
            }
            System.out.println();
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行以上test1方法,Eclipse控制台打印如下: 
这里写图片描述 
当然了,我们又可书写这样的hql语句:

from Customer c inner join c.orders with c.id=1
  • 1

即使用with再添加一个条件。所以为了便于进行测试,将HQLJoinTest单元测试类中的test1方法改为:

public class HQLJoinTest {

    // 测试显示内连接
    @Test
    public void test1() {

        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Customer c inner join c.orders with c.id=1";

        Query query = session.createQuery(hql);
        List<Object[]> list = query.list(); // 结果是一个List<Object[]>集合,而Object[]中装入的是Customer和Order对象。

        for (Object[] objs : list) {
            for (Object obj : objs) {
                System.out.print(obj + "\t");
            }
            System.out.println();
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行以上test1方法,Eclipse控制台打印如下: 
这里写图片描述

隐式内连接

隐式内连接也与我们在SQL中的操作不一样,它是通过.运算符来关联的。值得一提的是,隐式内连接使用频率并不高,大家知道就OK了。如果是在MySQL中使用隐式内连接,那么我们就要这样书写SQL语句:

select * from t_customer,t_order where 条件
  • 1

但在Hibernate框架中,我们则要书写这样的hql语句:

from Order o where o.c.id=1
  • 1

为了便于进行测试,在HQLJoinTest单元测试类中编写如下方法:

public class HQLJoinTest {

    // 测试隐式内连接(使用频率不高。大家知道就OK了)
    @Test
    public void test2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Order o where o.c.id=1";
        Query query = session.createQuery(hql);
        List list = query.list();
        System.out.println(list);

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行以上test2方法,Eclipse控制台打印如下: 
这里写图片描述

迫切内连接

迫切内连接使用的是inner join fetch。迫切内连接得到的结果是直接封装到PO类中,而内连接得到的是Object[]数组,数组中封装的是PO类对象。下面我们来验证这一点,在HQLJoinTest单元测试类中编写如下方法:

public class HQLJoinTest {

    // 迫切内连接
    @Test
    public void test3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Order o inner join fetch o.c";

        Query query = session.createQuery(hql);
        List list = query.list(); // 结果是一个List<>集合,集合中装入的是from后面的对象。
        System.out.println(list);

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

运行以上test3方法,Eclipse控制台打印如下: 
这里写图片描述 
可得出结论:query.list()返回的结果是一个List集合,集合中装入的是from后面的对象。如还有疑问,可将以上test3方法改为:

public class HQLJoinTest {

    // 迫切内连接
    @Test
    public void test3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "from Customer c inner join fetch c.orders";

        Query query = session.createQuery(hql);
        List list = query.list(); // 结果是一个List<>集合,集合中装入的是from后面的对象。
        System.out.println(list);

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

运行以上test3方法,Eclipse控制台打印如下: 
这里写图片描述 
从这里我们也能看出,迫切内连接底层也是执行的inner join,只不过数据结果封装到了对象中了。但问题又来了,我们查询的是两张表的信息,那就会得到合并后的结果,如果是查Order则没问题,但是你要查Customer,Customer就会出现很多重复的数据,这个时候,我们就需要使用关键字——distinct去消除重复了。故应将test3方法改为:

public class HQLJoinTest {

    // 迫切内连接
    @Test
    public void test3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        String hql = "select distinct c from Customer c inner join fetch c.orders";

        Query query = session.createQuery(hql);
        List<Customer> list = query.list(); 
        for (Customer order : list) {
            System.out.println(order);
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

再次运行以上test3方法,Eclipse控制台打印如下: 
这里写图片描述 
结论:使用迫切连接,结果可能出现重复,所以要使用distinct来去除重复。

外连接

左外连接

左外连接使用的是left outer join。以码明示,在HQLJoinTest单元测试类中编写如下方法:

public class HQLJoinTest {

    // 演示外连接
    @Test
    public void test4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<Object[]> list = session.createQuery("from Customer c left outer join c.orders").list(); // 左外连接

        for (Object[] objs : list) {
            for (Object obj : objs) {
                System.out.print(obj + "\t");
            }
            System.out.println();
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

运行以上test4方法,Eclipse控制台打印如下: 
这里写图片描述

右外连接

右外连接使用的是right outer join。以码明示,将HQLJoinTest单元测试类中的test4方法改为:

public class HQLJoinTest {

    // 演示外连接
    @Test
    public void test4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<Object[]> list = session.createQuery("from Customer c right outer join c.orders").list(); // 右外连接

        for (Object[] objs : list) {
            for (Object obj : objs) {
                System.out.print(obj + "\t");
            }
            System.out.println();
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

运行以上test4方法,Eclipse控制台打印如下: 
这里写图片描述

迫切左外连接

迫切左外连接使用的是left outer join fetch。以码明示,在HQLJoinTest单元测试类中编写如下方法:

public class HQLJoinTest {

    // 演示迫切左外连接
    @Test
    public void test5() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 注意:fetch不可以与单独条件的with一起使用
        List<Customer> list = session.createQuery("select distinct c from Customer c left outer join fetch c.orders with c.id=1").list();

        for (Customer customer : list) {
            System.out.println(customer);
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

运行以上test5方法,会发现报如下异常: 
这里写图片描述 
异常发生原因:fetch不可以与单独条件的with一起使用。如果非要让fetch与单独的一个条件使用,则必须使用where关键字。故将HQLJoinTest单元测试类中的test5方法改为:

public class HQLJoinTest {

    // 演示迫切左外连接
    @Test
    public void test5() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 注意:fetch不可以与单独条件的with一起使用
        List<Customer> list = session.createQuery("select distinct c from Customer c left outer join fetch c.orders where c.id=1").list();

        for (Customer customer : list) {
            System.out.println(customer);
        }

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

再次运行以上test5方法,Eclipse控制台打印如下: 
这里写图片描述

Hibernate中的Session管理

Hibernate提供了三种管理Session的方式:

  1. Session对象的生命周期与本地线程绑定(ThreadLocal)
  2. Session对象的生命周期与JTA事务绑定(也即分布式事务管理,应用在分布式数据库里面,在此并不打算讲解)
  3. Hibernate委托程序来管理Session的生命周期

我们之前一直所使用的是第三种方式,即通过程序获取一个Session对象,然后使用它,最后关闭它(即session.close();)。 
在实际开发中我们一般使用的是前两种,但由于第二种方式是基于分布式数据库而言的,所以在此主要介绍关于本地线程绑定Session这种方式,这种方式的使用步骤为:

  1. 需要在hibernate.cfg.xml核心配置文件添加如下配置:

    <property name="hibernate.current_session_context_class">thread</property>
    • 1
  2. 在获取Session时不要再使用openSession()方法而是使用getCurrentSession()方法。故应在HibernateUtils工具类中添加如下方法:

    public static Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
    • 1
    • 2
    • 3

    如此一来,HibernateUtils工具类的代码就变为:

    public class HibernateUtils {
    
        private static Configuration config;
        private static SessionFactory sessionFactory;
    
        static {
            config = new Configuration().configure();
            sessionFactory = config.buildSessionFactory();
        }
    
        public static Session openSession() {
            return sessionFactory.openSession();
        }
    
        public static Session getCurrentSession() {
            return sessionFactory.getCurrentSession();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

本地线程绑定Session这种方式的大概内部原理可用下图来表示: 
这里写图片描述 
接下来,我们就要在cn.itheima.test包下编写一个SessionManageTest单元测试类测试Session绑定到线程中,即在SessionManageTest单元测试类中编写如下方法:

public class SessionManageTest {

    // 测试Session绑定到线程中
    @Test
    public void test1() {
        // 这时每一次获取都是一个新的Session
        Session s1 = HibernateUtils.openSession();
        Session s2 = HibernateUtils.openSession();

        System.out.println(s1 == s2); // false

        Session s3 = HibernateUtils.getCurrentSession();
        Session s4 = HibernateUtils.getCurrentSession();

        System.out.println(s3 == s4); // true
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行以上方法,Eclipse控制台打印:

false 
true

关于getCurrentSession方法使用时的注意事项

例如,我们要简单查询id为1的客户,我们相当然地会在SessionManageTest单元测试类中编写如下方法:

public class SessionManageTest {

    @Test
    public void test2() {
        Session session = HibernateUtils.getCurrentSession();
        session.beginTransaction();

        Customer customer = session.get(Customer.class, 1);
        System.out.println(customer);

        session.getTransaction().commit();
        session.close();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

上述代码执行后,会产生如下问题: 
这里写图片描述 
产生该问题的原因:使用getCurrentSession方法获取的与线程绑定的session对象,在事务关闭(提交)时,session对象也会close掉,简单说,就不需要我们再手动close掉session对象了。故应将test2方法改为:

public class SessionManageTest {

    @Test
    public void test2() {
        Session session = HibernateUtils.getCurrentSession();
        session.beginTransaction();

        Customer customer = session.get(Customer.class, 1);
        System.out.println(customer);

        session.getTransaction().commit();
    }

}

Hibernate超简单多表操作

所谓一对多映射在数据库中我们通常会通过添加外键的方式将表关联起来,表现一对多的关系。 而在Hibernate中,我们则要通过在一方持有多方的集合来实现,即在“一”的一端中使用元素表示持有“多”的一段...
  • Marksinoberg
  • Marksinoberg
  • 2016-05-20 11:51:15
  • 10907

Hibernate(一对多表操作)

  • 2014年10月08日 10:03
  • 494KB
  • 下载

hibernate多表操作1(六)多对一和一对多

hibernate多表操作多对一和一对多
  • qi325
  • qi325
  • 2017-08-14 11:04:25
  • 116

hibernate多表操作之一对多的单项操作

一对多的单项: 实例:班级中有多个学生,班级能找到学生,学生不能找到班级 hibernate出现时候还没泛型,如果hibernate没有告诉的话,Classes联系哪个Student ...
  • l7945685
  • l7945685
  • 2016-08-24 18:41:00
  • 2330

hibernare多表关联(级联操作)

级联是有方向性的,所谓的方向性指的是,在保存单的一方级联多的一方和保存多的一方级联单的一方 手写要确定我们要保存那一方,要保存用户 ,用户就是主控方。保存客户的一方,客户就是主控方 那一方是主控方就...
  • qq_37172500
  • qq_37172500
  • 2017-06-01 00:29:11
  • 217

Hibernate 查询与多表操作

Hibernate主键ID生成方式 数据库中表有主键、主键的唯一性决定了数据库表中记录唯一。缓存在Session中的数据即实例都有一个唯一的ID,ID映射了数据库中主键。那么ID如何产生呢? 1、...
  • zuosixiaonengshou
  • zuosixiaonengshou
  • 2017-03-04 16:39:39
  • 995

Hibernate(多对多表操作)

  • 2014年10月09日 10:40
  • 480KB
  • 下载

Hibernate中的表的多对多关系及操作

表的多对多关系及操作 多对多的表关系表达 表: 使用中间表,分别引用两方的ID 对象:  两方都使用集合表达 配置: 操作: inverse: 我是否要放弃...
  • CSDN_GIA
  • CSDN_GIA
  • 2017-01-23 16:43:42
  • 1400

hibernate多表操作之多对多

一对多描述的是对象与集合的关系或者对象与对象之间的关系。 更准确的说:一对多描述的对象与集合之间的关系 即:在Classes类中set集合属性,多对一描述的是对象与对象的关系,即:在Student类中...
  • l7945685
  • l7945685
  • 2016-08-26 20:11:48
  • 2895

java-hibernate框架4(hibernate多表操作,级联操作与延迟加载,多对多关系操作,hibernate缓存技术)

1、Hibernate多表操作        关系型数据库具有三种常用关系:一对一关系、一对多关系和多对多关系。        建立了一对多关系的表之间,一方中的表叫“主表”,多方中的表叫“子表”;两...
  • qq_28654189
  • qq_28654189
  • 2016-10-02 00:27:07
  • 995
收藏助手
不良信息举报
您举报文章:Hibernate中的多表操作
举报原因:
原因补充:

(最多只允许输入30个字)