流程:
首先加载配置文件,将配置文件传递到properties中,然后创建sessionFactory工厂,在该工厂中创建和数据库的连接,最后拿到其中一个连接session,然后进行CURD。
Hibernate执行原理总结
可从度娘上摘抄到如下文字:
- 通过
Configuration().configure();
读取并解析hibernate.cfg.xml配置文件。 - 由hibernate.cfg.xml中的
<mapping resource="com/xx/Xxx.hbm.xml"/>
读取解析映射信息。 - 通过
config.buildSessionFactory();
得到sessionFactory。 sessionFactory.openSession();
得到session。session.beginTransaction();
开启事务。persistent operate;
执行你自己的操作。session.getTransaction().commit();
提交事务。- 关闭session。
- 关闭sessionFactory。
1.导jar包(lib/required)和数据库驱动
2.创建实体(持久化类)
cn.itcast.domain包下:
注意该类的属性名称和类型和数据库相应表对应。
实现相应get/set方法
3.创建映射文件(持久化类和数据库表字段,并没有配置数据库信息,在核心配置文件中配置)
一个类对应一个,最终要被加载到核心配置文件中。
<hibernate-mapping>
<!-- 建立类与表的映射 -->
<class name="com.itheima.hibernate.demo1.Customer" table="cst_customer">
<!-- 建立类中的属性与表中的主键对应 -->
<id name="cust_id" column="cust_id">
<generator class="native"/>
</id>
<!-- 建立类中的普通的属性和表的字段的对应 -->
<property name="cust_name" column="cust_name"/>
<property name="cust_source" column="cust_source" length="32"/>
<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>
4.创建Hibernate的核心配置文件(默认是hibernate.cfg.xml)
1.加载数据库相关信息
2.Hibernate的相关配置
3.加载映射配置文件
<hibernate-configuration>
<session-factory>
<!-- 连接数据库的基本参数 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate_day01?character=utf-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</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>
<!-- Hibernate加载映射-->
<mapping resource="com/itheima/hibernate/demo1/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
5.测试案列:
// 保存客户的案例
public void demo1(){
// 1.加载Hibernate的核心配置文件
Configuration configuration = new Configuration().configure();
// 手动加载映射
// configuration.addResource("com/itheima/hibernate/demo1/Customer.hbm.xml");
// 2.创建一个SessionFactory对象:类似于JDBC中连接池
SessionFactory sessionFactory = configuration.buildSessionFactory();
// 3.通过SessionFactory获取到Session对象:类似于JDBC中Connection
Session session = sessionFactory.openSession();
// 4.手动开启事务:
Transaction transaction = session.beginTransaction();
// 5.编写代码
Customer customer = new Customer();
customer.setCust_name("王西");
session.save(customer);
// 6.事务提交
transaction.commit();
// 7.资源释放
session.close();
sessionFactory.close();
}
6.抽取HiberbateUtils工具类
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();
}
}
7.Hibernate的API
7.1 Configuration:Hibernate的配置对象
作用: 加载核心配置文件(默认加载hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
7.2 SessionFactory:Session工厂(对应了核心配置文件的<session-factory> XXXXX </session-factory>标签,在加载完核心配置文件后,得到了properties里的各属性(大多数是和数据库的映射),方便创建该工厂类)。
需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够
SessionFactory内部维护了Hibernate的连接池和Hibernate的二级缓存(不讲)。是线程安全的对象。一个项目创建一个对象即可。
我的理解——SessionFactory它不是轻量级的,所以不要频繁创建关闭它。在一个项目中有一个SessionFactory就可以了,通过SessionFactory来获取Session进行操作。那么问题来了,怎样可以保证在一个项目中所使用的SessionFactory是同一个哪?
可在cn.itheima.utils包下创建一个工具类——HibernateUtils.java,其代码为:
7.3 Session:类似Connection对象是连接对象
Session代表的是Hibernate与数据库的链接对象。不是线程安全的。与数据库交互桥梁。
Session中的API
7.3.1保存方法:
Serializable save(Object obj);
7.3.2查询方法:
T get(Class c,Serializable id);
T load(Class c,Serializable id);
get方法和load方法的区别?
get方法
* * 采用的是立即加载,执行到这行代码的时候,就会马上发送SQL语句去查询。
* * 查询后返回是真实对象本身。
* * 查询一个找不到的对象的时候,返回null
load方法:
* * 采用的是延迟加载(lazy懒加载),执行到这行代码的时候,不会发送SQL语句,当真正使用这个对象的时候才会发送SQL语句。
* * 查询后返回的是代理对象。javassist-3.18.1-GA.jar 利用javassist技术产生的代理。
* * 查询一个找不到的对象的时候,返回ObjectNotFoundException
7.3.3 修改方法
void update(Object obj);
方法1:直接创建对象,进行修改
/*Customer customer = new Customer();
customer.setCust_id(1l);
customer.setCust_name("王聪");
session.update(customer);*/
方法2:先查询,再修改(推荐)
Customer customer = session.get(Customer.class, 1l);
customer.setCust_name("王小贱");
session.update(customer);
7.3.4 删除方法
voiddelete(Object obj);
方法1: 直接创建对象,删除
/* Customer customer = new Customer();
customer.setCust_id(1l);
session.delete(customer);*/
方法2:// 先查询再删除(推荐)--级联删除
Customer customer = session.get(Customer.class, 2l);
session.delete(customer);
7.3.5 保存或更新
void saveOrUpdate(Object obj)
方法1:
/*Customer customer = new Customer();
customer.setCust_name("王凤");
session.saveOrUpdate(customer);*/
方法2:
Customer customer = new Customer();
customer.setCust_id(3l); //有id更新
customer.setCust_name("李如花");
session.saveOrUpdate(customer);
7.3.6 查询所有
方法1:
// 接收HQL:Hibernate Query Language 面向对象的查询语言
/*Query query = session.createQuery("from Customer");
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}*/
方法2:
// 接收SQL:
SQLQuery query = session.createSQLQuery("select * from cst_customer");
List<Object[]> list = query.list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
第二讲:Hibernate主键生成策略--一级缓存--事务管理
1.1 什么是持久化类
持久化:将内存中的一个对象持久化到数据库中的过程。Hibernate框架就是用来进行持久化的框架。
持久化类:一个java对象与数据库的表建立了映射关系,那么这个类在Hibernate中称为是持久化类。
持久化类=java类+映射文件
1.2 持久化类的编写规则
l 对持久化类提供一个无参数的构造方法: Hibernate底层需要使用反射机制生成实例
l 属性需要私有,对私有属性提供public的get和set方法: Hibernate中获取,设置对象的值。
l 对持久化类提供一个唯一标识符OID与数据库主键对应: java中通过对象的地址区分是否是同一个对象,数据库中通过主键确定是否是同一个记录,在Hibernate中通过持久化类的OID的属性区分是否是同一个对象。
l 持久化类中的属性尽量要使用包装类类型来定义: 因为基本数据类型默认值是0,那么0就会有很多的歧义。但是包装类类型默认值是null.
l 持久化类不要使用final进行修饰: 延迟加载本身是Hibernate一个优化手段。返回的是一个代理对象(javassist,可以对没有实现接口的类产生代理---使用了非常底层字节码增强技术,继承这个类来代理),如果不能被继承,不能产生代理对象,延迟加载也就失效,Load和get就一致了。
1.3 主键生成规则
1.3.1 主键的分类
1.3.1.1 自然主键
l 自然主键:主键的本身就是表中的一个字段(实体中的一个具体属性)
创建一个人员表,人员都会有一个身份证号(唯一的不可重复的),使用了身份证号作为主键。
1.3.1.2 代理主键
l 代理主键:主键的本身不是表中必须的一个字段(不是实体中的一个具体属性)
n 创建一个人员表,没有使用了人员的身份证号,用了一个与这个表不相关的字段ID,(PNO)。这种主键就是代理主键。
l 在实际开发中尽量使用代理主键
n 一旦自然主键参与到业务逻辑中,后期就有可能需要修改源代码。
n 一个好的程序设计要满足OCP原则,对程序的扩展是open的,对修改源代码是close的。
1.3.2 主键的生成策略
在实际开发中一般不允许用户手动设置主键,一般将主键交给数据库,手动编写程序进行设置。在Hibernate中为减少程序编写,提供了很多的主键生成策略。
l increment: hibernate中提供的自动增长机制,适用于short、int、long类型的主键。在单线程程序中使用。
n 首先发送一条语句,select max(id) from 表; 然后让id+1作为下一条记录的主键。
l identity:适用于short、int、long类型的主键,使用的是数据库底层的自动增强机制。适用于有自动增强机制数据库(MySQL,MSSQL),但是Oracle是没有自动增长。
l sequence: 适用于short、int、long类型的主键,采用的是序列的方式。(Oracle支持序列)。
l uuid: 适用于字符串类型主键。使用hibernate中的随机方式生成字符串主键。
l native: 本地策略,可以在identity和sequence之间进行自动切换。
l assigned: hibernate放弃外键的管理,需要通过手动编写程序或者用户自己设置。
l foreign: 外部的,一对一的一种关联映射的情况下使用。
1.4持久化类的三种状态
1.4.1持久化类的三种状态
Hibernate是持久层框架,通过持久化类完成ORM操作。Hibernate为了更好的管理持久化类,将持久化类分成三种状态。
持久化类=java类+映射
1.4.1.1 瞬时态
l 这种对象没有唯一的标识OID,没有被session管理,称为是瞬时态对象。
1.4.1.2 持久态
l 这种对象有唯一的标识OID,被session管理,称为是持久态对象。
n 持久化类的持久态的对象,可以自动更新数据库。
1.4.1.3 脱管态
l 这种对象有唯一的标识OID,没有被session管理,称为是脱管态对象。
1.4.1.4 如何区分这3中状态
1.4.2 持久化类的状态转换(了解)
1.4.2.1 三种状态的转换图
1.4.2.2 瞬时态对象
l 瞬时态对象
n 获得瞬时态对象
u Customercustomer = new Customer();
n 状态转换
u 瞬时->持久
save(Object obj)、saveOrUpdate(Objectobj)
u 瞬时->脱管
customer.setCust_id(1l);
1.4.2.3 持久态对象
l 持久态对象
n 获得
u get()、load()、find()、iterate()
u Customercustomer = session.get(Customer.class, 1l);
n 状态转换
u 持久->瞬时
delete()
u 持久->脱管
close()、clear()、evict(Objectobj)、
1.4.2.4 脱管态对象
l 脱管态对象
n 获得
u Customercustomer = new Customer(); customer.setCust_id(1l);
n 状态转换
u 脱管->持久
update()、saveOrUpdate()
u 脱管->瞬时
customer.setCust_id(null);
1.4.3 持久态对象特性
1.4.3.1 持久化类持久态对象自动更新数据库
@Test
//持久态对象自动更新数据库
publicvoid demo2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
//获得持久化对象,该对象可以自动更新数据库
Customer customer = session.get(Customer.class, 1l);
customer.setCust_name("王南");
//session.update(customer);
tx.commit();
session.close();
}
1.5Hibernate的一级缓存
1.5.1 缓存的概述
1.5.1.1 什么是缓存
缓存:一种优化的方式,将数据存入到内存中,使用的时候直接从缓存中获取,不用通过存储源。
1.5.2 Hibernate的缓存
1.5.2.1 Hibernate的一阶缓存
Hibernate框架中提供了优化手段:缓存和抓取策略。Hibernate中提供了两种缓存机制:一级缓存、二级缓存。
Hibernate的一级缓存:称为是Session级别的缓存,一级缓存生命周期与Session一致(一级缓存是由Session中的一系列的java集合构成)。一级缓存是自带的不可卸载的。(Hibernate 的二级缓存是SessionFactory级别的缓存,需要配置的缓存)。
1.5.2.2 证明一级缓存的存在
publicclass HibernateDemo3 {
@Test
//证明一级缓存的存在
publicvoid demo1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
/*Customer customer1 =session.get(Customer.class, 1l); //发送SQL语句
System.out.println(customer1);
Customer customer2 =session.get(Customer.class, 1l); //不发送SQL语句
System.out.println(customer2);
System.out.println(customer1==customer2);
*/
Customer customer = new Customer();
customer.setCust_name("凤姐");
Serializable id= session.save(customer);
Customer customer2 = session.get(Customer.class, id); //不发送SQL语句
System.out.println(customer2);
tx.commit();
session.close();
}
}
1.5.3 Hibernate的一级缓存的内部结构
1.5.3.1 一级缓存中特殊区域:快照区
@Test
//一级缓存的清空
publicvoid demo3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = session.get(Customer.class, 1l); //发送SQL语句查询,同时放入到一级缓存中
session.clear(); //清空所有
session.evict(customer1); //清除一个对象
Customer customer2 = session.get(Customer.class, 1l); //发送SQL语句查询
System.out.println(customer1);
System.out.println(customer2);
tx.commit();
session.close();
}
1.6Hibernate的事务管理
1.6.1 事务的回顾
1.6.1.1 什么是事务
l 事务:事务指的是逻辑上的一组操作,组成这组操作的各个逻辑单元要么全都成功,要么全都失败。
1.6.1.2 事务特性
l 原子性 : 代表事务不可分割
l 一致性: 代表事务执行的前后,数据的完整性保持一致。
l 隔离性:代表一个事务执行的过程中,不应该受到其他事务的干扰、
l 持久性:代表事务执行完成后,数据就持久到数据库中。
1.6.1.3 如果不考虑隔离性,引发安全性问题
l 读问题
n 脏读: 一个事务读到另一个事务未提交的数据。
n 不可重复读: 一个事务读到了另一个事务已经提交的update数据,导致在前一个事务多次查询结果不一致。
n 虚读: 一个事务读到了另一个事务已经提交的insert数据,导致在前一个事务多次查询结果不一致。
l 写问题(了解)
n 引发两类数据更新
1.6.1.4 读问题的解决
l 设置事务的隔离级别
n Readuncommitted: 以上读问题都会发生
n Read committed: 解决脏读,但是不可重复读和虚读都有可能发生
n Repeatable read: 解决脏读和不可重复读,但是虚读有可能发生
n Serializable: 解决所有问题
1.6.2 Hibernate中设置事务隔离级别
1.6.2.1 Hibernate中设置事务隔离级别
<!-- 设置事务隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
1.6.3 Service层事务
1.6.3.1 Hibernate解决Service的事务管理
l 改写工具类
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();
}
public static Session getCurrentSession() {
return sf.getCurrentSession();
}
}
<!-- 配置当前线程绑定的Session -->
这是获取一个与线程绑定的Session(即线程安全的Session)。
<property name="hibernate.current_session_context_class">thread</property>
Hibernate中常用API-Session的补充
讲完Hibernate持久化对象的三种状态和一级缓存之后,我就可以继续深入一点的讲解Session类中的以下方法了。
update
udpate操作主要是针对于脱管对象而言的,因为持久化对象具有自动更新数据库的能力。如果我们直接操作的对象是一个脱管对象,执行update会出现什么情况?
得出结论:update操作时,如果操作的对象是一个脱管对象,则可以操作,并且它会将脱管对象转换成持久对象再操作。
第三讲 Hibernate表操作-多对多配置
1.1
1.2创建映射文件
l 多的一方的映射的创建
<!-- 配置多对一的关系:放置的是一的一方的对象 -->
<!--
many-to-one标签
* name :一的一方的对象的属性名称。
* class :一的一方的类的全路径。
* column :在多的一方的表的外键的名称。
-->
<many-to-one name="customer" class="com.itheima.hibernate.domain.Customer" column="lkm_cust_id"/>
l 一的一方的映射的创建
<!-- 配置一对多的映射:放置的多的一方的集合 -->
<!--
set标签 :
* name :多的一方的对象集合的属性名称。
* cascade:级联
* inverse:放弃外键维护权。
-->
<set name="linkMans" >
<!--
key标签
* column:多的一方的外键的名称。
-->
<key column="lkm_cust_id"/>
<!--
one-to-many标签
* class :多的一方的类的全路径
-->
<one-to-many class="com.itheima.hibernate.domain.LinkMan"/>
</set>
1.3 测试
// 创建两个客户
Customer customer1 = new Customer();
customer1.setCust_name("王东");
Customer customer2 = new Customer();
customer2.setCust_name("赵洪");
// 创建三个联系人
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("凤姐");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("如花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("旺财");
// 设置关系:
linkMan1.setCustomer(customer1);
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer2);
customer1.getLinkMans().add(linkMan1);
customer1.getLinkMans().add(linkMan2);
customer2.getLinkMans().add(linkMan3);
//3次联系人插入,2次客户插入,3次联系人更新(联系人对象发起,通过快照去和一级缓存区对比发现不一样),3次联系人发起(客户发起)
// 保存数据:
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
session.save(customer1);
session.save(customer2);
1.4 一对多关系只保存一边是否可以
public void demo2(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("赵洪");
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name("如花");
customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);
// 只保存一边是否可以:不可以,报一个瞬时对象异常:持久态对象关联了一个瞬时态对象。
// session.save(customer); //保存的那个是持久态,但是没保存的是瞬时态
session.save(linkMan);
tx.commit();
}
报错:21:19:17,351 ERROR SessionImpl:2994 - HHH000346: Error during managed flush [object references an unsaved transient instance - save the transient instance before flushing: com.itheima.hibernate.domain.Customer]
1.5 级联保存或更新操作:
* 级联保存或更新操作:
* * 保存客户级联联系人,操作的主体是客户对象,需要在Customer.hbm.xml中进行配置
* * <set name="linkMans" cascade="save-update">
1.6 测试对象的导航
/**
* 测试对象的导航
* * 前提:一对多的双方都设置cascade="save-update"
*/
public void demo5(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("李兵");
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("凤姐");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("如花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("芙蓉");
linkMan1.setCustomer(customer);
customer.getLinkMans().add(linkMan2);
customer.getLinkMans().add(linkMan3);
// 双方都设置了cascade
// session.save(linkMan1); // 发送几条insert语句 4条
// session.save(customer); // 发送几条insert语句 3条
session.save(linkMan2); // 发送几条insert语句 1条
tx.commit();
1.7 级联删除:
/**
* 级联删除:
* * 删除客户级联删除联系人,删除的主体是客户,需要在Customer.hbm.xml中配置
* * <set name="linkMans" cascade="save_update,delete">
*/
public void demo6(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
// 没有设置级联删除,默认情况:修改了联系人的外键,删除客户
/*Customer customer = session.get(Customer.class, 1l);
session.delete(customer);*/
// 删除客户,同时删除联系人
Customer customer = session.get(Customer.class, 1l);
session.delete(customer);
tx.commit();
}
1.8 一对多设置了双向关联产生多余的SQL语句
@Test
/**
* 将2号联系人原来归1号客户,现在改为2号客户
*/
public void demo8(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
// 查询2号联系人
LinkMan linkMan = session.get(LinkMan.class, 2l);
// 查询2号客户
Customer customer = session.get(Customer.class, 2l);
// 双向的关联
linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);
tx.commit();
}