• 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方维护,能起到一定的优化作用。

  • 相关阅读:
    IoC和AoP
    学习树
    Avalon Framework概念
    java利用WatchService实时监控某个目录下的文件变化并按行解析
    [DBT-08001] 无法检查可用内存。
    C#之http协议与soap协议之间的区别
    C#之ActionResult 详解
    C#实现连接池
    C#MVC之传入字典的模型项为 null,但此字典需要类型“System.Decimal”的非 null 模型项。
    Func的介绍
  • 原文地址:https://www.cnblogs.com/qin-derella/p/6748279.html
Copyright © 2020-2023  润新知