木心

毕竟几人真得鹿,不知终日梦为鱼

导航

hibernate(二)对象的三种状态、一级缓存、多对一、inverse、cascade

录:

1、对象的三种状态
2、session.save()方法
3、与session建立关联的对象的id,不允许修改
4、Hibernate一级缓存(更深层次理解hibernate中对象的操作)
5、Hibernate一级缓存细节问题   
6、session.save()方法和persist()方法的区别
7、session的其他API
8、映射文件表达多对一(或一对多)关系
9、多对一关系-多表操作
10、多对一关系的维护--inverse 属性
11、cascade级联操作

 

1、对象的三种状态    <--返回目录

  对象的三种状态
    1)瞬时态(或临时态): 没有与Hibernate产生关联,与数据库中的记录没有产生关联(有关联就是与数据库中的 id 有关联)
    2)持久态: 与Hibernate有关联,与数据库有关联(对象有 id)
    3)游离态(或托管态): 与Hibernate没有关联,与数据库有关联(对象有 id)

  如何判断:

    1)对象有 id <==> 与数据库有关联

    2)使用 session 操作过 对象 <==> 与 Hibernate 有关联

 

 

  HibernateUtils

package com.oy.helloworld;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
    // 会话工厂,整个程序只有一份。
    private static SessionFactory sf;

    static {
        // 1 加载配置
        Configuration config = new Configuration().configure();

        // 2 获得工厂
        sf = config.buildSessionFactory();
        // 3 关闭虚拟机时,释放SessionFactory
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                System.out.println("虚拟机关闭!释放资源");
                sf.close();
            }
        }));
    }

    // 获得一个新的session
    public static Session openSession() {
        return sf.openSession();
    }

    // 获得当前线程中绑定session
    public static Session getCurrentSession() {
        return sf.getCurrentSession();
    }
}
View Code

  测试代码

public class TestHelloWorld {
    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();
        // ===========================================
        User u = new User();  // 临时态
        u.setUsername("tom"); // 临时态
        u.setPassword("333"); // 临时态
        // save 方法会使用主键生成策略,为 User 指定 id
        session.save(u); // 持久态: 与Hibernate有关联,与数据库有关联(对象有 id)
        // ===========================================
        session.getTransaction().commit(); // 持久态
        session.close(); // 游离态
    }
}
@Test
public void fun2() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();
    // ===========================================
    User u = (User) session.get(User.class, 1); // 持久状态
    u.setUsername("jerry");
    session.update(u); // 多余代码,Hibernate会自动将持久化状态对象的变化同步到数据库中,无需该代码
    // ===========================================
    session.getTransaction().commit();// 持久状态,打印update语句
    session.close(); // 游离状态
}

 

2、session.save()方法    <--返回目录

  session.save()方法作用:获得 id
    1)当主键生成策略为 identity 主键自增长时,该方法会通过 insert 语句来获得 id
    2)当主键生成策略为 increment,该方法会使用语句 select max(id) from t_user; (使用断点查看,是否生成该 sql 语句),然后事务提交时,才生成 insert 语句
    3)当主键生成策略为 assigned 时,在 session.save() 之前没有手动设置id,报错。


  关于save()方法的测试

package com.oy.test;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;

import com.oy.domain.User;

public class Demo1 {
    @Test
    /**
     * 主键生成策略increment:数据库自己生成。先从数据库总查询最大的ID值,将ID值加1;
     * 当主键生成策略为increment,save()方法会使用语句select max(uid) from t_user来给user设置uid;
     * 由于这里没有使用事务,最后没有使用insert语句,数据库没有添加记录;
     */
    public void fun1() {
        // 1.读取配置文件
        Configuration conf = new Configuration().configure();
        // 2.根据配置创建Factory
        SessionFactory sessionFactory = conf.buildSessionFactory();
        // 3.获得操作数据库的session对象
        Session session = sessionFactory.openSession();
        // 4.操作数据库

        //
        User user = new User();
        user.setUname("小张1");
        user.setPassword("123");
        System.out.println("save前:" + user);//save前:User [uid=0, uname=小张1, password=123]
        session.save(user);//当主键生成策略为increment,该方法会使用语句select max(uid) from t_user
                            //由于这里没有insert语句,后面也没有

        System.out.println("save后:" + user);//save后:User [uid=7, uname=小张1, password=123]

        // 5.关闭资源
        session.close();
        System.out.println("session关闭后:" + user);//session关闭后:User [uid=7, uname=小张1, password=123]
        sessionFactory.close();
    }
    
    @Test
    /**
     * 主键生成策略increment:数据库自己生成。先从数据库总查询最大的ID值,将ID值加1;
     * 当主键生成策略为increment,save()方法会使用语句select max(uid) from t_user来给user设置uid;
     * 由于这里使用事务,事务提交时使用insert语句添加记录。
     */
    public void fun2() {
        // 1.读取配置文件
        Configuration conf = new Configuration().configure();
        // 2.根据配置创建Factory
        SessionFactory sessionFactory = conf.buildSessionFactory();
        // 3.获得操作数据库的session对象
        Session session = sessionFactory.openSession();
        // 4.开启事务
        Transaction ts = session.beginTransaction();
        
        //
        User user = new User();
        user.setUname("小张1");
        user.setPassword("123");
        System.out.println("save前:" + user);//save前:User [uid=0, uname=小张1, password=123]
        session.save(user);//当主键生成策略为increment,该方法会使用语句select max(uid) from t_user
                            //这里没有insert语句

        System.out.println("save后:" + user);//save后:User [uid=8, uname=小张1, password=123]

        //5.提交事务
        ts.commit();  //事务提交,这里执行insert into语句
        // 6.关闭资源
        session.close();
        System.out.println("session关闭后:" + user);//session关闭后:User [uid=8, uname=小张1, password=123]
        sessionFactory.close();
    }
    
    @Test
    /**
     * 主键生成策略是identity
     */
    public void fun3() {
        // 1.读取配置文件
        Configuration conf = new Configuration().configure();
        // 2.根据配置创建Factory
        SessionFactory sessionFactory = conf.buildSessionFactory();
        // 3.获得操作数据库的session对象
        Session session = sessionFactory.openSession();
        // 4.操作数据库

        //
        User user = new User();
        user.setUname("小张2");
        user.setPassword("123");
        System.out.println("save前:" + user);//save前:User [uid=0, uname=小张2, password=123]
        session.save(user);//由于主键生成策略是identity,使用insert语句获取uid;
                            //使用insert语句获取uid,然后给user设置uid;
                            //由于使用了insert语句,save()方法后数据已经持久化到数据库中了;
        System.out.println("save后:" + user);//save后:User [uid=7, uname=小张2, password=123]

        // 5.关闭资源
        session.close();
        System.out.println("session关闭后:" + user);//session关闭后:User [uid=7, uname=小张2, password=123]
        sessionFactory.close();
    }
}

 

3、与session建立关联的对象的id,不允许修改    <--返回目录

Session session = HibernateUtils.openSession();
session.beginTransaction();
User u = (User)session.get(User.class, 1);
u.setId(3); //报错,因为与session建立关联的对象的id,不允许修改
session.commint();
session.close();

 

4、Hibernate一级缓存(更深层次理解hibernate中对象的操作)    <--返回目录

  缓存:Hibernate中也存在缓存. Hibernate中存在的缓存也是用来提高效率.
  Hibernate中存在两种缓存:
          - 线程级别的缓存. Session缓存   (现在要学习的)
          - 进程级别的缓存. Hibernate 二级缓存.
  session缓存: 就是session对象中存在的缓存.缓存中存在的是(持久化)对象.
    
  证明session缓存的存在

@Test
public void fun2() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();
    // ===========================================
    User u1 = (User) session.get(User.class, 1);// 发送select语句,从数据库取出记录封装成User对象
                                                // 持久化状态对象=>存到缓存中
    User u2 = (User) session.get(User.class, 1);// 再次查询时,会从缓存中查找,不会发送select语句
    // ===========================================
    session.getTransaction().commit();// 持久状态,打印update语句
    session.close(); // 游离状态
}

       
  问题:一级缓存以什么形式存在的?
    Map形式存在,Map<串行化Serializable, Object>

  缓存中的快照: 在从数据库取得数据时,会将数据一式两份,一份作为缓存中的对象,一份作为快照。 在session提交时作为对比。

 

  一级缓存执行原理

 

5、Hibernate一级缓存细节问题       <--返回目录

  持久化状态本质:就是存在缓存中的对象,就是持久化状态
    
  保存对象时可以使用save()方法和persist()方法
          - 区别:有区别
          - save()方法来自hibernate
          - persist()方法来自JPA接口,persist就是持久化的意思
    
  HQL语句批量查询时,查询结果是否会进入缓存?
          - 会。

List<User> userList = session.createQuery("from User").list();//查询所有,并把查询出的结果封装成对象放入缓存
User u = (User)session.get(User.class, 1);//若缓存中有id=1的对象,直接从缓存中取User对象;缓存没有找到,使用select where id=1查询数据库

 

  HQL查询是否会使用一级缓存?
          - 批量查询时不会。

  案例1:

// 批量查询,不使用一级缓存
List<User> userList1 = session.createQuery("from User").list();//打印select语句
List<User> userList2 = session.createQuery("from User").list();//也会打印select语句

  案例2:

// 批量查询,不使用一级缓存
Query query1 = session.createQuery("from com.oy.domain.User where username='张三'");
User u1 = (User)query1.uniqueResult();//执行hql语句,返回一个User对象
Query query2 = session.createQuery("from com.oy.domain.User where username='张三'");
User u2 = (User)query2.uniqueResult();//执行hql语句,返回一个User对象

  案例3:

// 非批量查询,使用一级缓存
Query query3 = session.createQuery("from com.oy.domain.User where id=2"); User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象 Query query4 = session.createQuery("from com.oy.domain.User where id=2"); User u4 = (User)query4.uniqueResult();//不执行hql语句

  案例4:

List<User> userList1 = session.createQuery("from User").list();//打印select语句
Query query3 = session.createQuery("from com.oy.domain.User where uid=2");
User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象
Query query4 = session.createQuery("from com.oy.domain.User where uid='2");
User u4 = (User)query4.uniqueResult();//不执行hql语句

  案例5:

Query query3 = session.createQuery("from com.oy.domain.User where uid=2");
User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象
User u = (User)session.get(User.class, 2);//不打印sql语句,直接从缓存中取

 

  原生SQL语句查询时,查询结果是否会进入缓存?
        - 如果有这句query.addEntity(User.class),会。否则不会。
        
  criteria查询,会将查询结果放入一级缓存,但是查询不会使用一级查询。与HQL一样的。

 

6、session.save()方法和persist()方法的区别    <--返回目录

  - 主键生成策略为native,数据库主键设置自增
        - persist()方法体现的是持久化;persist()的理念是将对象完整的持久化,也包括对象的id;
          在保存之前设置了id,那么将会使用设置的id进行insert,但是主键策略是由数据库来维护,所以产出矛盾,所以抛出异常。
            User user = new User();
            user.setId(99);
            session.persist(user);  //这里报错
        - session.save()方法:如果保存之前设置了id,那么该id也被认为是无效的id。
            User user = new User();
            user.setId(99);
            session.savet(user);  //这里不报错

 

7、session的其他API    <--返回目录

    * session.evict(user): 将user对象从session缓存中移除
    * session.clear(): 将session缓存中所有对象移除
    * session.refresh(user): 强制刷新缓存中的对象,发送seclet语句,将数据库中数据更新到session缓存。
      该方法可以用来解决缓存与数据库数据不同步的问题,但不是一种好的解决方案。
    * session.flush():对比快照,立刻将session缓存中对象提交(执行update语句)
    
    * saveOrUpdate方法:
        - 数据库为代理主键,设置主键自增,主键生成策略为native:主键为空,save;主键非空,update
        - 数据库为自然主键,主键生成策略为assigned: 必须手动设置id,然后根据id查询,查不到save,查到update

 

8、映射文件表达多对一(或一对多)关系    <--返回目录

  订单表 t_order(多)                客户表 t_customer(一)
       oid    oname    cid(外键)             cid     cname    

  mysql 建表 sql

create database test default character set utf8;
use test;

create table t_order(
    oid    int primary key auto_increment,
    oname varchar(40),
    cid int
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

create table t_customer(
    cid    int primary key auto_increment,
    cname varchar(40)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
View Code

   
  实体类Order.java

public class Order{
    private Integer oid;//订单id
    private String oname;//订单名称
    private Customer customer;//外键
}

   
  实体类Customer.java

public class Customer{
    private Integer cid;//客户id
    private String cname;//客户名称
    private Set<Order> orders = new HashSet<Order>();
}

 

  先配置Order==>t_order的映射  Order.hbm.xml文件

<class name="com.oy.domain.Order" table="t_order">
    <id name="oid" column="oid">
        <generator class="native"></generator>
    </id>
    <property name="oname" column="oname"></property>
    <many-to-one name="customer" column="cid" class="com.oy.domain.Customer"></many-to-one>
</class>

   
  然后配置Customer==>t_customer的映射  Customer.hbm.xml文件

<class name="com.oy.domain.Order" table="t_order">
    <id name="cid" column="cid">
        <generator class="native"></generator>
    </id>
    <property name="cname" column="cname"></property>
    
    <set name="orders">
        <key column="cid"></key>
        <one-to-many class="com.oy.domain.Order"></one-to-many>
    </set> 
</class>

   
  在hibernate.cfg.xml配置文件中引入映射文件

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        
        <property name="show_sql">true</property>  <!--操作数据库时,向控制台打印sql语句-->
        <property name="format_sql">true</property>  <!--打印sql语句前,优化sql语句-->
        <property name="hbm2ddl.auto">update</property>  <!--是否自动生成表结构-->
        <property name="hibernate.connection.autocommit">true</property>  <!--事务自动提交-->
        
        <!-- 添加映射文件 -->
        <mapping resource="com/oy/domain/Customer.hbm.xml"/>
        <mapping resource="com/oy/domain/Order.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
View Code

 

9、多对一关系-多表操作    <--返回目录

demo 源码见:链接:https://pan.baidu.com/s/12nwIyodOhVCEemaAyPVPtQ 提取码:tp9w

 

  增(下面代码是按照如下顺序:先持久化无外键所在对象,后持久化有外键的对象)

Customer customer = new Customer();
customer.setCname("张三1");
session.save(customer);//没这句报错
                    //org.hibernate.TransientObjectException: object references an unsaved transient 
                    //instance - save the transient instance before flushing: com.oy.domain.Customer
Order o1= new Order();
o1.setOname("牙膏1");
o1.setCustomer(customer);
session.save(o1);

  增:session.save(customer); 移到后面也是可以的

Customer customer = new Customer();
customer.setCname("张三1");
//session.save(customer);

Order o1= new Order();
o1.setOname("牙膏1");
o1.setCustomer(customer);
session.save(customer);//移到后面也是可以的
session.save(o1);
//session.save(customer);//移到这是也是可以的

   控制台打印结果:

Hibernate: 
    insert 
    into
        t_customer
        (cname) 
    values
        (?)
Hibernate: 
    insert 
    into
        t_order
        (oname, cid) 
    values
        (?, ?)
虚拟机关闭!释放资源

 

  给已有的 Customer 新增 Order

@Test
public void fun2() {
    Session session = HibernateUtils.openSession();
    Transaction ts = session.beginTransaction();
    // ==============================================
    Customer customer = (Customer) session.get(Customer.class, 1);

    Order o1 = new Order();
    o1.setOname("手机");
    Order o2 = new Order();
    o2.setOname("电脑");
    
    customer.getOrders().add(o1);
    customer.getOrders().add(o2);
    
    session.save(o1);
    session.save(o2);
    session.save(customer);  // 如果 Customer.hbm.xml 配置了 inverse=true, 这句可以注释掉,并且不会打印 Update 语句
    // ==============================================
    ts.commit(); // 事务提交,这里使用insert into语句
    session.close();
}

  打印结果:

Hibernate: 
    select
        customer0_.cid as cid0_0_,
        customer0_.cname as cname0_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.cid=?
Hibernate: 
    select
        orders0_.cid as cid0_1_,
        orders0_.oid as oid1_,
        orders0_.oid as oid1_0_,
        orders0_.oname as oname1_0_,
        orders0_.cid as cid1_0_ 
    from
        t_order orders0_ 
    where
        orders0_.cid=?
Hibernate: 
    insert 
    into
        t_order
        (oname, cid) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        t_order
        (oname, cid) 
    values
        (?, ?)
Hibernate: 
    update
        t_order 
    set
        cid=? 
    where
        oid=?
Hibernate: 
    update
        t_order 
    set
        cid=? 
    where
        oid=?
虚拟机关闭!释放资源

  

10、多对一关系的维护--inverse 属性    <--返回目录

  外键维护的放弃,只能有非外键所在的的对象来放弃,即t_customer放弃维护关系


  在Customer.hbm.xml表中配置:t_customer放弃维护关系
    <set name="orders" inverse="true">
    inverse: 是否将关系的维护反转给对方,默认false;true表示放弃维护关系
    
  当inverse="false"时,删除t_customer表中记录,成功,但是会把引用该客户的外键cid设置为null;
  当inverse="true"时,删除t_customer表中记录,报错。这时候怎么办?

List<Order> orders = customer.getOrders();
for(Order o: orders) {
    o.setCustomer(null); // 设置订单不属于任何Customer
}

 

11、cascade级联操作    <--返回目录

  cascade: 级联操作

<!-- 表达一对多关系中的集合
    name:集合的属性名称
    inverse: 是否将关系的维护反转给对方. 默认值: false
           true: 在Customer 中 放弃维护外键关系
           
    cascade: 级联操作
        save-update:级联保存,级联修改. 保存A时,同时保存B. 
        delete:删除A,同时删除B,AB都不存在
        delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。
        如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">
        
        all: save-update 和 delete 整合
        all-delete-orphan: 三个整合
        
 -->
<set name="orders" inverse="false" cascade="all-delete-orphan"  >
    <!--
        key 用来描述外键
        column : 外键的值
      -->
    <key column="cid" ></key>
    <!-- one-to-many 表达, Customer 与orders 的关系是一对多
        class: 表达关联的另一方的完整类名
     -->
    <one-to-many class="Order" />
</set>

  

  级联删除要注意:不要在两表之间都使用级联删除,否则会全部删除数据!!!

---

posted on 2020-06-13 19:30  wenbin_ouyang  阅读(246)  评论(0编辑  收藏  举报