一.持久化类的编写规则
什么是持久化类?
持久化:将内存中的数据永久存储到关系型数据库中的过程,Hibernate框架就是用来进行持久化的框架;
持久化类:一个Java对象与数据库中的表建立了映射关系,那么这类在Hibernate中称为是持久化类,可以通俗认为,持久化类 = Java类 + 映射文件
持久化类的编写规则:
对持久化类提供一个无参的构造方法:因为Hibernate底层需要使用反射生成实例;
属性需要私有,对私有属性提供public的get和set方法:因为Hibernate中需要获取或者设置对象的值;
对持久化类提供一个唯一标识OID与数据库主键对应:Java中通过对象的地址区分是否是同一个对象,数据库中通过主键确定是否是同一个记录,在Hibernate中通过持久化类的OID属性区分是否是同一个对象。
持久化类中属性尽量使用包装类类型:因为基本数据类型默认是0,0会有许多歧义,包装类类型默认为null,与0有着明显的区别。
持久化类不要使用final修饰:延迟加载(也称懒加载)本身是Hibernate的一个优化手段,返回的是一个代理的对象(javassist可以对没有实现接口的类产生代理,其使用了非常底层的字节码增强技术,继承这个类进行代理)。这里如果用final进行修饰,则不能被继承了,也即不能产生代理对象,延迟加载也就失效了,则load方法和get方法也就一样了。
二.主键生成策略
主键分类:
自然主键:主键的本身就是数据库表中的一个字段(即为实体中的一个具体的属性)
举例:创建一个人员表,人员都会有一个身份证号(唯一不可重复的),使用了身份证号作为主键,这种主键称为是自然主键;
代理主键:主键的本身不是表中必须的一个字段(即不是实体中的某个具体的属性)
举例:创建一个人员表,没有使用人员中的身份证号,用了一个与这个表不相关的字段,如ID,这种主键称为是代理主键;
注:在实际开发中,尽量使用代理主键,因为一旦自然主键参与到业务逻辑中,后期有可能需要修改源代码;好的程序一般满足OCP原则,即对程序的扩展是Open的,对源码的修改是Close的。
Hibernate主键生成策略:
在实际开发中,一般不允许用户手动设置主键,一般将主键交给数据库,手动编写程序进行设置。在Hibernate中,为了减少程序编写,提供了很多种的主键生成策略:
increment:hibernate中提供的自动增长机制(没有采用数据库底层的自动增长),适用于short、int、long类型的主键,在单线程程序中使用;
说明:实现过程为,首先发送一条语句“select max(id) from 表名”,然后让id+1作为下一条记录的主键,这就可能产生线程安全问题。
identity:使用short、int、long类型的主键,使用的是数据库底层的自动增长机制,适用于有自动增长机制功能的数据库(MySQL、MSSQL),但是Oracle是没有自动增长机制的;
sequence:使用short、int、long类型的主键,采用的是序列的方式。(Oracle支持序列,MySQL不支持序列);
uuid:适用于字符串类型的主键,使用hibernate中的随机方式生成字符串主键;
native:本地策略,可以在identity和sequence之间进行自动切换;
assigned:hibernate放弃外键的管理,需要手动编写程序或者用户自己设置;
foreign:外部的,在一对一的一种关联映射的情况下使用(了解即可)。
Hibernate主键生成策略实例:
创建项目:
引入jar包,创建两个配置文件(hibernate.cfg.xml,Customer.hbm.xml),持久化类(Customer.java),工具类(HibernateUtils.java),测试类(hibernate_demo1.java)
代码:
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>
<!-- 建立类与表的映射 -->
<class name = "hibernate_start1.Customer" table = "cst_customer">
<!-- 建立类中的属性与表中的主键对应 -->
<id name = "cust_id" column = "cust_id">
<generator class="identity"/>
</id>
<!-- 建立类中的普通的属性和表的字段的对应 -->
<property name="cust_name" column = "cust_name"/>
<property name="cust_source" column = "cust_source"/>
<property name="cust_industry" column = "cust_industry"/>
<property name="cust_level" column = "cust_level"/>
<property name="cust_phone" column = "cust_phone"/>
<property name="cust_mobile" column = "cust_mobile"/>
</class>
</hibernate-mapping>
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:///hibernate_start1</property>
<property name = "hibernate.connection.username" >root</property>
<property name = "hibernate.connection.password" >2e5y8hxf</property>
<!-- hibernate方言设置 -->
<property name = "hibernate.dialect" >org.hibernate.dialect.MySQLDialect</property>
<!-- 可选配置 -->
<!-- 打印SQL -->
<property name = "hibernate.show_sql">true</property>
<!-- 格式化SQL -->
<property name = "hibernate.format_sql">true</property>
<!-- 自动创建表 -->
<property name = "hibernate.hbm2ddl.auto">update</property>
<mapping resource = "hibernate_start1/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Customer.java
package hibernate_start1;
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_phone;
private String cust_mobile;
public Long getCust_id() {
return cust_id;
}
public void setCust_id(Long cust_id) {
this.cust_id = cust_id;
}
public String getCust_name() {
return cust_name;
}
public void setCust_name(String cust_name) {
this.cust_name = cust_name;
}
public String getCust_source() {
return cust_source;
}
@Override
public String toString() {
return "Customer [cust_id=" + cust_id + ", cust_name=" + cust_name + ", cust_source=" + cust_source
+ ", cust_industry=" + cust_industry + ", cust_level=" + cust_level + ", cust_phone=" + cust_phone
+ ", cust_mobile=" + cust_mobile + "]";
}
public void setCust_source(String cust_source) {
this.cust_source = cust_source;
}
public String getCust_industry() {
return cust_industry;
}
public void setCust_industry(String cust_industry) {
this.cust_industry = cust_industry;
}
public String getCust_level() {
return cust_level;
}
public void setCust_level(String cust_level) {
this.cust_level = cust_level;
}
public String getCust_phone() {
return cust_phone;
}
public void setCust_phone(String cust_phone) {
this.cust_phone = cust_phone;
}
public String getCust_mobile() {
return cust_mobile;
}
public void setCust_mobile(String cust_mobile) {
this.cust_mobile = cust_mobile;
}
}
HibernateUtils.java:
package hibernate_utils1;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
public static final Configuration cfg;
public static final SessionFactory sf;
static{
cfg = new Configuration().configure();
sf = cfg.buildSessionFactory();
}
public static Session openSession(){
return sf.openSession();
}
}
hibernate_demo1.java:
package hibernate_start1;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import hibernate_utils1.HibernateUtils;
public class hibernate_demo1 {
@Test
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer = new Customer();
customer.setCust_name("张无忌");
session.save(customer);
transaction.commit();
session.close();
}
@Test
public void demo2() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer = new Customer();
customer.setCust_name("赵敏");
session.save(customer);
transaction.commit();
session.close();
}
}
测试结果:
分析:将Customer.hbm.xml文件中的主键生成策略参数<generator = “native”>改成increment,再将hibernate.cfg.xml文件中的自动建表方式改成create,这时创建的表是非自动增长的,此时再将create改成update,在测试类hibernate_demo1中同时测试demo1()和demo2(),当demo1()执行保存操作后,会查询表中当前的最大的主键id:
提交操作前执行demo2(),到demo2()执行到提交操作前暂停,此时demo2()也会查询表中的最大主键id:
也就是说demo1()和demo2()查询到的主键id一样。之后回去继续执行demo1()中的提交,此时表中按照自动增长的方式添加了一条数据A(主键为a+1),之后再执行demo2()中的提交操作,这时程序报错,此时demo2()也将添加一条数据B(主键也为a+1),所以两个线程发生冲突。
解决方法:将increment换成identity,使用的是数据库底层的自动增长机制,不受Hibernate约束。
三.持久化类的三种状态
三种状态概述:
1.瞬时态(transient):这种对象没有持久化标识OID(相当于主键),没有被session管理;
2.持久态(persistent):这种对象有持久化标识OID,并且被session管理;
3.脱管态(detached):这种对象有持久化标识OID,没有别session管理;
@Test
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer = new Customer(); //这里customer为瞬时态对象,没有持久化标识OID,没有被session管理
customer.setCust_name("张无忌");
session.save(customer); //这行代码执行过后,customer为持久态对象,有持久化标识OID,并且被session管理
transaction.commit();
session.close();
System.out.println(customer.getCust_name()); //这里的customer为脱管态对象,有持久化标识OID,但是没有被session管理
}
分析:
Customer customer = new Customer(); //这里customer为瞬时态对象,没有持久化标OID,没有被session管理
session.save(customer); //这行代码执行过后,customer为持久态对象,有持久化标识OID,并且被session管理
System.out.println(customer.getCust_name()); //这里的customer为脱管态对象,有持久化标识OID,但是没有被session管理
三种状态转换:
状态转换图:
瞬时态对象:
获取瞬时态对象:
Customer customer = new Customer();
状态转换:
瞬时态
→
\to
→持久态:
save(Object obj)、saveOrUpdate(Object obj)
瞬时态
→
\to
→脱管态:
customer.setCust_id(1l);
持久态对象:
获取持久态对象:
通过get()、load()、find()、iterate()对象获取,比如:Customer customer = new Customer();
状态转换:
持久态
→
\to
→瞬时态:
delete();
持久态
→
\to
→脱管态:
close()、clear()、evict(Object obj);
脱管态对象:
获取脱管态对象:
Customer customer = new Customer();
customer.setCust_id(1l);
状态转换:
脱管态
→
\to
→持久态:update()、saveOrUpdate();
脱管态
→
\to
→瞬时态:customer.setCust_id(null);
持久态对象特性:
主要特性:持久化类的持久化对象能够自动地更新数据库
实例:在获取持久化对象后,再调用save等方法对数据库进行更新
@Test
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer = session.get(Customer.class, 1l); //这里不再需要调用save方法
customer.setCust_name("赵敏");
transaction.commit();
session.close();
}
四.Hibernate一级缓存
一级缓存概述:
什么是Hibernate一级缓存:
Hibernate一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放java对象。在使用Hibernate查询对象的时候,首先会使用对象属性的OID的值在Hibernate一级缓存中查找,如果找到匹配OID的值,则将该对象从一级缓存中取出(这里可以认为是复制)使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需的数据时,该数据信息也会放置到一级缓存中。Hibernate一级缓存的作用就是减少对数据库的访问次数。
Hibernate一级缓存的特点:
当程序调用Session接口的save()、update()、saveOrUpdate()时,如果一级缓存中没有相应java对象,则将该对象信息加入到一级缓存中;
当程序调用Session接口的load()、get(),以及Query接口的list()、iterator()方法时,会判断一级缓存中是否存在该对象,有则从一级缓存中返回该对象,不会再查询数据库;如果一级缓存中没有要查询的对象,再到数据库中查询相应的对象,并将其添加到一级缓存中。
当调用Session的close()方法时,Session缓存会被清空。
一级缓存实例:
@Test
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer1 = session.get(Customer.class, 1l);
System.out.println(customer1);
Customer customer2 = session.get(Customer.class, 1l);
System.out.println(customer2);
System.out.println(customer1 == customer2);
transaction.commit();
session.close();
}
运行结果:
结果分析:
第一次查询(customer1),一级缓存中没有这个对象,所以需要去数据库中查询,所以打印了select…查询语句,之后将这个对象存到一级缓存中;第二次查询(customer2),一级缓存中已经有了这个对象,所以直接返回就行,不需要再去数据库中查找,所以没有打印select…查询语句;而且最后两次查询得到的持续化对象地址比较为true,说明说明这两个对象存在同一个地方(即一级缓存中)。
快照区:
一级缓存包含:缓存区和快照区,底层实现一般是用map,缓存区和快照区分别对应于,map的键和值。
举例:
当执行代码Customer customer = session.get(Customer.class, 1l); 时,会将缓存区中id为1的数据拷贝一份到快照区,当执行customer.setCust_name(“赵敏”);时,会改变缓存区中的值(比如改为“赵敏”),当进行事务提交时,会比较缓存区与快照区中id为1的值是否一致,若一致,不更新数据库,若不一致,则更新数据库。
Customer customer = session.get(Customer.class, 1l);
customer.setCust_name("赵敏");
transaction.commit();
五.Hibernate中的事务管理
事务的隔离级别:
Hibernate中设置事务隔离级别:在核心配置文件中设置,在Spring中默认为4。
<property name = "hibernate.connection.isolation">4</property>
与线程绑定的session:
分析:在DAO层中执行的是对单个数据源的操作,在Service层中执行封装的业务逻辑操作,在这里s1()方法中的DAO1.aaa()和DAO2.bbb()必须保证连接对象是同一个Session对象,有两种实现方式:
1.向下传递 DBUtils
2.使用ThreadLocal对象,即线程绑定对象(实现过程:如首先将DAO1.aaa()这个连接绑定到当前线程中,在调用DAO2.bbb()时,通过当前线程获取Session连接,就能保住DAO1和DAO2连接的是同一个Session了)
Hibernate中:Hibernate框架内部已经绑定好了ThreadLocal,在SessionFactory提供了一个方法getCurrentSession()用于获取当前线程中的Session。
实例:
在工具类HibernateUtils中加入getCurrentSession()函数:
在测试类中,以Session session = HibernateUtils.getCurrentSession();方式获取Session对象:
注:由于这里的Session是线程绑定的,随着线程的结束而销毁,所以不需要关闭。
@Test
public void demo4() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//编写代码
Customer customer1 = session.get(Customer.class, 1l);
System.out.println(customer1);
Customer customer2 = session.get(Customer.class, 1l);
System.out.println(customer2);
System.out.println(customer1 == customer2);
transaction.commit();
//session.close();
}
核心配置文件代码:
五.Hibernate的其他API
1.Query
概述:Query代表Hibernate中面向对象的查询操作。在Hibernate中,通常使用session.createQuery()方法接收一个HQL语句,然后调用Query的list()或uniqueResult()方法执行查询。
HQL:Hibernate Query Language,其语法很想SQL,但HQL是完全面向对象的。
在Hibernate中使用Query对象的步骤:
(1)获得Hibernate的Session对象;
(2)编写HQL语句;
(3)调用session.createQuery创建查询对象;
(4)如果HQL语句包含参数,则调用Query的setXxx语句来设置参数;
(5)调用Query的list()或者uniqueResult()方法来执行查询;
举例:
查询所有数据:
@Test
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//编写代码
String hql = "from Customer";
Query query = session.createQuery(hql);
List<Customer> list = query.list();
System.out.println(list);
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
//session.close(); 因为这里使用的是线程绑定的session,随着线程的结束而关闭,不需要手动关闭
}
查询结果:
条件查询:
@Test
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//编写代码
String hql = "from Customer where cust_name like ?";
Query query = session.createQuery(hql);
query.setParameter(0, "赵%");
List<Customer> list = query.list();
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
查询结果:
分页查询:这里分页查询查询1页,参数setFirstResult(0)代表从id为0(不包括0)开始查询,setMaxResults(2)这里的2代表每页有两个记录。
@Test
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//编写代码
String hql = "from Customer";
Query query = session.createQuery(hql);
query.setFirstResult(0);
query.setMaxResults(2);
List<Customer> list = query.list();
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
2.Query
概述:Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层是怎么实现的,以及SQL语句是如何编写的。Criteria查询又称QBC(Query by Criteria)查询,是Hibernate中另一种对象检索方式。
举例:
查询所有:
@Test
public void demo6() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//查询所有
Criteria criteria = session.createCriteria(Customer.class);
List<Customer> list = criteria.list();
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
条件查询:
@Test
public void demo6() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//条件查询
Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.like("cust_name", "赵%"));
List<Customer> list = criteria.list();
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
分页查询:
@Test
public void demo6() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//分页查询
Criteria criteria = session.createCriteria(Customer.class);
criteria.setFirstResult(0);
criteria.setMaxResults(2);
List<Customer> list = criteria.list();
for(Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}