SSH Chapter 07 HQL 连接查询和注解

SSH Chapter 07 HQL连接查询和注解

本章目标 :

  • 掌握Hibernate的连接查询
  • 掌握聚合函数分组查询
  • 掌握子查询
  • 掌握注解

技术内容 :

本章将介绍HQL的其他操作技能 , 如分组统计 , 自查询以及HQL优化 . 最后介绍如何使用注解代替XML文件完成对象-关系映射工作 .

1 . HQL的连接查询

基于ORM,Hibernate在对象模型和关系数据库的表之间建立了一座桥梁,通过Hibernate,程序员就不需要再使用SQL语句操纵数据库中的表,使用API直接操作JavaBean对象就可以实现数据的存储,查询,更改和删除等操作,显著降低了由于对象与关系数据库在数据表现方面的范例不匹配导致的开发成本。

1.1 各种连接查询

SQL查询一样,HQL也支持多种连接查询,如内连接外连接。我们知道在SQL中可通过JOIN子句实现多表之间的连接查询,HQL也提供了连接查询机制,还允许显示指定迫切内连接迫切左外连接

迫切连接是指不仅指定了连接查询方式,而且显示指定了关联级别的查询策略。迫切查询使用fetch关键字实现,fetch关键字表明“左边”对象用来与“右边”对象关联的属性会立即被初始化。

HQL常用的连接方式:

连接类型HQL语法适用范围
内连接inner join OR join适用于有关联关
迫切内连接inner join fetch OR join fetch系的持久化类
左外连接left outer join OR left join并且在映射文件
迫切左外连接left outer join fetch OR left join fetch中对这种关联关
右外连接right outer join OR right join系做了映射
1. 内连接

Hibernate内连接语法如下:

语法:

from Entity as e [inner] join [fetch] e.property

注意:为实体类起别名

使用内连接查询部门和员工信息 , 如示例1所示 :

示例1:

测试方法关键代码如下:

@Test
public void testInnerJoin() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        //注意使用连接语法时 需将对象的toString()方法删除掉或者注释掉
        //注意查询的两张表的集合 所以返回的是object[]
        // 否则程序会抛出java.lang.StackOverflowError
        //普通连接以对象数组封装,关联属性未初始化
        List<Object[]> list = HibernateUtil.currentSession().createQuery(
                "from Dept d inner join d.emps").list();//inner 可以省略
        for (Object[] objects : list) {
            System.out.println(objects[0] + "\t" + objects[1]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例1, Hibernate执行以下select语句:

Hibernate: 
    select
        dept0_.DEPTNO as DEPTNO1_0_0_,
        emps1_.EMPNO as EMPNO1_1_1_,
        dept0_.DNAME as DNAME2_0_0_,
        dept0_.LOC as LOC3_0_0_,
        emps1_.ENAME as ENAME2_1_1_,
        emps1_.JOB as JOB3_1_1_,
        emps1_.SAL as SAL4_1_1_,
        emps1_.HiREDATE as HiREDATE5_1_1_,
        emps1_.DEPTNO as DEPTNO6_1_1_ 
    from
        DEPT dept0_ 
    inner join
        EMP emps1_ 
            on dept0_.DEPTNO=emps1_.DEPTNO

示例1list集合中的每个元素都是一个Object数组 , 数组中的第一个元素是Dept对象 , 第二个元素是Emp对象 , Dept对象的emps集合元素没有被初始化 , 即emps集合没有存放管理的Emp对象。

接下来使用迫切内连接查询部门和员工信息 , 如示例2所示。

示例2:

测试方法的关键代码如下:

@Test
public void testInnerJoinFetch() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list = HibernateUtil.currentSession().createQuery(
                "from Dept d inner join fetch d.emps").list();
        list.forEach(d-> System.out.println(d.getDeptName()+"\n\t"+d
         .getEmps()));
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例2,Hibernate执行以下select语句:

Hibernate: 
    select
        dept0_.DEPTNO as DEPTNO1_0_0_,
        emps1_.EMPNO as EMPNO1_1_1_,
        dept0_.DNAME as DNAME2_0_0_,
        dept0_.LOC as LOC3_0_0_,
        emps1_.ENAME as ENAME2_1_1_,
        emps1_.JOB as JOB3_1_1_,
        emps1_.SAL as SAL4_1_1_,
        emps1_.HiREDATE as HiREDATE5_1_1_,
        emps1_.DEPTNO as DEPTNO6_1_1_,
        emps1_.DEPTNO as DEPTNO6_1_0__,
        emps1_.EMPNO as EMPNO1_1_0__ 
    from
        DEPT dept0_ 
    inner join
        EMP emps1_ 
            on dept0_.DEPTNO=emps1_.DEPTNO

示例2中 , list集合中的每个元素都是Dept对象 , Hibernate使用fetch关键字实现了将Emp对象读取出来后立即填充到对应的Dept对象的集合属性中。

通过观察示例1示例2在控制台的输出 , 发现会有一些重复的Dept对象 , 可以使用select distinct d from Dept d inner join fetch d.emps解决这一问题 , 如示例3所示:

示例3:

测试方法关键代码如下:

@Test
public void testInnerJoinFetch_Distinct() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list = HibernateUtil.currentSession().createQuery(
                "select distinct d from " +
                        "Dept d inner join fetch " +
                        "d.emps").list();
        list.forEach(d -> System.out.println(d.getDeptName() + "\n\t" + d.getEmps()));
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例3, 控制台输出的部门信息没有重复!

2. 外连接

(1) Hibernate左外连接语法如下:

语法 :

from Entity as e left [outer] join [fetch] e.property

注意:为实体类起别名

fetch关键字用来指定查询策略 .

修改示例3HQL语句 , 将内连接HQL语句修改为左外连接的HQL语句 : from Dept d left join fetch d.emps, 详细代码如下:

/**
 * 使用迫切左外连接查询部门和员工的信息。
 */
@Test
public void testLeftJoinFetch() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list = HibernateUtil.currentSession().createQuery(
                "from Dept d left join fetch " +
                        "d.emps").list();
        list.forEach(d -> System.out.println(d.getDeptName() + "\n\t" + d
                .getEmps()));
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

执行测试方法 , Hibernate执行以下select语句:

Hibernate: 
    select
        dept0_.DEPTNO as DEPTNO1_0_0_,
        emps1_.EMPNO as EMPNO1_1_1_,
        dept0_.DNAME as DNAME2_0_0_,
        dept0_.LOC as LOC3_0_0_,
        emps1_.ENAME as ENAME2_1_1_,
        emps1_.JOB as JOB3_1_1_,
        emps1_.SAL as SAL4_1_1_,
        emps1_.HiREDATE as HiREDATE5_1_1_,
        emps1_.DEPTNO as DEPTNO6_1_1_,
        emps1_.DEPTNO as DEPTNO6_1_0__,
        emps1_.EMPNO as EMPNO1_1_0__ 
    from
        DEPT dept0_ 
    left outer join
        EMP emps1_ 
            on dept0_.DEPTNO=emps1_.DEPTNO

Hibernate使用fetch关键字实现了将Emp对象读出来后立即填充到对应的Dept对象的集合属性中 .

(2) Hibernate 的右外连接语法如下:

语法:

from Entity as e right [outer] join e.property

注意:为实体类起别名

注意:

fetch关键字只对 inner joinleft join有效 .

对于right outer join而言 , 由于作为关系容器的左边对象可能为null , 所以也就无法通过fetch关键字强制Hibernate进行集合填充操作.

在实际开发中很少使用右外连接 .

3. 等值连接

HQL支持SQL风格的等值连接等值连接适用于**两个类之间没有定义任何关联时,如统计报表数据。**在where子句中,通过属性作为筛选条件,语法如下:

from Dept d , Emp e where d=e.dept

示例4:

演示等值连接 , 增加测试方法 , 代码如下:

/**
 * 使用等值连接查询部门和员工的信息。
 */
@Test
public void testCross() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Object[]> list = HibernateUtil.currentSession().createQuery(
                "from Dept as d,Emp as e " +
                        "where d=e.dept").list();//注意类型必须匹配
        for (Object[] obj : list) {
            System.out.println(obj[0] + "\t" + obj[1]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例4测试的方法 , Hibernate执行以下select语句:

Hibernate: 
    select
        dept0_.DEPTNO as DEPTNO1_0_0_,
        emp1_.EMPNO as EMPNO1_1_1_,
        dept0_.DNAME as DNAME2_0_0_,
        dept0_.LOC as LOC3_0_0_,
        emp1_.ENAME as ENAME2_1_1_,
        emp1_.JOB as JOB3_1_1_,
        emp1_.SAL as SAL4_1_1_,
        emp1_.HiREDATE as HiREDATE5_1_1_,
        emp1_.DEPTNO as DEPTNO6_1_1_ 
    from
        DEPT dept0_ cross 
    join
        EMP emp1_ 
    where
        dept0_.DEPTNO=emp1_.DEPTNO

使用等值连接时应避免"from Dept , Emp"这样的语句出现 . 执行这条HQL查询语句 , 返回DEPT表和EMP表交叉组合 , 结果集的记录数为两个表的记录数之积 , 也就是数据库中的笛卡尔积 . 这样的查询结果没有意义 .

4. 隐式内连接

HQL查询语句中,对Emp类可以通过dept.deptName的形式访问其关联的dept对象的deptName属性。

使用隐式内连接按部门条件查询员工信息 , 如示例5所示:

示例5:

测试方法中的关键代码如下:

@Test
public void testImplInnerJoin() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Emp> list = HibernateUtil.currentSession().createQuery(
                "from Emp e where e.dept.deptName=:deptName").setParameter("deptName","SALES").list();
        list.forEach(System.out::println);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例5 , Hibernate执行以下select语句:

Hibernate: 
    select
        emp0_.EMPNO as EMPNO1_1_,
        emp0_.ENAME as ENAME2_1_,
        emp0_.JOB as JOB3_1_,
        emp0_.SAL as SAL4_1_,
        emp0_.HiREDATE as HiREDATE5_1_,
        emp0_.DEPTNO as DEPTNO6_1_ 
    from
        EMP emp0_ cross 
    join
        DEPT dept1_ 
    where
        emp0_.DEPTNO=dept1_.DEPTNO 
        and dept1_.DNAME=?

示例5中的HQL语句未使用任何连接的语法 , 而Hibernate会根据类型之间的关联关系 , 自动使用等值连接(等效内连接)的方式实现查询 , 这是所谓的隐式内连接 .

隐式内连接使得编写HQL语句时能够以更加面向对象的方式进行思考,更多地依据对象之间的关系,而不必过多考虑数据库表的结构。

隐式内连接也可以用在select子句中 , 例如:

select e.empName , e.dept.deptName from Emp e

2 . 分组统计数据

HQLSQL语句一样,使用GROUP BY关键字对数据分组,使用HAVING关键字对分组数据设定约束条件,从而对数据完成分组和统计。基本语法如下:

语法:

[select xxx] from xxx [where xxx] [group by xxx [having xxx] ] [order by xxx]

2.1 聚合函数

聚合函数:常被用来实现数据统计功能 , HQL查询语句中常用的聚合函数如下:

函数名称说明
count()统计记录条数
sum()求和
max()求最大值
min()求最小值
avg()求平均值
(1) count( ) : 统计记录数

例如 : 查询DEPT表中所有记录的条数:

测试方法关键代码如下:

/**
 * 查询DEPT表中所有记录条数
 */
@Test
public void testDeptCount() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        Long count = HibernateUtil.currentSession().createQuery(
                "select count(id) from Dept", Long.class).uniqueResult();
        System.out.println(count);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

执行测试方法 , Hibernate执行以下SQL语句:

Hibernate: 
    select
        count(dept0_.DEPTNO) as col_0_0_ 
    from
        DEPT dept0_
(2) sum( ) : 求和

例如 : 计算所有员工应发的工资总和 :

测试方法代码如下:

/**
 * 计算EMP表中所有员工应发的工资总和
 */
@Test
public void testTotalSalary() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        double sum = HibernateUtil.currentSession().createQuery(
                "select sum(salary) from Emp", Double.class).uniqueResult();
        System.out.println(sum);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法 , Hibernate执行以下SQL语句:

Hibernate: 
    select
        sum(emp0_.SAL) as col_0_0_ 
    from
        EMP emp0_
(3) min( ) : 求最小值

例如 : 计算员工的最低工资是多少

测试方法关键代码如下:

/**
 * 计算EMP表中员工的最低工资是多少
 */
@Test
public void testMinSalary() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        double min = HibernateUtil.currentSession().createQuery(
                "select min(salary) from Emp", Double.class).uniqueResult();
        System.out.println(min);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法 , Hibernate执行以下SQL语句:

Hibernate: 
    select
        min(emp0_.SAL) as col_0_0_ 
    from
        EMP emp0_
(4) max( ) : 求最大值

例如 : 员工的最高工资是多少

测试方法关键代码如下:

/**
 * 计算EMP表中员工的最高工资是多少
 */
@Test
public void testMaxSalary() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        double max = HibernateUtil.currentSession().createQuery(
                "select max(salary) from Emp", Double.class).uniqueResult();
        System.out.println(max);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法 , Hibernate执行以下SQL语句:

Hibernate: 
    select
        max(emp0_.SAL) as col_0_0_ 
    from
        EMP emp0_
(5) avg() : 求平均值 :

例如 : 员工的平均工资是多少

测试方法关键代码如下:

/**
 * 计算EMP表中员工的平均工资是多少
 */
@Test
public void testAvgSalary() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        double avg = HibernateUtil.currentSession().createQuery(
                "select avg(salary) from Emp", Double.class).uniqueResult();
        System.out.println(avg);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法 , Hibernate执行以下SQL语句:

Hibernate: 
    select
        avg(emp0_.SAL) as col_0_0_ 
    from
        EMP emp0_
小结 :

HQL查询语句很灵活,它提供了SQL常用的几乎所有功能,可以利用select子句同时查询出多个聚合函数的结果。例如 , 查询出员工最低工资,最高工资以及平均工资:

测试方法关键代码如下:

/**
 * 计算EMP表中最低工资,最高工资,以及平均工资
 */
@Test
public void testSalary() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        Object[] salarys =
                (Object[]) HibernateUtil.currentSession().createQuery(
                        "select min(salary),max(salary) ,avg(salary) from Emp").uniqueResult();
        System.out.println(salarys[0] + "," + salarys[1] + "," + salarys[2]);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

因为选取多个对象 , 所以uniqueResult()方法返回的是一个Object数组 .

经验 : HQL查询语句可以返回各种类型的查询结果 . 调试程序时 , 当不能确定查询结果的类型时 , 可以使用以下方法来确定查询结果类型 。

例如 : 统计职位记录 , 判断统计结果的数据类型。

/**
 * 统计职位记录 , 判断统计结果的数据类型
 */
@Test
public void testGetType() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        Object count = HibernateUtil.currentSession().createQuery(
                        "select count(distinct job) from Emp").uniqueResult();
        System.out.println(count.getClass().getName());
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

count.getClass().getName()显示查询结果类型为:java.lang.Long.

2.2 分组查询

HQL查询语句使用group by子句进行分组查询 . 使用having字句筛选分组结果 . 下面通过示例说明 .

(1) 按职位统计员工个数

示例6所示:

示例6:

测试方法关键代码如下:

@Test
public void testEg_1() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Object[]> list =
                HibernateUtil.currentSession().createQuery(
                        "select e.job,count(e) from Emp e group by e.job").list();
        for (Object[] obj : list) {
            System.out.println(obj[0] + "," + obj[1]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例6 , Hibernate执行以下select语句:

Hibernate: 
    select
        emp0_.JOB as col_0_0_,
        count(emp0_.EMPNO) as col_1_0_ 
    from
        EMP emp0_ 
    group by
        emp0_.JOB

Querylist()方法返回集合中包含Object[]类型的元素 , 每个Object[]对应查询结果中的一条记录 , 数组第一个元素是职位名称 , 第二个元素是该职位的员工个数.

(2). 统计各部门的平均工资

示例7所示:

示例7:

测试方法中的关键代码如下:

/**
 * 统计各部门的平均工资。
 */
@Test
public void testEg_2() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Object[]> list =
                HibernateUtil.currentSession().createQuery(
                        "select e.dept.deptName,avg(e.salary) from Emp e " +
                                "group by e.dept.deptName").list();
        for (Object[] obj : list) {
            System.out.println(obj[0] + "," + obj[1]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例7时,Hibernate会执行以下select语句:

Hibernate: 
    select
        dept1_.DNAME as col_0_0_,
        avg(emp0_.SAL) as col_1_0_ 
    from
        EMP emp0_ cross 
    join
        DEPT dept1_ 
    where
        emp0_.DEPTNO=dept1_.DEPTNO 
    group by
        dept1_.DNAME

Querylist()方法返回集合中包含Object[]类型的元素 , 每个Object[]对应查询结果中的一条记录 , 数组第一个元素是部门名称 , 第二个元素是该部门员工的平均工资.

(3) 统计各个职位的最低工资和最高工资 .

示例8所示:

示例8:

测试方法中的关键代码如下:

/**
 * 统计各个职位的最低工资和最高工资 
 */
@Test
public void testEg_3() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Object[]> list =
                HibernateUtil.currentSession().createQuery(
                        "select e.job,min(e.salary),max(e.salary) from " +
                                "Emp e group by e.job").list();
        for (Object[] obj : list) {
            System.out.println(obj[0] + "," + obj[1] +","+obj[2]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例8,Hibernate执行以下查询语句:

Hibernate: 
    select
        emp0_.JOB as col_0_0_,
        min(emp0_.SAL) as col_1_0_,
        max(emp0_.SAL) as col_2_0_ 
    from
        EMP emp0_ 
    group by
        emp0_.JOB

Querylist()方法返回集合中包含Object[]类型的元素 , 每个Object[]对应查询结果中的一条记录 , 数组第一个元素是职位名称 , 第二个元素是该职位员工的最低工资 , 第三个元素是该职位员工的最高工资.

(4) 统计各个部门平均工资高于4000的部门名称

输出部门名称 , 部门的平均工资 . 如示例9所示:

示例9:

测试方法关键代码如下:

/**
 * 统计各个部门平均工资高于4000的部门名称 , 输出部门名称 , 部门的平均工资
 */
@Test
public void testEg_4() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Object[]> list =
                HibernateUtil.currentSession().createQuery(
                        "select e.dept.deptName,avg(e.salary) from " +
                                "Emp e group by e.dept.deptName " +
                                " having avg(e.salary) > 4000 ").list();
        for (Object[] obj : list) {
            System.out.println(obj[0] + "," + obj[1]);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例9,Hibernate执行以下查询语句:

Hibernate: 
    select
        dept1_.DNAME as col_0_0_,
        avg(emp0_.SAL) as col_1_0_ 
    from
        EMP emp0_ cross 
    join
        DEPT dept1_ 
    where
        emp0_.DEPTNO=dept1_.DEPTNO 
    group by
        dept1_.DNAME 
    having
        avg(emp0_.SAL)>4000

having字句用于筛选分组结果 . Querylist()方法返回集合中包含Object[]类型的元素 , 每个Object[]对应查询结果中的一条记录 , 数组第一个元素是部门名称 , 第二个元素是该部门员工的平均工资 .

经验:

使用select字句时 , Hibernate返回的查询结果为关系数据而不是持久化对象 , 不会占用Session缓存 . 为了方便访问 , 可以定义一个JavaBean 来封装查询结果中的关系数据 , 使应用程序可以按照面向对象的方式来访问查询结果 . 代码示例10所示:

示例10:

定义普通的JavaBean , 用来封装查询结果中的关系数据,代码如下:

/**
 * 封装查询数据
 */
public class DeptSalary {
    private String deptName;
    private Double avgSalary;

    public DeptSalary() {
    }

    public DeptSalary(String deptName, Double avgSalary) {
        this.deptName = deptName;
        this.avgSalary = avgSalary;
    }

    //省略getter setter toString
}

测试方法代码如下:

/**
 * 统计各个部门平均工资高于2000元的部门名称,打印部门名称、部门平均工资,使用JavaBean封装查询结果
 */
@Test
public void testEg_5() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<DeptSalary> list =
                HibernateUtil.currentSession().createQuery(
                        "select new cn.hibernatedemo.entity.DeptSalary(" +
                                "e.dept.deptName,avg(e.salary))" +
                                "from " +
                                "Emp e group by e.dept.deptName " +
                                " having avg(e.salary) > 2000 ").list();
        for (DeptSalary deptSalary: list) {
            System.out.println(deptSalary);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法 , Hibernate执行的SQL语句如下:

Hibernate: 
    select
        dept1_.DNAME as col_0_0_,
        avg(emp0_.SAL) as col_1_0_ 
    from
        EMP emp0_ cross 
    join
        DEPT dept1_ 
    where
        emp0_.DEPTNO=dept1_.DEPTNO 
    group by
        dept1_.DNAME 
    having
        avg(emp0_.SAL)>2000

控制台打印的结果如下:

DeptSalary{deptName='ACCOUNTING', avgSalary=2916.6666666666665}
DeptSalary{deptName='RESEARCH', avgSalary=2518.75}

3 . 子查询

子查询应用在HQL语句的where字句中 , 子查询语句需放在()里面 .

例如 , 查询工资高于平均工资的员工 , 代码如下

from Emp e where e.salary > (select avg(salary) from Emp)

3.1 子查询关键字

如果子查询语句返回多条记录 , 可以使用下标的关键字进行量化:

关键字说明
all返回的所有记录
any返回的任意一条记录
some“any”意思相同
in“=any”意思相同
exists至少返回一条记录

下面演示使用表中的关键字进行查询 .

(1) 查询所有员工工资都小于5000元的部门

示例11所示:

示例11:

测试方法关键代码如下:

/**
 * 查询所有工资都小于5000元的部门
 */
@Test
public void testEg_6() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        //注意此处的hql语句 必须将条件写在之前 all写在之后 必须使用该类关联的属性
        //若单独使用Emp 则不会有连接条件 所以必须使用 dept的emps属性作为子查询的对象
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where 5000 > all(select e.salary " +
                                "from d.emps e )").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例11,得到与以下类似的查询结果:

Dept{deptNo=20, deptName='RESEARCH', location='DALLAS'}
Dept{deptNo=30, deptName='SALES', location='CHICAGO'}
Dept{deptNo=40, deptName='OPERATIONS', location='BOSTON'}

示例11中 , 可以发现没有员工的40号部门也会被查询出来 , 若不想查询没有员工的部门 , 可以使用下面的size属性进行处理:

修改测试方法 , 关键代码如下:

/**
 * 查询所有工资都小于5000元的部门
 */
@Test
public void testEg_7() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where 5000 > all(select e.salary " +
                                "from d.emps e ) and d.emps.size > 0").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行测试方法,查询结果如下:

Dept{deptNo=20, deptName='RESEARCH', location='DALLAS'}
Dept{deptNo=30, deptName='SALES', location='CHICAGO'}
(2) 查询至少有一位员工的工资低于5000元的部门

示例12所示:

示例12:

测试方法中的关键代码如下:

/**
 * 查询至少有一位员工的工资低于5000元的部门
 */
@Test
public void testEg_8() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where 5000 > any(select e.salary " +
                                "from d.emps e )").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例12 , 得到以下类似的查询结果:

Dept{deptNo=10, deptName='ACCOUNTING', location='NEW YORK'}
Dept{deptNo=20, deptName='RESEARCH', location='DALLAS'}
Dept{deptNo=30, deptName='SALES', location='CHICAGO'}

ACCOUNTING,RESEARCH,SALES都有工资低于5000元的员工.

(3) 查询有员工工资正好是5000元的部门

示例13所示:

示例13:

测试方法中的关键代码如下:

/**
 * 查询有员工工资正好是5000元的部门
 */
@Test
public void testEg_9() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where 5000 = any(select e.salary " +
                                "from d.emps e )").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例13, 得到与以下类似的查询结果:

Dept{deptNo=10, deptName='ACCOUNTING', location='NEW YORK'}

ACCOUNTING有一位员工的工资正好是5000元 . 这条HQL语句也可以采用以下两种形式:

from Dept d where 5000=some(select e.salary from d.emps e)

或者

from Dept d where 5000 in (select e.salary from d.emps e)
(4) 查询至少有一位员工的部门

示例14所示:

示例14:

测试方法中关键代码如下:

/**
 * 查询至少有一位员工的部门
 */
@Test
public void testEg_10() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where exists(from d.emps)").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例14 , 得到以下类似的查询结果:

Dept{deptNo=10, deptName='ACCOUNTING', location='NEW YORK'}
Dept{deptNo=20, deptName='RESEARCH', location='DALLAS'}
Dept{deptNo=30, deptName='SALES', location='CHICAGO'}

ACCOUNTING,RESEARCH,SALES至少有一位员工 .

3.2 操作集合的函数或属性

HQL提供了操作结合的函数或属性 , 如下表所示:

函数或属性说 明
size() 或 size获取集合中元素的数目
minIndex() minIndex对于建立了索引的集合,获得最小的索引
maxIndex() maxIndex对于建立了索引的集合,获得最大的索引
minElement() minElement对于包含基本类型元素的集合,获取最小值元素
maxElement() maxElement对于包含基本类型元素的集合,获取最大值元素
elements()获取集合中的所有元素

下面使用部分函数进行查询

(1). 查询指定员工所在的部门

示例15所示 :

示例15:

测试方法关键代码如下:

/**
 * 查询指定员工所在的部门
 */
@Test
public void testEg_11() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        //构建查询条件
        Emp emp = new Emp();
        emp.setEmpNo(7934);
        //注意:
        //方式一:from Dept d where :emp in (from d.emps) 
        //方式二:如果使用elements 则必须不能使用from 
        //直接写该类关联的那个属性即可
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where :emp in elements(d.emps)").setParameter("emp",emp).list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

运行示例15 , 得到以下类似的查询结果:

Dept{deptNo=10, deptName='ACCOUNTING', location='NEW YORK'}

编号是7934的员工 在10号部门 , 此员工对应的OID7934 . 这条HQL查询语句也可以采用以下形式:

from Dept d where :emp in (from d.emps)
(2). 查询员工个数大于5的部门

示例16所示:

示例16:

测试方法中的关键代码 :

/**
 * 查询指定员工个数大于5的部门
 */
@Test
public void testEg_13() {
    Transaction transaction = null;
    try {
        transaction = HibernateUtil.currentSession().beginTransaction();
        //也可以使用:from Dept d where size(d.emps) > 5
        List<Dept> list =
                HibernateUtil.currentSession().createQuery(
                        "from Dept d where  d.emps.size>5").list();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        HibernateUtil.rollback(transaction);
    }
}

这条HQL查询语句也可以采用以下形式:

from Dept d where size(d.emps) > 5

以上HQL查询语句对应的SQL语句中包含子查询:

Hibernate: 
    select
        dept0_.DEPTNO as DEPTNO1_0_,
        dept0_.DNAME as DNAME2_0_,
        dept0_.LOC as LOC3_0_ 
    from
        DEPT dept0_ 
    where
        (
            select
                count(emps1_.DEPTNO) 
            from
                EMP emps1_ 
            where
                dept0_.DEPTNO=emps1_.DEPTNO
        )>5

4 . 查询性能优化

1. Hibernate查询优化策略

(1) 使用迫切左外连接或迫切内连接查询策略,配置二级缓存和查询等方式 , 减少select语句的数目 , 降低访问数据库的频率 .

(2) 使用延迟加载等方式避免加载多余的不需要访问的数据 .

(3) 使用Query接口的iterate()方法减少select语句中的字段 , 降低访问数据库的数据量 , 并结合缓存等机制减少数据库访问次数 , 提高查询效率

经验:

list()方法:

无法利用一级缓存和二级缓存(对缓存只写不读),它只能在开启查询缓存的前提下使用查询缓存
Hibernate是通过一条Select SQL获取所有的记录。并将其读出,填入到POJO中返回.所以list()方法返回的每个对象都是完整的(对象中的每个属性都被表中的字段填充上了)。所以返回的每个对象都是原本的对象。

iterate()方法:

iterate()方法所返回的对象中仅包含了主键值(标识符),只有当你对iterate中的对象进行操作时,Hibernate才会向数据库再次发送SQL语句来获取该对象的属性值。所以返回的对象是代理对象。list方法需要1条SQL语句,iterate可能需要N+1条。
可以充分利用缓存,可以对缓存读和写。返回对象的主键值,hibernate首先会根据这个id 在本地Cache 内寻找对应的数据,如果没找到,再去数据库中检索。如果目标数据只读或者读取频繁,使用iterate()方法可以减少性能开销。

2. HQL优化

HQL优化是Hibernate程序性能优化的一个方面,HQLSQL的语法非常类似。HQL是基于SQL的,只是增加了面向对象的封装。如果抛开HQLHibernate本身一些缓存机制的关联,HQL的优化技巧同SQL的优化技巧一样。在编写HQL时,需要注意以下几个原则:

(1) 避免or操作的使用不当

如果where子句中有多个条件,并且其中某个条件没有索引,使用or将导致全表扫描。假定在HOUSE表中TITLE有索引,PRICE没有索引,执行以下HQL语句:

from House where title='出租一居室' or price < 1500

当price比较时,会引起全表扫描。

(2) 避免使用not

如果where条件的子句包含not关键字,那么执行时该字段的索引失效。这些语句需要分成不同情况区别对待,如查询租金不多于1800元的租房信息的HQL语句:

from House as h where not (h.price>1800)

对于这种不大于、不小于的条件,建议使用比较运算符代替not,如下不大于就是小于等于。所以上述查询的HQL语句也可以为:

from House as h where h.price <= 1800
(3) 避免like的特殊形式

如果like以一个"%""_"开始即前模糊,则该字段的索引不起作用。但是非常遗憾的是,对于这种问题并没有额外的解决方法,只能通过改变索引字段的形式变相的解决。

(4) 避免having子句

应尽可能的在where子句而不是having子句中指定条件。having是在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销

(5) 避免使用distinct

指定distinct会导致在结果中删除重复的行。这会对处理时间造成一定的影响,因此在不要求或允许冗余时,应避免使用distinct

(6) 索引在以下情况下失效,应注意使用
  1. 只要对字段使用函数,该字段的索引不起作用,如substring(aa,1,2)='xxx'
  2. 只要对字段进行计算,该字段的索引不起作用,如price+10

5 . 注解

5.1 简介

Hibernate中使用注解,主要是为了替代映射文件,完成“类到表,属性到字段”的映射。

JPA提供了一套功能强大的注解。Hibernate直接使用了JPA的这套注解。

当然,对于JPA中的一些不足,Hibernate又开发了一些自己的注解。这些注解要么继承自JPA,要么是独立的注解,作为JPA的补充。

故,我们使用的注解,基本是javax.persistence包中的。

使用注解时需注意以下几点:

  1. 使用的均为javax.persistence包中的注解
  2. 不再需要映射文件了
  3. Hibernate主配置文件中无需指定映射文件了,但需要指定注解的实体类 , 语法如下:。(springboot这一步也省了)
<mapping class="持久化类的完全限定名" />

JPA全称Java Persistence API, 它通过JDK 5.0注解或XML描述对象和关系表的映射关系 , 并将运行期对象持久化到数据库 . Hibernate提供了JPA的实现.

5.2 使用注解配置持久化类

注 解含义和作用
@Entity将一个类声明为一个持久化类
@Table为持久化类映射指定表
@Id声明了持久化类的标识属性
@GeneratedValue定义标识属性值的生成策略
@SequenceGenerator定义序列生产器
@Column将属性映射到列(字段)
@Transient将忽略这些属性

下面举例说明使用注解映射员工信息 , 如示例17所示 :

示例17 :

持久化类Emp中的关键代码如下:

/**
 * 雇员类
 */
@Entity//将一个类声明为一个持久化类,默认所有的属性都会映射到数据库成同名字段,可使用注解忽略
@Table(name="EMP")//为持久化类映射指定表
public class Emp implements Serializable {
    @Id//声明了持久化类的标识属性
    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator = "seq_emp")//定义标识属性值的生成策略
    //注意序列必须定义起始值 以及 自增值
    @SequenceGenerator(name="seq_emp",sequenceName = "seq_emp_id",
            allocationSize = 10,initialValue = 1)//定义序列生产器
    private Integer empNo;
    @Column(name="ENAME")
    private String empName;
    private String job;
    @Column(name="SAL")
    private Double salary;
    private Date hireDate;
    @Transient
    private Dept dept;
	//省略getter setter toString
}

hibernate.cfg.xml文件中的关键代码如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.url">jdbc:oracle:thin:@localhost:1521:orcl
        </property>
        <property name="connection.username">scott</property>
        <property name="connection.password">tiger</property>
        <property name="connection.driver_class">
            oracle.jdbc.driver.OracleDriver
        </property>
        <property name="dialect">
            org.hibernate.dialect.Oracle10gDialect
        </property>
        <property name="current_session_context_class">thread</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <!--<mapping resource="cn/hibernatedemo/entity/Dept.hbm.xml"/>-->
        <!--<mapping resource="cn/hibernatedemo/entity/Emp.hbm.xml"/>-->
        <mapping class="cn.hibernatedemo.entity.Emp"/>

        <!-- DB schema will be updated if needed -->
        <!-- <property name="hbm2ddl.auto">update</property> -->
    </session-factory>
</hibernate-configuration>

示例17Emp类映射到EMP表 , empNo属性为OID , 主键采用序列生成 , 需在数据库中创建名为seq_emp_id的序列 , 语法如下:

create sequence seq_emp_id
 start with 1
increment by 10;

注解可以配置在属性定义的上方 , 或者getter方法的上方

下面对示例17中部分注解做进一步介绍 :

  • @Table : 可以省略 , 省略时默认表名与持久化类名相同
  • @GeneratedValue : 指定了OID的生成策略 , 不使用此注解时 , 默认OID由程序赋值(相当于在映射问文件中指定assigned) . 其属性strategy用于指定主键的生成策略。其值为系统定义好的四种策略之一。默认为AUTO。JPA提供了四种标准用法:
    1. GenerationType.AUTO:根据底层数据库自动选择(默认)若数据库支持自增长,则为自增长。类似于配置文件中的native生成策略。
    2. GenerationType.IDENTITY:根据数据库的Identity字段生成。类似于配置文件中的identity生成策略。
    3. GenerationType.TABLE:使用指定表来决定主键取值
    4. GenerationType.SEQUENCE:使用Sequence来决定主键的取值。类似于配置文件中的Sequence生成策略。
  • @SequenceGenerator: 设置了序列生成器 ,
    • name="seq_emp"定义序列生成器的名称为seq_name;
    • sequenceName="seq_emp_id"指定了序列的名称seq_emp_id;
    • initialValue设置了主键起始值;
    • allocationSize设置了生成器分配id时的增量 ;
  • @Column : 用于指定属性映射的数据库字段名 , 若不指定 , 默认字段名和属性相同
  • @Transient : 用于忽略不需要持久化到数据库中的属性 . dept作为关联属性 , 其映射方式将在下面中介绍 , 这里也暂时忽略该属性

注意 : 注意使用注解定义持久化类 , 在hibernate.cfg.xmlmapping元素中要使用class属性进行声明 , 指定该持久化类的全类名

示例17的映射基础上 , 完成对Emp对象的添加操作 , 如示例18所示:

示例18 :

EmpDao.java中的关键代码如下:

public class EmpDao {
    public void save(Emp emp){
        HibernateUtil.currentSession().save(emp);
    }
}

EmpBiz.java中的关键代码如下:

public class EmpBiz {
    private EmpDao empDao = new EmpDao();
    public void addNewEmp(Emp emp){
        Transaction transaction = null;
        try {
            transaction = HibernateUtil.currentSession().beginTransaction();
            empDao.save(emp);
            transaction.commit();
        } catch (Exception e) {
            e.printStackTrace();
            HibernateUtil.rollback(transaction);
        }
    }
}

测试方法的关键代码如下:

@Test
public void testAddNewEmp(){
    Emp emp = new Emp();
    emp.setEmpName("张三");
    emp.setHireDate(new Date());
    EmpBiz empBiz = new EmpBiz();
    empBiz.addNewEmp(emp);
}

5.3 使用注解配置关联关系

通过下表所示的注解可以完成对象关联关系的常用配置 .

注 解含义和作用
@OneToOne建立持久化类之间的一对一关联关系
@OneToMany建立持久化类之间的一对多关联关系
@ManyToOne建立持久化类之间的多对一关联关系
@JoinColumn和@ManyToOne配合,指定外键列
@ManyToMany建立持久化类之间的多对多关联关系

对象关联关系中使用频率最高的是一对多(多对一)关联关系 . 下面以此为例讲解注解的配置 , 如示例19所示 . 其他关联关系的注解配置与此类似 .

示例19:

持久化类Emp中的关键代码如下:

/**
 * 雇员类
 */
@Entity//将一个类声明为一个持久化类,默认所有的属性都会映射到数据库成同名字段,可使用注解忽略
@Table(name="EMP")//为持久化类映射指定表
public class Emp implements Serializable {
    @Id//声明了持久化类的标识属性
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator="seq_emp")//定义标识属性值的生成策略
    @SequenceGenerator(name="seq_emp",sequenceName = "seq_emp_id",
            allocationSize = 10,initialValue = 1)//定义序列生产器
    private Integer empNo;
    @Column(name="ENAME")
    private String empName;
    private String job;
    @Column(name="SAL")
    private Double salary;
    private Date hireDate;
    //@Transient
    @ManyToOne(fetch = FetchType.LAZY)
    //name:当前表的字段
	//referencedColumnName:引用表对应的字段,如果不注明,默认就是引用表的主键
    @JoinColumn(name = "DEPTNO",referencedColumnName = "DEPTNO")
    private Dept dept;

    //省略getter setter 以及toString
}

持久化类Dept中的关键代码如下:

/**
 * 部门信息
 */
@Entity
@Table(name = "DEPT")
public class Dept implements Serializable {
    //private Byte deptNo;
    @Id
    private Short deptNo;
    @Column(name="DNAME")
    private String deptName;
    @Column(name="LOC")
    private String location;

    /**
     * 因为mappedBy是定义在Dept中,即Dept类不负责维护级联关系.即维护者是Emp.
     * 所以:
     * 1.要将Dept的数据,赋给Emp,即用emp的setDept()方法去捆定Dept数据;
     * 2.在进行数据插入/更新session.save()/session.update()时,
     * 最后操作的是Emp,外键关系也是由Emp维护
     */
    //private Set<Emp> emps;//部门对应多个员工,即一对多的关系
    @OneToMany(mappedBy = "dept",cascade = CascadeType.ALL)
    private Set<Emp> emps = new HashSet<>();
    //方便赋值,这里可以直接创建实例化

   	//省略getter setter toString
}

hibernate.cfg.xml中的关键代码如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.url">jdbc:oracle:thin:@localhost:1521:orcl
        </property>
        <property name="connection.username">scott</property>
        <property name="connection.password">tiger</property>
        <property name="connection.driver_class">
            oracle.jdbc.driver.OracleDriver
        </property>
        <property name="dialect">
            org.hibernate.dialect.Oracle10gDialect
        </property>
        <property name="current_session_context_class">thread</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <!--<mapping resource="cn/hibernatedemo/entity/Dept.hbm.xml"/>-->
        <!--<mapping resource="cn/hibernatedemo/entity/Emp.hbm.xml"/>-->
        <mapping class="cn.hibernatedemo.entity.Emp"/>
        <mapping class="cn.hibernatedemo.entity.Dept"/>

        <!-- DB schema will be updated if needed -->
        <!-- <property name="hbm2ddl.auto">update</property> -->
    </session-factory>
</hibernate-configuration>

通过fetch属性指定加载方式,有两个值:

  • FetchType.LAZY:延迟加载
  • FetchType.EAGER:急加载

示例19中使用@ManyToOne注解配置Emp类和Dept类之间的多对一关联 . 其属性fetch=FetchType.LAZY设置关联级别采用延迟策略 ; 若不指定,则该属性的默认值是EAGER , 查询Emp时 , Hibernate将使用左外连接将相关的Dept对象一并查出。

注解@JoinColumn(name="DEPTNO")指定了维系关系的外键字段是EMP表的DEPTNO

注解@OneToMany配置了Dept类和Emp类之间的一对多的关系 . 属性mappedBy="dept"将关联关系的控制权交给Emp类的这一方 , 相当于Dept.hbm.xml中配置的inverse="true" .mappedBy属性的值是Emp类中与Dept类关联的属性名 . 属性cascade = CascadeType.ALL 设置了级联操作类型 , cascade属性可选的取值如下:

  • CascadeType.PERSIST 级联新增(又称级联保存)
  • CascadeType.MERGE 级联合并(又称级联更新)
  • CascadeType.REMOVE 级联删除
  • CascadeType.REFRESH 级联刷新
  • CascadeType.ALL 包含所有持久化方法

以上取值在使用中可以多选 , 多选时用逗号隔开

注意:用主动维护关系的一方来插入数据,不仅可以进行级联操作,也能保证外键关系的正常

基于示例19的配置 . 下面尝试添加部门 , 同时添加员工 ,修改示例19的测试方法, 如示例20所示:

示例20 :

测试方法中的关键代码如下 :

@Test
public void testAddNewEmp(){
    Emp emp = new Emp();
    emp.setEmpName("李四");
    emp.setHireDate(new Date());
    
    Dept dept = new Dept();
    dept.setDeptNo((short)50);
    dept.setDeptName("产品部");
    dept.setLocation("北京");
    //建立Emp到Dept的单向关联即可
    emp.setDept(dept);
    
    EmpBiz empBiz = new EmpBiz();
    //用主动维护关系的一方来插入数据 以保证外键关系的正常维护
    empBiz.addNewEmp(emp);
}

注意:因为一端维护关系另一端不维护关系的原因,我们必须注意避免在应用中用不维护关系的类(Dept)建立关系,因为这样建立的关系是不会在数据库中存储的。

运行测试方法 , Hibernate将保存Emp对象 , 并级联保存Dept对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值