hibernate_get和load方法的区别、持久化类、一级缓存和二级缓存、事务控制

1.get和load方法的区别

1.1 获取Session的两种方式

Session是Hibernate的核心,我们在创建了SessionFactory对象后,有两种方式可以获取Session实例

  • Session session = sessionFactory.openSession();
    采用openSession方法获取Session实例时,SessionFactory直接创建一个新的Session实例,并且在使用完成后需要调用close方法进行手动关闭。
  • Session session = sessionFactory.getCurrentSession();
    而getCurrentSession方法创建的Session实例会被绑定到当前线程中,它在提交或回滚操作时会自动关闭。

1.2 Session的事务管理—Transaction对象

我们通过Session开启事务,获取Transaction对象

//开启事务
Transaction transaction = session.beginTransaction();

// 提交事务
transaction.commit();

// 回滚事务
transaction.rollback();

Session执行完数据库操作后,要使用Transaction接口的commit()方法进行事务提交,才能真正的将数据操作同步到数据库中。发生异常时,需要使用rollback()方法进行事务回滚,以避免数据发生错误。

由于Session对象属于线程不安全,当一个线程有多个Session时,无法保证事务。所以一个线程要保障只有一个Session对象。

为了保证一个线程中只有一个Session,所以在实际开发中,我们将Session绑定到当前线程中

1.3 get方法和load方法

Hibernate中get()和load()的区别
在hibernate.cfg.xml中开启显示SQL语句

<property name="hibernate.show_sql">true</property>

(1)实体表和映射文件
Customer.java

public class Customer {
    private Long custId;// 客户编号
    private String custName;// 客户名称
    private String custSource;// 客户信息来源
    private String custIndustry;// 客户所属行业
    private String custLevel;// 客户级别
    private String custAddress;// 客户联系地址
    private String custPhone;// 客户联系方式

    //一对多关系映射:一个客户可以对应多个联系人
    private Set<LinkMan> linkmans = new HashSet<LinkMan>();
   	......
}

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="cn.itcast.domain">
    <class name="Customer" table="cst_customer">
        <id name="custId" column="cust_id">
            <generator class="native"></generator>
        </id>
        
        <property name="custName" column="cust_name"></property>
        <property name="custSource" column="cust_source"></property>
        <property name="custIndustry" column="cust_industry"></property>
        <property name="custLevel" column="cust_level"></property>
        <property name="custAddress" column="cust_address"></property>
        <property name="custPhone" column="cust_phone"></property>
        
        <set name="linkmans" table="cst_linkman" cascade="save-update,delete" inverse="true">
            <key column="lkm_cust_id"></key>
            <one-to-many class="cn.itcast.domain.LinkMan"></one-to-many>
        </set>
    </class>
</hibernate-mapping>

LinkMan.java

public class LinkMan implements Serializable {
    private Long lkmId;//联系人编号(主键)
    private String lkmName;//联系人姓名
    private String lkmGender;//联系人性别
    private String lkmPhone;//联系人办公电话
    private String lkmMobile;//联系人手机
    private String lkmEmail;//联系人邮箱
    private String lkmPosition;//联系人职位
    private String lkmMemo;//联系人备注

    private Customer customer;//对应联系人表中的外键
    ......
}

LinkMan.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入DTD约束
位置:在Hibernate的核心jar包(hibernate-core-5.0.7.Final.jar)中名称为hibernate-mapping-3.0.dtd
明确该文件中的内容:
	实体类和表的对应关系
	实体类中属性和表的字段的对应关系
-->
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.itcast.domain"><!-- package属性用于设定包的名称,接下来该配置文件中凡是用到此包中的对象时都可以省略包名 -->

    <!-- 建立实体类和表的对应关系 -->
    <class name="LinkMan" table="cst_linkman">
        <!-- 映射主键 -->
        <id name="lkmId" column="lkm_id">
            <!-- 配置主键的生成策略 -->
            <generator class="native"></generator>
        </id>
        <!-- 映射其他字段 -->
        <property name="lkmName" column="lkm_name"></property>
        <property name="lkmGender" column="lkm_gender"></property>
        <property name="lkmPhone" column="lkm_phone"></property>
        <property name="lkmMobile" column="lkm_mobile"></property>
        <property name="lkmPosition" column="lkm_position"></property>
        <property name="lkmEmail" column="lkm_email" ></property>
        <property name="lkmMemo" column="lkm_memo"></property>
        <!-- 多对一关系映射
        many-to-one标签:用于建立多对一的关系映射配置
        属性:
           name:指定的实体类中属性的名称
           class:该属性所对应的实体类名称。如果在hibernate-mapping上没有导包,则需要写全限定类名
           column:指定从表中的外键字段名称
        -->
        <many-to-one name="customer" class="cn.itcast.domain.Customer" column="lkm_cust_id"></many-to-one>
    </class>

</hibernate-mapping>

(2)dao类

public class CustomerDao {

    /**
     * 根据id查询客户信息	get立即加载
     */
    public static Customer findOneById(long id){
        Session session = null;//Session对象
        try{
            // 查询不需要开启事务,只有对数据库内容进行多次增删改操作时
            session = HibernateUtil.openSession();
            Customer newCustomer = session.get(Customer.class, id);
            return newCustomer;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            session.close();//释放资源
        }
        return null;
    }

    /**
     * 根据id查询客户信息 load延迟加载
     */
    public static Customer loadOneById(long id){
        Session session = null;//Session对象
        try{
            // 查询不需要开启事务,只有对数据库内容进行多次增删改操作时
            session = HibernateUtil.openSession();
            Customer newCustomer = session.load(Customer.class, id);
            return newCustomer;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            session.close();//释放资源
        }
        return null;
    }
}   

(3)工具类获取session
HibernateUtil

public class HibernateUtil {
    private static SessionFactory factory;
    // 使用静态代码块获取SessionFactory
    static {
        //细节:Hibernate把所有可预见的异常,都转成了运行时异常(工具中不会提示要添加异常块)
        try {
            // 加载hibernate.cfg.xml配置文件
            Configuration config = new Configuration().configure();
            factory = config.buildSessionFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取一个新的Session对象(每次都是一个新对象)
     * @return
     */
    public static Session openSession(){
        return factory.openSession();
    }

    /**
     * 获取一个新的Session对象(每次都是一个新对象)
     * @return
     */
    public static Session getCurrentSession(){
        return factory.getCurrentSession();
    }
}

(4)测试get方法

@Test
public void testGet(){
    Customer oneById = CustomerDao.findOneById(1L);
    System.out.println("-------------------------");
    System.out.println(oneById);
}

测试结果如下:
在这里插入图片描述
我们发现确实有执行sql,但最后还是报错了,报错的位置在Customer的toString方法中,在获取Customer中的linkmans属性时报错,这是因为在执行get方法时,查询的sql,只针对于cst_customer表(等于select * from cst_customer where cust_id = ?),所以不会填充linkmans属性,而在CustomerDao方法的最后,我们手动关闭了session,所以在test方法中,再想通过session去懒加载就加载不了了

解决方式:

  • 1.删除finally中的session.close();
    不可取,使用openSession时,必须要手动关闭session,如果此次不关闭,session就会一直存在,消耗性能
  • 2.在session没关闭时,使用linkmans对象,这样就会去执行sql,初始化linkmans对象了
public static Customer findOneById(long id){
    Session session = null;//Session对象
    try{
        session = HibernateUtil.openSession();
        Customer newCustomer = session.get(Customer.class, id);
        System.out.println("---------------------------------");
        System.out.println(newCustomer);
        return newCustomer;
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        session.close();//释放资源
    }
    return null;
}

在这里插入图片描述

(5)测试load方法

@Test
public void tesLoad(){
    Customer customer = CustomerDao.loadOneById(1L);
    System.out.println(customer);
}

执行结果:我们发现这次连sql语句都没有,报错的情况也是no session,所以load方法获取的对象,也是在使用时才去执行sql
在这里插入图片描述
当调用load()方法的时候会返回一个目标对象的代理对象,在这个代理对象中只存储了目标对象的ID值,只有当调用除ID值以外的属性值的时候才会发出SQL查询的。

public static Customer loadOneById(long id){
    Session session = null;//Session对象
    try{
        // 查询不需要开启事务,只有对数据库内容进行多次增删改操作时
        session = HibernateUtil.openSession();
        Customer newCustomer = session.load(Customer.class, id);
        System.out.println(newCustomer.getCustId());
        return newCustomer;
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        session.close();//释放资源
    }
    return null;
}

这个查询结果也证明了刚刚说的话
在这里插入图片描述
解决方式:和get方法一样,在session未关闭前,使用他们
可以使用 Hibernate.initialize(xx);方法,不过这个方法需要对类中的属性对象也要使用

public static Customer loadOneById(long id){
    Session session = null;//Session对象
    try{
        // 查询不需要开启事务,只有对数据库内容进行多次增删改操作时
        session = HibernateUtil.openSession();
        Customer newCustomer = session.load(Customer.class, id);
        Hibernate.initialize(newCustomer);
        Hibernate.initialize(newCustomer.getLinkmans());
        return newCustomer;
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        session.close();//释放资源
    }
    return null;
}

总结:

  • 返回值:
    get()返回的是查询出来的实体对象
    而load()查询出来的是一个目标实体的代理对象。
  • 查询时机:
    get()在调用的时候就立即发出SQL语句查询
    而load()在访问非ID属性的时候才会发出查询语句并且将被代理对象target填充上,但是如果这个动作发生在Session被关闭后的话就会抛出LazyInitializationException。
  • 查询结果为空时:
    get()抛出NullPointerException
    load()抛出ObjectNotFoundException

2.持久化类和对象标识符

2.1 持久化类的编写规范
  • 持久化类需要提供无参数的构造方法。因为在Hibernate的底层需要使用反射生成类的实例。
  • 持久化类的属性需要私有,对私有的属性提供公有的get和set方法。因为在Hibernate底层会将查询到的数据进行封装。
  • 持久化类的属性要尽量使用包装类的类型。
  • 持久化类要有一个唯一标识OID与表的主键对应。
  • 持久化类尽量不要使用final进行修饰。

3.一级缓存和二级缓存

3.1 一级缓存:session

Hibernate的一级缓存就是指Session缓存

在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。(session关闭,缓存消失)

Hibernate的一级缓存的作用就是减少对数据库的访问次数(降低IO读写次数)。

Hibernate的一级缓存有如下特点:

  • 一级缓存默认是打开状态
  • 一级缓存的使用范围是session范围(从session创建到session关闭)
  • 一级缓存中存储的数据必须都是持久态数据
@Test
public void testCache(){
	//从Hibernate封装的工具类中获取Session对象
 	Session session=HibernateUtil.openSession();
 	//开启事务
 	Transaction tx=session.beginTransaction();
 	//第一次执行get方法:一级缓存中无数据,会去数据库中查询
	Customer c1=session.get(Customer.class, 100L);
	System.out.println("One : "+c1);
	//第二次执行get方法:一级缓存中有数据,直接获取缓存中的数据
Customer c2=session.get(Customer.class, 100L);
	System.out.println("Two : "+c2);
	System.out.println(c1==c2);//结果为true
	tx.commit();
	session.close();
}

在这里插入图片描述
一级缓存执行过程
在这里插入图片描述
一级缓存的特性:快照机制

public void testUpdateName(){
	//从Hibernate封装的工具类中获取Session对象
 	Session session=HibernateUtil.openSession();
 	//开启事务
 	Transaction tx=session.beginTransaction();
 	//第一步:先查询出客户信息(根据ID查询)
 	Customer c=session.get(Customer.class, 95L);
     //第二步:对查询出的客户实体进行修改(修改名称)
 	c.setCustName("传智.黑马程序员");
 	//第三步:调用Hibernate方法实现更新操作
 	//session.update(c); //常规方式是要调用update方法,但此处省略看看执行结果
 	tx.commit();
	session.close();
}

在这里插入图片描述
解释:以上java程序中没有直接调用update方法,同样也对数据修改成功。主要是借助了Hibernate的快照功能

Hibernate 向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行update语句。

结论:Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。


4.Hibernate的事务控制

在这里插入图片描述
为了实现一个线程中session是同一个,可以使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。

在 Hibernate 的配置文件中, hibernate.current_session_context_class 属性用于指定 Session 管理方式, 可选值包括:

<property name="hibernate.current_session_context_class">thread</property>

获取当前线程的session

public static Session getCurrentSession(){
	return factory.getCurrentSession();
}	

5.Session、EntityManager、HibernateTemplate的区别

  • Session是Hibernate操作数据库的对象
  • EntityManager是JPA操作数据库的对象
  • HibernateTemplate是Spring整合Hibernate后操作数据库的对象(底层是session)

6.数据库中字段类型和映射文件中不匹配时

cst_customer表:表中cust_id并非主键,只是不能为null
在这里插入图片描述
Customer.hbm.xml,配置中custId为主键,且生成策略为native(使用本地数据库的策略)

<hibernate-mapping package="cn.itcast.domain">
    <class name="Customer" table="cst_customer">
        <id name="custId" column="cust_id">
            <generator class="native"></generator>
        </id>

        <property name="custName" column="cust_name"></property>
        <property name="custSource" column="cust_source"></property>
        <property name="custIndustry" column="cust_industry"></property>
        <property name="custLevel" column="cust_level"></property>
        <property name="custAddress" column="cust_address"></property>
        <property name="custPhone" column="cust_phone"></property>
    </class>
</hibernate-mapping>

此时当我们在保存对象时:

public static void findOne(Customer c){
    Session session = null;//Session对象
    Transaction tx = null ;//事务对象
    try{
        //使用Hibernate工具类获取Session对象
        session = HibernateUtil.openSession();
        tx = session.beginTransaction();//开启事务
        //保存客户
        session.save(c);//save方法返回实体保存后的主键值
        //提交事务
        tx.commit();
    }catch(Exception e){
        tx.rollback();//事务回滚
        e.printStackTrace();
    }finally{
        session.close();//释放资源
    }
}

执行报错,我们看执行的sql,sql中并没有cust_id的值,而表中cust_id的值不能为空,所以报了这个错
在这里插入图片描述
那么将表中cust_id改为可以为空,能成功吗
还是报错,
在这里插入图片描述
所以我们在开发中,一定要保证表中字段的生成策略和映射中一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值