hibernate

什么是Hibernate及其作用


Hibernate是一个数据访问(dao)框架(持久层框架),可以简化数据库操作代码,提升开发效率。Hibernate框架是对JDBC技术的封装,类似的框架有MyBatis、JPA等。

原有使用JDBC+SQL方式对数据库操作时,有以下几点弊端:

1. 表字段多的情况下,需要写复杂的sql语句

2. 不同数据库SQL语句存在一定差异,移植性较差 (一开始用的是sqlserver,换成oracle则需要修改语句)

3. 需要编写大量的代码实现实体对象和表记录之间的转换,非常繁琐。

利用Hibernate框架可以解决上述问题。有以下优点:

1. 可以自动生成sql

2. 可以自动完成实体类和表记录之间的转换(映射)

3. 可以增强数据库的移植性

Hibernate设计原理


Hibernate框架是基于ORM思想对JDBC进行封装设计的。ORM:Object Relation Mapping 被称为对象关系映射

主要思想是:能够完成程序中Java实体类和关系数据库中表记录之间的转换。可发者可以直接将对象直接写入数据库,查询时可以直接从数据库中以对象的形式取出,中间对象和记录的转换细节由ORM框架负责,开发者对底层细节不用关心。

目前流行的ORM框架(ORM工具)有以下几个:Hibernate、MyBatis  、JPA等。

利用Hibernate框架可简化数据库操作,他将JDBC和SQL语句封装起来,不需要使用者编写.。发者需要了解和使用Hibernate API。

Hibernate框架结构


1. Hibernate框架使用时,需要以下几个重要文件:

a. 实体类(*.java) n个  与数据表对应,用于封装数据表的一行记录。

b. XML映射文件(*.hbm.xml) n个  用于描述实体类与数据表之间的对应关系.类属性与表字段之间的对应关系,一个实体类对应一个XML映射文。

c. 主配置文件(Hibernate.cfg.xml) 1个  用于指定连接数据库的参数,框架参数等。

2. Hibernate编程API

a. Configuration

Configuration conf=new Configuration();
conf.configure("hibernate.cfg.xml");//负责加载hibernate.cfg.xml配置文件和映射文件

b. SessionFactory

 SessionFactory factory=conf.buildSessionFactory();//负责生成数据库的连接对象(Session)

c. Session

Session session=factory.openSession();//原Connection对象的封装,代表Hibernate与数据库之间的一次连接.负责执行增删改查操作.
session.load()
session.get();//查询记录
session.save();// 添加记录--插入
session.update(); //更新记录--更新,一次只能更新1行,按主键做条件
session.delete();//删除记录--删除,一次只能删除1行,按主键做条件

d. Query 用于执行非主键查询的操作

e. Transaction 用于事务控制,将两个或者两个以上的DML操作封装成一个整个操作

Hibernate基本应用


使用步骤

1. 引入hibernate开发包、数据库驱动包

2. 引入hibernate.cfg.xml配置文件(src下)

3. 根据数据表编写一个实体类

4. 编写hbm.xml映射描述文件(描述实体类和表之间的对应关系)

5. 使用hibernate API编程

案例

1. 引入jar包

2. 添加hibernate配置文件(hibernate.cfg.xml)。注意应该放在源文件的src目录下,默认为Hibernate.cfg.xml,文件内容是Hibernate工作时必须用到的基础信息。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 数据库连接信息-->
        <property name="connection.url">
            jdbc:mysql://localhost:3306/qin?useUnicode=true&amp;characterEncoding=utf8
        </property>
        <property name="connection.username">root</property>
        <property name="connection.password">sa</property>
        <property name="connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <!--dialect是方言,用于配置生成生成对哪个数据库的SQL语句-->
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <!--显示底层生成的SQL语句,将执行SQL打印到控制台,一般用于SQL调优-->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <!-- 指定映射描述文件,可以添加多个mapping定义 -->
        <mapping resource="org/tarena/mapping/User.hbm.xml" />
    </session-factory>
</hibernate-configuration>

3. 根据数据表,编写实体类,映射文件

User.java

public class User implements Serializable{
         private Integer id;
         private String email;private String nickname;
         private String password;//get/set方法
}

User.hbm.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- 
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping package="org.entity">
    <!-- name指定实体类  table指定表名  catalog指定是哪个数据库用户-->
    <class name="User" table="d_user" catalog="testq">
         <!-- id仅用于主键字段的映射 -->
        <id name="id" type="integer">
            <column name="id" />
            <!---主键生成方式 -->
            <generator class="identity"></generator>
        </id>
        <!-- property用于非主键字段的映射
             name指定实体类属性名(大小写敏感)
             type指定属性类型(大小写不敏感) -->
        <property name="email" type="string">
            <!-- column指定对应的字段名 -->
            <column name="email" length="50" not-null="true" unique="true" />
        </property>
        <property name="nickname" type="string">
            <column name="nickname" length="50" />
        </property>
        <property name="password" type="string">
            <column name="password" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>

该步骤可以使用注解方式

@Entity
@Table(name = "help_question")
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Proxy(lazy = false)
public class Question {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    
    @Column(name = "question_id")
    private Integer questionId;

    @Column(name = "question_title")
    private String questionTitle;

    @Column(name = "question_content")
    private String questionContent;

    @Column(name = "issue_date")
    private Date issueDate;

}

4. 在配置文件中关联映射文件

 <mapping resource="entity/User.hbm.xml" />

5. 利用Hibernate API操作实体对象

public void testFindById(){
  //加载hibernate配置及hbm.xml映射文件,默认加载src下的文件,如果配置文件关联了映射文件,同时也装载了映射文件
  Configuration conf = new Configuration();
  conf.configure("/hibernate.cfg.xml");
   //获取SessionFactory
   SessionFactory sf = conf.buildSessionFactory();
   //获取Session
   Session session = sf.openSession();
   //按id主键条件查询load(),get()。 get(要查询的类型,主键值);get没记录返回null,load没记录抛异常
   User user = (Cost)session.get(User.class, 3);
   if(user != null){
    System.out.println(user.getId());
   }else{
    System.out.println("没有记录");
  }
  //关闭连接资源
  
session.close(); } public void testSave(){   Configuration conf = new Configuration();   conf.configure("/hibernate.cfg.xml"); SessionFactory sf = conf.buildSessionFactory(); Session session = sf.openSession()
  //开启事务控制,默认情况下,关闭了自动commit功能,如果进行DML操作,必须追加事务控制
Transaction tx = session.beginTransaction();
  
  User user
= new User();
  user.setEmail(
"test@qq.com");
  session.save(user);
//添加一条记录,将user对象信息写入数据表
  tx.commit(); //提交事务
  
  session.close(); //关闭连接
}

public void testDelete(){
  Configuration conf
= new Configuration();
  conf.configure(
"/hibernate.cfg.xml");
  SessionFactory sf
= conf.buildSessionFactory();
  Session session
= sf.openSession();
  Transaction tx
= session.beginTransaction();
  
User user = new User();
  user.setId(
91);
  session.delete(user); //执行删除
  tx.commit();
//关闭
  session.close();
}

public void testUpdate(){
  Configuration conf
= new Configuration();
  conf.configure(
"/hibernate.cfg.xml");
  SessionFactory sf
= conf.buildSessionFactory();
  Session session
= sf.openSession();
  Transaction tx
= session.beginTransaction();
  User user
= (Cost)session.get(User.class, 101);
  user.setEmail(
"test2@qqcom");
  session.update(cost);
//执行更新,将cost数据状态更新到数据表
  tx.commit(); //提交事务

  session.close(); //关闭连接
}

Hibernate映射类型


在hbm.xml中定义属性和字段映射时,通过type属性指定映射类型,其作用是指定属性和字段之间采用哪种类型赋值.可以采用下面两种方法指定:

1. 指定Java类型  java.lang.String  java.lang.Integer

2. Hibernate映射类型(推荐)

   字符串      string

   字符       character

   整数      byte,short,integer,long

   浮点数     float,double

   日期        date,time,timestamp

   boolean类型   yes_no,true_false

 true_false : 完成实体类boolean属性和表字段char之间的转换。true值转换成T;false值转换成F

   yes_no : 完成实体类boolean属性和表字段char之间的转换。true值转换成Y;false值转换成N

   其他   big_decimal,big_integer,clob(大对象类型),blob(小对象类型)

Hibernate主键生成方式


在hbm.xml映射描述中,可以指定主键值采用哪种方法生成和管理。(仅适用于添加操作)

<generator class="生成方法">
     //....
</generator>

class属性用于指定主键生成方法,Hibernate提供了以下几个预定义的方法:

sequence

采用一个序列生成主键值。只适用于Oracle数据库。

<generator class="sequence">
    <param name="sequence">
         //指定序列名称
    </param>
</generator>    

identity

Hibernate会利用数据库自动增长机制生成主键。适用于MySQL、SQLSERVER数据库

<generator class="identity"></generator>

注意:建表时需要为主键字段设置自增长功能

关于解决mysql乱码:

a. create database XXX default character set utf8

b. create table qin_emp(....)engine=innodb default charset=utf8;

c. hibernate.cfg.xml中连接字符串

   <property name="connection.url">jdbc:mysql://localhost:3306/qin?useUnicode=true&amp;characterEncoding=utf8</property>

native

根据hibernate.cfg.xml中的dialect属性指定主键生成方法。如果dialect是OracleDialect会采用sequence方法;如果是MySQLDialect会identity方法。

如果dialect是oracle

<generator class="sequence">
    <param name="sequence">
         //指定序列名称
    </param>
</generator> 

如果dialect是mysql

<generator class="identity"></generator>

increment

首先发送一个select max(ID)语句查询当前表中ID最大值,然后将最大值+1给insert语句指定。适用于各种类型数据库。

<!-- 注意:该方式在并发时,有可能产生重复ID,因此并发几率高时,不要使用。 -->
<generator class="increment">
</generator>

       

assigned

Hibernate会放弃主键值的生成和管理。意味着程序员需要在程序中显式指定ID值。

uuid算法

采用UUID算法生成一个主键值(字符串类型的ID)

<generator class="uuid"></generator>

hilo算法

采用高低位算法生成一个主键值。hilo 和 seqhilo生成器给出了两种hi/lo算法的实现

第一种情况

<id name="id" type="id" column="id">
  <generator class="hilo">
    <param name="table">zhxy_hilo_tbl</param>
    <param name="column">next_value</param>
    <param name="max_lo">0</param>
  </generator>
</id>

第二种情况

<id name="id" type="long" column="cat_id">
    <generator class="seqhilo">
        <param name="sequence">hi_value</param>
        <param name="max_lo">100</param>
    </generator>
</id>    

第二种情况需要sequence的支持,这里只讨论更通用的第一种情况

默认请况下使用的表是hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。算法百度

自定义规则生成主键值

如果需要按自定义规则生成主键值,可以自定义一个主键生成器.方法如下:

1. 编写一个生成器类,实现IdentifierGenerator接口,实现约定好的generator方法,该方法返回值就是主键值.

2. 在hbm.xml中通过<generator class="包名.类名">的方式使用

StudentGeneratorId.java

// 添加时,会自动调用该方法获取一个主键值
public class StudentGeneratorId implements IdentifierGenerator {

    public Serializable generate(SessionImplementor arg0, Object arg1) throws HibernateException {

        // 根据t_student中id值的状态生成下一个主键值

        // 1.查询出t_student表中当前id值
        String hql = "select max(id) from Student";
        Session session = HibernateUtil.getSession();
        Query query = session.createQuery(hql);
        List list = query.list();// 执行查询,获取结果
        String curr_id = (String)list.get(0);// 获取最大的id值
        session.close();// 关闭session
        if(curr_id == null){// 如果没有记录,返回一个0001编号
            return "001";
        }
        // 2.根据当前id+1,获取下一个
        String classNo = curr_id.substring(0, 7);// 班号
        String stuNo = curr_id.substring(7);// 学号
        int nextStuNo = Integer.parseInt(stuNo) + 1;// 学号+1
        // 将nextStuNo变成XXXX格式
        int len = (nextStuNo + "").length();
        String tmpNo = "";// 判断该补几个0
        for(int i = 1; i <= 4 - len; i++){
            tmpNo += "0";
        }
        String no = classNo + tmpNo + nextStuNo;
        System.out.println("no:" + no);
        return no;
    }
}

Student.hbm.xml

<id name="id" type="string">
    <column name="id"></column>
    <generator class="id.StudentGeneratorId"></generator>
</id>

测试

public void testAdd() {
        Student stu = new Student();
        stu.setName("tom");
        stu.setAge(20);
        stu.setSex("M");
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        session.save(stu);// stu处于持久状态,id已分配有值
        System.out.println(stu.getId());
        tx.commit();
        session.close();
}

Hibernate的基本特性


一级缓存

二级缓存

对象持久化

延迟加载

一级缓存(默认启用)


一般将频繁使用的数据放入缓存中,以内存空间换取时间的一种策略。

什么是一级缓存

一级缓存指的是Session级别的缓存,由Session对象负责管理。不同的Session对象都有自己独立一级缓存空间,不能互相访问。

session如何管理一级缓存的:

1. session.get/load方法时,会先去一级缓存查找,没有对象信息才去数据库查找,查找后将返回的对象放入一级缓存。后续再查找该对象会返回缓存中的信息,从而减少了访问数据库的次数。

2. session需要负责实时维护在缓存中的数据,保证缓存中的数据与数据库数据的一致性,一旦用户对缓存中的数据做了修改,当提交时,session负责将数据更新到数据库中。   

一级缓存的好处

利用同一个Session多次访问同一个实体对象时,对数据库只查询一次,后续几次从缓存获取。

一级缓存的管理

当使用session.load,session.get方法会将查询出的对象自动放入一级缓存,要移除一级缓存的对象,可以使用

session.clear()//移除缓存中所有对象
session.evict(obj)//移除指定的obj对象
session.flush()//将缓存中对象的状态与数据库同步
rx.commit()//内部会首先调用flush,之后commit提交
session.close()//关闭连接,释放缓存资源。

批量操作,注意及时清理缓存

循环次数很多,每次id不同时

for(;;){
Cost cost = (Cost)session.get(Cost.class,id);
//使用cost对象--省略
session.evict(cost);//及时清理缓存的对象
}

为了更好的使用一级缓存,在同一个线程处理中不同组件应使用同一个Session对象.可以使用ThreadLocal技术session对象与处理线程绑定

public class HibernateUtil {
    private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
    private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
    private static SessionFactory sessionFactory;
    // SessionFactory一般只有一个,一个SessionFactory可以创建多个Session
    static{

        try{
            // 加载hibernate.cfg.xml文件,创建SessionFactory
            Configuration conf = new Configuration();
            conf.configure(CONFIG_FILE_LOCATION);
            sessionFactory = conf.buildSessionFactory();
        }
        catch(Exception e){
            System.err.println("%%%% Error Creating SessionFactory %%%%");
        }

    }

    // 返回ThreadLocal中的session实例
    public static Session getSession() {
        Session session = threadLocal.get();
        // 如果session为空或者session被关闭
        if(session == null || !session.isOpen()){
            if(sessionFactory == null){
                return null;
            }
            session = sessionFactory.openSession();
            threadLocal.set(session);
        }
        return session;
    }

    // 关闭session
    public static void closeSession() {
        Session session = threadLocal.get();
        if(session != null){
            session.close();
        }
        threadLocal.set(null);
    }
}

对象持久化


什么是持久化

Hibernate的持久化指的是将程序中Java对象的数据以数据库存储形式保存下来。Hibernate是一个持久层框架。持久层里面都是由持久对象构成,这些对象的持久化操作由Hibernate实现。

对象持久性:当一个对象的数据发生改变,会与数据库记录进行同步修改。垃圾回收器不能回收该对象。

Hibernate对象状态

在使用Hibernate中,java对象有以下3种状态

1. 临时状态---临时对象

使用时,刚new出来的对象。使用new 操作运算符初始化的对象的状态时瞬间的,如果没有任何跟数据表相关联的行为,只要应用程序不再引用这些对象,它们的状态将会消失,并由垃圾回收机制回收。这种状态被称为暂时态。

2. 持久状态--持久对象

使用了session对象的save,update,load等方法后,该对象就处于持久状态.

a. 持久对象存在于Session缓存中,由Session负责管理

b. 持久对象不能被垃圾回收器回收,它的数据状态改变可以与数据库同步。由session负责同步操作。

c. 持久对象数据改变后,在事务commit之后执行update更新。

session.flush();//将缓存中对象与数据库同步
tx.commit();//等价于session.flush+事务提交.

3. 托管或游离状态

当关闭session,或使用session.evict(),clear()方法将对象移除后,该对象脱离了Session管理。表示这个对象不能再与数据库保持同步,它们不再受Hibernate管理。

测试持久性

  public void test1() {
        Foo foo = new Foo();
        foo.setValue("foo100");// 现在的foo是暂时态
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        Session.save(foo);// 现在的foo是持久态
        // 测试:当foo为持久态时,修改value为foo200
        foo.setValue("foo200");
        foo.setValue("foo300");
        // 当执行tx.commit()操作时,事务提交,此时会自动调用session.flush(),再执行commit()操作。而只有当执行了session.flush()操作时,session才会把持久对象的改变更新到数据库。
        tx.commit();
        Session.close();
    }// 只执行一次update()语句

    public void test2() {
        Foo foo = new Foo();
        foo.setValue("foo100");// 现在的foo是暂时态
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        Session.save(foo);// 现在的foo是持久态
        // 测试:当foo为持久态时,修改value为foo200
        foo.setValue("foo200");
        Session.flush();
        foo.setValue("foo300");
        tx.commit();
        Session.close();
    }// 执行两次update()操作

批量操作

// 向COST插入100000条记录
Transaction tx = session.beginTransaction();
 // 插入操作
for(int i = 1; i <= 100000; i++){
    Cost cost = new Cost();
    // 设置cost属性值
    session.save(cost);
     // 分批执行
     if(i % 100 == 0){
         // 将缓存对象与数据库同步操作
         session.flush();
         session.clear();// 清除缓存的对象
     }
}
tx.commit();
session.close();

延迟加载


1. 什么是延迟加载

Hibernate在使用时,有些API操作是具有延迟加载机制的。延迟加载机制的特点:当通过Hibernate的API获取一个对象结果后,该对象并没有数据库数据。而是在调用实体对象的getXXX方法时才会发送SQL语句加载数据库数据。

2. 哪些操作会采用延迟加载机制

a. 查询:load()延迟加载查询;get()立即加载

b. 执行HQL:iterator()延迟加载;list()非延迟加载

c. 关联操作:获取关联对象属性值时,采用的延迟加载机制

注意:这些方法返回的对象,只有id属性有值,其他数据库在使用时候(调用getXXX()方法)才去获取。

Query query = session.createQuery("from TestQin");
Iterator it = query.iterate();
while(it.hasNext()){
  TestQin qin = (TestQin)it.next();
  System.out.println(qin.getName());
}// 先查询所有的id值,再根据id值查询对应的数据

while(it.hasNext()){
  TestQin qin = (TestQin)it.next();
  System.out.println(qin.getId());
}// 不发送sql语句

TestQin qin = (TestQin)session.load(TestQin.class, 1);
System.out.println(qin.getId());// 不发送sql语句
TestQin qin = (TestQin)session.load(TestQin.class, 1);
System.out.println(qin.getName());// 发送sql语句

关联映射


关联映射主要是在对象之间建立关系。开发者可以通过关系进行信息查询、添加、删除和更新操作。如果不使用Hibernate关联关系映射,我们也可以取到用户对应的服务。

Account   account = (Account)session.get(Account.class,1);//取到用户的信息
String hql = “from Service s where s.accountId=1 ”;
Query query=session.createQuery(hql);//取到用户对应的服务
List<Service> list=query.list();

而Hibernate提供的关联映射,更方便一些。

一对多关系 one-to-many

多对一关系 many-to-one

关联操作

多对多关系 many-to-many

继承关系

一对多关系 one-to-many


典型的一对多关系:班级和学生,一个班级可以有很多学生,一个学生就只能属于一个班级。

需求:在操作班级时同时要操作班级的学生信息,这样可以写成noe-to-many映射。Account和Service也是一对多关系,一个客户可以开通多个业务,一个业务只能属于一个客户。

实现:在查询account时,可以把account对应的service信息取出来。

1. 在one方也就是account实体类添加Set集合属性,以及对应的get/set方法

public class Account implements java.io.Serializable {
         // Fields
         private Integer id;
         private Integer recommenderId;
         private String loginName;
         private String loginPasswd;
         //...
         //追加关系属性,用于存储相关的Service对象信息
         private Set<Service> services =  new HashSet<Service>();
  }

2. 在One方Account.hbm.xml映射文件中,加入Set节点的映射。

  <set  name=”属性名” >
        <!--关联条件,column写外键字段,会默认的与account表中的主键相关联-->
        <key column=”指定关联条件的外键字段”></key>
        <one-to-many  class=”指定要关联和加载的一方many方”/>
    </set>
    <!--如果是list集合用<list name=””></list>  set 集合用<set  name=’”>-->

    <set  name=’”services”>
        <key column=”ACCOUNT_ID” ></key>
        <one-to-many  class=”entity.Service”/>
    </set>
    <!--注意:关联属性数据加载默认采用延迟加载机制,使用中不要过早关闭session。 -->

3. 测试

Session session = HibernateUtil.getSession();
Account account = (Account)session.load(Account.class, 1005);
// 延迟加载,第一次发送sql语句查询 from Account where id = 1005
System.out.println(account.getRealName());
// 延迟加载,第二次发送sql语句查询 from Service where account_id = 1005
Set<Service> services = account.getServices();
for(Service s : services){
    System.out.println(s.getOsUsername());
}
session.close();

多对一关系 many-to-one


多个学生对应一个班级,这是多对一关系。多个Service服务对应一个Account账号,所以是多对一关系。Service是N方

需求:在得到Service信息时,同时得到它所对应的Account信息。

1. 在N方Service实体类添加Account属性,以及对应的get/set方法

public class Service implements java.io.Serializable {
    private long id;
    // private long accountId;//注意:Account中已经包含了accountId了,所以原来的accountId属性要删除掉,否则会报错
    private String unixHost;
    private String osUsername;
    private long costId;
    private Account account; // 追加属性,用于存储关联的Account信息

}

注意事项:Service实体原来的accountId属性删除了,相应的get/set方法也删除,Service的映射文件对应的描述也要删掉,否则会报错,Repeated column in mapping for entity

2. 在N方Service.hbm.xml映射文件中描述account属性

<many-to-one name="关联属性名"  class="要关联和加载的一方Account"  column="指定关联条件的外键字段(不写主键)"/>
<many-to-one name="account" class="entity.Account" column="ACCOUNT_ID">

3. 测试

Session session = HibernateUtil.getSession();
Service s = (Service)session.load(Service.class, 2001);// hql= "from Service where id=2001";
System.out.println(s.getOsUsername());// 延迟加载,第一次发送sql语句查询
Account account = s.getAccount();
System.out.println(account.getId());// 获得id值时没有去数据库查询,在第一次发送sql语句时查询出account_id值放入account对象的id属性中hql= from Account where id=...
System.out.println(account.getRealName());// 延迟加载,在调用get方法后第二次发送sql语句查询

关联操作


关联抓取 join fetch

在建立关联映射后,默认情况下在调用关联属性的getter方法时,会再次发送一个sql语句加载关系表数据。如果需要将关联数据与主对象一起加载(两个SQL查询合成一个SQL查询),可以采用join fetch。

方式一:使用lazy=”false” fetch=”join”

在hbm.xml关联属性映射描述中,使用lazy=”false” fetch=”join”。该方法不推荐采用,因为会影响所有查询操作,建议采用HQL的join fetch写法。不推荐使用,因为影响的映射范围太广,如果不需要用到关联对象,也加载了浪费内存。

lazy属性

可以控制是否延迟加载。lazy="true"采取延迟加载,lazy="false"采取立刻加载。默认为true

fetch属性

可以控制关联属性抓取的策略。fetch="select"单独再发送一个select语句加载关联属性数据(默认值),fetch="join"采取表连接方式将关联属性数据同主对象一同抓取.(一个SQL完成)。

<set name="services" lazy="false" fetch="join">
  <key column="ACCOUNT_ID"></key>
  <one-to-many  class="entity.Service"/>
</set>
<many-to-one name="account" class="entity.Account" column="ACCOUNT_ID" lazy="false" fetch="join" />
Session session = HibernateUtil.getSession();
Account account = (Account)session.load(Account.class, 1011);// lazy=false时,取出account后,在属性services中已经填充了所有的服务项
System.out.println(account.getRealName());
session.close();// 如果Account.hbm.xml中关联属性设置了lazy=false时,在这里关闭能正常运行。
System.out.println(account.getServices().size());// 如果lazy=true,这里会报错

方式二:HQL,关联抓取join fetch(推荐使用)

HQL语法格式:"from 类型 别名  join fetch 别名.关联属性"。如"from Account a join fetch a.services"

默认情况下,关联属性在抓取时,采用单独在发送一条SQL语句实现。采用join fetch可以实现用一条SQL语句抓取主对象和关联属性的数据信息。

提示:如果需要使用主对象和关系属性数据,建议采用join fetch方式,可以减少与数据库的交互次数。

1. 一对多

Session session = HibernateUtil.getSession();
String hql = "from Account a join fetch a.services where a.id=?";// hql语句和在hbm.xml中设置fetch=”join”效果一样
Query query = session.createQuery(hql);
query.setInteger(0, 1011);
Account account = (Account)query.uniqueResult();// 发送sql语句进行查询
System.out.println(account.getId());
Set<Service> services = account.getServices();
for(Service s : services){
  System.out.println(s.getOsUsername());
}

2. 多对一

Session session = HibernateUtil.getSession();
String hql = "from Service s join fetch s.account where s.id=?";
Query query = session.createQuery(hql);
query.setInteger(0, 2001);
Service s = (Service)query.uniqueResult();
System.out.println(s.getOsUsername());
System.out.println(s.getAccount().getRealName());

级联操作


在建立关联映射之后,可以通过关系实现级联的添加、删除、更新操作。级联操作默认是关闭的,如果需要使用,可以在关联属性映射部分添加cascade属性,

属性值有:

1. none默认值,不支持级联

2. save-update: 级联保存(load以后如果子对象发生了更新,也会级联更新).但它不会级联删除

3. delete: 级联删除, 但不具备级联保存和更新

4. all-delete-orphan: 在解除父子关系时,自动删除不属于父对象的子对象,也支持级联删除和级联保存更新.

5. all: 级联删除, 级联更新,但解除父子关系时不会自动删除子对象.

6. delete-orphan:删除所有和当前对象解除关联关系的对象

<set name="services" cascade="all">
    <key column="ACCOUNT_ID"></key>
    <one-to-many  class="entity.Service"/>
</set>  

测试级联添加

public class Test {
    /**
     *整个测试发送了5个sql语句
     *1.insert into Account...
     *2.insert into Service ..
     *3.insert into Service...
     *service中有个account,最后更新service_qin表中的account_id值, 值为Service的属性account的id值
     *4.update Service ...
     *5. update Service...
     */
    public void test1() {
        Session session = HibernateUtil.getSession();
        Transaction tc = session.beginTransaction();
        Account account = new Account();
        account.setLoginName("test1");
        account.setLoginPasswd("test1");
        account.setTelephone("123456789");
        account.setIdcardNo("123456789");
        account.setRealName("test1");
        Service s1 = new Service();
        s1.setOsUsername("test2");
        s1.setUnixHost("1.1.0.127");
        s1.setLoginPasswd("test2");
        s1.setCostId(3);
        s1.setAccount(account);

        Service s2 = new Service();
        s2.setOsUsername("test3");
        s2.setUnixHost("1.1.0.128");
        s2.setLoginPasswd("test3");
        s2.setCostId(3);
        s2.setAccount(account);

        account.getServices().add(s1);
        account.getServices().add(s2);
        session.save(account);
        tc.commit();
        session.close();
    }
}

测试级联删除

当对主对象进行删除时,关联属性的记录也进行相应删除。

public class Test {
    public void test2() {

        Session session = HibernateUtil.getSession();
        Transaction tc = session.beginTransaction();
        Account account = (Account)session.load(Account.class, 115);// account需要采用查询方式获取,不能采用new方式。建议在one-to-many部分添加inverse=true设置,这样可以避免update account_id=null的操作,直接进行delete删除。
        session.delete(account);
        tc.commit();
        session.close();

        /**
         * 整个测试发送5个sql语句
         * 1.select from account_qin where id=115
         * 2.select from service_qin where account_id=115
         * 3.delete from service_qin where id=?
         * 4.delete from service_qin where id=?
         * 5.delete from service_qin where id=?
         * 可以看出hibernate级联删除的缺点:delete都是按照id(主键)一条一条删除的,不是按照关系字段删的,当数据量小时可以用hibernate的级联删除,简单方便些。但是当数据量大时,Hibernate的级联删除效率低,则不建议使用Hibernate的级联删除。
         * 建议采用HQL语句的方式
         * delete from Account where id=?
         * delete from Service where account.id=?
         * 注意事项: 级联删除不写inverse=”true”,且数据库中service_qin表中的account_id为not null约束,那么程序最后会执行update,会设置account_id=null,将与数据库冲突!报错,所以应当加上inverse=”true”
         */

    }
}

inverse属性


1. 默认情况下,在采用了级联操作时,Hibernate在执行insert update delete基本操作后,还要执行update关系字段的操作(即关系维护工作account表中的id和service表中的account_id字段)。

2. 默认是关联对象双方都要负责关系维护,在级联添加案例中,控制台最后会有两个update语句,因为当前添加了一个Account和两个Service,所以One方要维护两个Service。即两个update语句。如果数据量很大,则要维护N个Service。则有N个update语句,此时会影响性能。

3. 为了使update语句不出现。可以在Account.hbm.xml中的关联属性添加inverse=”true”属性,即当前一方放弃关系维护,这项工作交给对方负责。

<set name="services" cascade="all" inverse="true">
    <key column="ACCOUNT_ID"></key>
    <one-to-many  class="entity.Service"/>
</set>

4. 注意:当遇到一对多、多对一关系映射时,把inverse=”true”属性加到one-to-many方One方放弃,Many方维护,能起到一定的优化作用。

原文地址:https://www.cnblogs.com/qin-derella/p/6748279.html