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改为可以为空,能成功吗
还是报错,
所以我们在开发中,一定要保证表中字段的生成策略和映射中一致