Java
Java Web框架

Hibernate(6)

简介:Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

1. 多对多关系

1.1. 多对多的配置

  1. 创建实体类及相关的映射:

Student.java文件:

  • package com.coderap.domain;
  • import java.util.HashSet;
  • import java.util.Set;
  • public class Student {
  • private Integer id;
  • private String name;
  • private Set<Course> courses = new HashSet<Course>();
  • public Integer getId() {
  • return id;
  • }
  • public void setId(Integer id) {
  • this.id = id;
  • }
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • public Set<Course> getCourses() {
  • return courses;
  • }
  • public void setCourses(Set<Course> courses) {
  • this.courses = courses;
  • }
  • }

Course.java文件:

  • package com.coderap.domain;
  • import java.util.HashSet;
  • import java.util.Set;
  • public class Course {
  • private Integer id;
  • private String name;
  • private Set<Student> students = new HashSet<Student>();
  • public Integer getId() {
  • return id;
  • }
  • public void setId(Integer id) {
  • this.id = id;
  • }
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • public Set<Student> getStudents() {
  • return students;
  • }
  • public void setStudents(Set<Student> students) {
  • this.students = students;
  • }
  • }
  1. XML文件配置:

Student.hbm.xml文件:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Student" table="t_student">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name"></property>
  • <!-- 多对多关系 -->
  • <!--
  • set 表达集合
  • name: 集合的属性名
  • table:多对多中间表的表名
  • key 表达外键
  • column:引用我的外键名
  • many-to-many表达多对多
  • class : 集合引用方的类型
  • column:对方在中间表的外键名
  • -->
  • <set name="courses" table="t_student_course" inverse="false"
  • cascade="save-update">
  • <key column="sid"></key>
  • <many-to-many class="Course" column="cid"></many-to-many>
  • </set>
  • </class>
  • </hibernate-mapping>

Course.hbm.xml文件:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Course" table="t_course">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name"></property>
  • <set name="students" table="t_student_course" inverse="true">
  • <key column="cid"></key>
  • <many-to-many class="Student" column="sid"></many-to-many>
  • </set>
  • </class>
  • </hibernate-mapping>

同时,在上面的配置中,为了将维护关系交由Student对象来操作,所以将Student 的inverse置为falsecascade置为save-update,将Course的inverse置为true

1.2. 多对多操作

  • @Test
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Student stu1 = new Student();
  • stu1.setName("tom");
  • Student stu2 = new Student();
  • stu2.setName("jerry");
  • Course c1 = new Course();
  • c1.setName("Struts2");
  • Course c2 = new Course();
  • c2.setName("Hibernate");
  • Course c3 = new Course();
  • c3.setName("Spring");
  • stu1.getCourses().add(c1); // 维护关系,级联保存
  • stu1.getCourses().add(c2);
  • stu1.getCourses().add(c3);
  • stu2.getCourses().add(c1);
  • stu2.getCourses().add(c2);
  • stu2.getCourses().add(c3);
  • session.save(stu1);
  • session.save(stu2);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2. 抓取策略

2.1. 检索方式

  • 立即检索:立即查询,在执行查询语句时,立即查询所有的数据。
  • 延迟检索:延迟查询,在执行查询语句之后,在需要时在查询。(懒加载)

2.2. 检查策略

  • 类级别检索:当前的类的属性获取是否需要延迟。
  • 关联级别的检索:当前类 关联 另一个类是否需要延迟。

2.3. 类级别检索

  • get:立即检索。get()方法一执行,立即查询所有字段的数据。
  • load:延迟检索。默认情况,load()方法执行后,如果只使用OID的值不进行查询,如果要使用其他属性值将查询 。可以通过设置Customer.hbm.xml<class lazy="true | false">来指定检索方式,lazy默认值true,表示延迟检索,如果设置false表示立即检索:

Customer.hbm.xml:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Customer" table="t_customer">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name" type="string"></property>
  • <set name="orders" >
  • <key column="cid"></key>
  • <one-to-many class="Order" />
  • </set>
  • </class>
  • </hibernate-mapping>
  • @Test
  • // 默认值的load获得时,会返回代理对象,不查询数据库;使用时才查询
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Customer c = (Customer) session.load(Customer.class, 1);
  • System.out.println(c.getName());// 用到对象的时候该行才会发送sql语句
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

lazy置为false后,Customer.hbm.xml:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Customer" table="t_customer" lazy="false">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name" type="string"></property>
  • <set name="orders" >
  • <key column="cid"></key>
  • <one-to-many class="Order" />
  • </set>
  • </class>
  • </hibernate-mapping>
  • @Test
  • // 当将lazy置为false后,load方法执行就会发送sql语句,与get方法一致
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Customer c = (Customer) session.load(Customer.class, 1);// 发送sql语句的时机提前到了此处
  • System.out.println(c.getName());
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2.4. 关联级别检索

在查询有关联关系的数据时,加载一方的数据是否需要将另一方立即查询出;默认:情况下与之关联的数据在使用时才会加载。

对于集合的一对多的情况,可以设置<set>标签的lazyfetch来设置不同的效果:

  • lazy:是否对set数据使用懒加载
  1. true:(默认值)对集合使用才加载
  2. false:集合将会被立即加载
  3. extra:极其懒惰;如果使用集合时,只调用size()方法查询数量,Hibernate会发送count语句,只查询数量,不加载集合内数据。
  • fetch:决定加载集合使用的sql语句种类
  1. select:(默认值)普通select查询
  2. join表连接语句查询集合数据
  3. subselect:使用子查询一次加载多个集合数据
fetch lazy 结论
select true 默认值,会在使用集合时加载,普通select语句
select false 立刻使用select语句加载集合数据
select extra 会在使用集合时加载,普通select语句,如果只是获得集合的长度,会只发送count语句查询长度
join true 查询集合时使用表链接查询,会立刻加载集合数据
join false 查询集合时使用表链接查询,会立刻加载集合数据
join extra 查询集合时使用表链接查询,会立刻加载集合数据
subselect true 会在使用集合时加载,子查询语句
subselect false 会在查询用户时,立即使用子查询加载客户的订单数据
subselect extra 会在使用集合时加载,子查询语句,如果只是获得集合的长度,会只发送count语句查询长度

相关Customer.hbm.xml文件:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Customer" table="t_customer">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name" type="string"></property>
  • <!--
  • lazy:是否对set数据使用懒加载
  • fetch:决定加载集合使用的sql语句种类
  • -->
  • <set name="orders" lazy="true|false|extra" fetch="select|join|subselect">
  • <key column="cid"></key>
  • <one-to-many class="Order" />
  • </set>
  • </class>
  • </hibernate-mapping>

测试代码:

  • @Test
  • // 关联级别懒加载
  • // 默认:与我关联的数据,在使用时才会加载
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Customer c = (Customer) session.get(Customer.class, 1);
  • for (Order o : c.getOrders()) { // 此处才会查询订单数据
  • System.out.println(o.getName());
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // 关联级别懒加载
  • // lazy:false
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Customer c = (Customer) session.get(Customer.class, 1); // 此处就会查询订单数据
  • for (Order o : c.getOrders()) {
  • System.out.println(o.getName());
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // 关联级别懒加载
  • // lazy:false/true
  • // fetch:join
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Customer c = (Customer) session.get(Customer.class, 1);// 此处就会查询订单数据
  • for (Order o : c.getOrders()) {
  • System.out.println(o.getName());
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // 关联级别懒加载
  • // lazy:true
  • // fetch:subselect
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<Customer> list = session.createQuery("from Customer").list();
  • for (Customer c : list) {
  • System.out.println(c.getName() + "下单数量:" + c.getOrders().size());// 此处才会查询订单数据
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // 关联级别懒加载
  • // lazy:false
  • // fetch:subselect
  • public void fun5() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<Customer> list = session.createQuery("from Customer").list();// 此处就会查询订单数据
  • for (Customer c : list) {
  • System.out.println(c.getName() + "下单数量:" + c.getOrders().size());
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // 关联级别懒加载
  • // lazy:extra
  • // fetch:select
  • public void fun6() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 查询Customer
  • Customer c = (Customer) session.get(Customer.class, 1);
  • // 查询Customer下订单的数量
  • System.out.println(c.getOrders().size());// 此处只会使用count查询订单数量
  • // 真正使用订单中的数据
  • for (Order o : c.getOrders()) {
  • System.out.println(o.getName());// 此处才会查询订单数据
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @SuppressWarnings("unchecked")
  • @Test
  • // 关联级别懒加载
  • // lazy:extra
  • // fetch:subselect
  • public void fun7() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<Customer> list = session.createQuery("from Customer").list();
  • for (Customer c : list) {
  • System.out.println(c.getName() + "下单数量:" + c.getOrders().size());// 此处只会使用count查询订单数量
  • }
  • for (Customer c : list) {
  • for (Order o : c.getOrders()) {
  • System.out.println(c.getName() + "下单名称:" + o.getName());// 此处才会查询订单数据
  • }
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

对于集合的多对一的情况,可以设置<set>标签的lazyfetch来设置不同的效果:

  • lazy:是否对set数据使用懒加载
  1. true:(默认值)对集合使用才加载;
  2. false:集合将会被立即加载;
  3. proxy:看“一”的一方对象的类加载策略来决定;
  4. no-proxy:不做研究。
  • fetch:决定加载集合使用的sql语句种类
  1. select:(默认值)普通select查询
  2. join表连接语句查询集合数据
fetch lazy 结论
select false 加载订单时,立即加载客户数据,普通select语句加载客户
select proxy 根据“一”的一方对象的类加载策略,如果其lazy为false,则同上;如果其lazy为true,则加载订单时先不加载客户数据,使用客户数据时才加载
join false 使用表连接查询订单以及对应客户信息,lazy属性无效
join proxy 使用表连接查询订单以及对应客户信息,lazy属性无效

如Order.hbm.xml:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.itheima.domain">
  • <class name="Order" table="t_order">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name" type="string"></property>
  • <!-- lazy false proxy no-proxy : 不做研究. fetch= select join -->
  • <many-to-one name="customer" column="cid" class="Customer" lazy="false|proxy|no-proxy" fetch="select|join"></many-to-one>
  • </class>
  • </hibernate-mapping>
  • @Test
  • // fetch:select
  • // lazy:false
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Order o = (Order) session.get(Order.class, 2);// 立即加载Customer数据
  • System.out.println(o.getCustomer().getName());
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // fetch:select
  • // lazy:proxy
  • // Customer的lazy:false
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Order o = (Order) session.get(Order.class, 2);// 立即加载Customer数据
  • System.out.println(o.getCustomer().getName());
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // fetch:select
  • // lazy:proxy
  • // Customer的lazy:true
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Order o = (Order) session.get(Order.class, 2);
  • System.out.println(o.getCustomer().getName());// 此时才加载Customer数据
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • @Test
  • // fetch:join
  • // lazy:proxy|false
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • Order o = (Order) session.get(Order.class, 2);// 立即加载Customer数据,使用表连接
  • System.out.println(o.getCustomer().getName());
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2.5. 批量查询

在上面的例子中,当客户关联查询订单时,查询客户相关订单时,会对每一个订单使用一条select语句查询进行;批量查询会使用in语句减少查询订单的语句条数。

  • 默认:select * from t_order where customer_id = ?
  • 批量:select * from t_order where customer_id in (?,?,?,?)

如Customer.hbm.xml文件:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <!DOCTYPE hibernate-mapping PUBLIC
  • "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  • "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  • <hibernate-mapping package="com.coderap.domain">
  • <class name="Customer" table="t_customer">
  • <id name="id" column="id">
  • <generator class="native"></generator>
  • </id>
  • <property name="name" column="name" type="string"></property>
  • <!--
  • batch-size: 决定一次加载几个对象的集合数据;使用in条件加载多个用户的订单
  • -->
  • <set name="orders" batch-size="2" >
  • <key column="cid"></key>
  • <one-to-many class="Order" />
  • </set>
  • </class>
  • </hibernate-mapping>
  • @Test
  • // 查询所有客户
  • // 遍历客户,打印客户下的订单信息
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<Customer> list = session.createQuery("from Customer").list();
  • for (Customer c : list) {
  • System.out.println(c.getOrders().size());
  • }
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2.6. 检索总结

检索策略 优点 缺点 优先考虑使用的场合
立即检索 对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 (1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 (1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存
延迟检索 由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 (1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象
表连接检索 (1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 (1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 (1)多对一或一对一关联 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。