Hibernate
1、Hibernate框架概述
1.1、什么是Hibernate?
1、Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
2、Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JaveEE架构中取代CMP,完成数据持久化的重任。
3、总结下来
- 一个框架
- 一个Java领域的持久层框架
- 一个ORM框架
1.2、对象的持久化
1、狭义的理解,“持久化”仅仅指把对象永久保存到数据库中
2、广义的理解,“持久化"包括和数据库相关的各种操作:
- 保存:把对象永久保存到数据库中。
- 更新:更新数据库中对象(记录)的状态。一删除:从数据库中删除一个对象。
- 查询:根据特定的查询条件,把符合查询条件的一个或多个对象从数据库加载到内存中。
- 加载:根据特定的OID,把一个对象从数据库加载到内存中。
3、什么是OID?
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier—OID)。
1.3、对ORM的理解
1、ORM(Object/Relation Mapping):对象关系映射。ORM主要解决对象-关系的映射。
2、ORM的思想:将关系数据库中表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。
3、ORM采用元数据来描述对象-关系映射细节,元数据通常采用XML格式,并且存放在专门的对象-关系映射文件中。
1.4、Java里流行的ORM框架
1、Hibernate:
- 非常优秀、成熟的ORM框架。-完成对象的持久化操作
- Hibernate 允许开发者采用面向对象的方式来操作关系数据库。
- 消除那些针对特定数据库厂商的SQL代码
2、MyBatis:
- 相比 Hibernate灵活高,运行速度快
- 开发速度慢,不支持纯粹的面向对象操作,需熟悉sql语句,并且熟练使用sql语句优化功能
3、TopLink
4、OJB 等
2、Hibernate第一个入门程序
2.1、环境准备
使用maven新建一个普通Java项目,并导入Hibernate需要的依赖(hibernate-core),junit以及MySQL驱动的依赖即可,并解决资源导出问题,注意这里关于Hibernate只需导入hibernate-core的依赖即可,maven会自动帮助我们下载其他需要的依赖。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.howie</groupId>
<artifactId>day01</artifactId>
<version>1.0-SNAPSHOT</version>
<!--相关依赖-->
<dependencies>
<!--mysql-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
<!--hibernate-core-->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.10.Final</version>
</dependency>
</dependencies>
<build>
<finalName>ssm-build</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
2.2、编写实体类(pojo)与映射文件(xxx.hbm.xml)
1、编写实体类,注意这里我们以一个User实体类为例,我们这里使用Hibernate暂时不需要创建与实体类对应数据库表,只要建好数据库即可。
User.class
/**
* @description: User实体类
* @author: laizhenghua
* @date: 2020/12/4 13:18
*/
public class User {
private Integer id;
private String username;
private String password;
public User(){} // 注意必须要有空参构造器
...
// getter and setter
// toString
}
2、编写映射文件,在Hibernate中,映射文件的命名规范为类名.hbm.xml
。编写完的映射配置文件应与实体类在同一个包下。
User.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">
<!--Mapper映射文件-->
<hibernate-mapping>
<!-- 建立类与表的映射 -->
<class name="com.howie.pojo.User" table="user">
<!-- 建立类中的属性与表中的主键相对应 -->
<id name="id" type="java.lang.Integer">
<column name="id"/>
<!-- 主键的生成策略,后面会讲,native:使用数据库本地生成策略 -->
<generator class="native"/>
</id>
<!-- 建立类中的普通属性和表中的字段相对应 -->
<property name="username" type="java.lang.String">
<column name="username"/>
</property>
<property name="password" type="java.lang.String">
<column name="password"/>
</property>
</class>
</hibernate-mapping>
2.3、编写Hibernate的核心配置文件
核心配置文件主要是Hibernate框架所使用的,它主要包含了连接数据库的相关信息和Hibernate的相关配置等,至于核心配置文件的命名规范为hibernate.cfg.xml
,为什么要这样命名呢?
源码分析:
// 当我们通过Configuration类的对象加载核心配置文件时,读取的文件名为 hibernate.cfg.xml
public Configuration configure() throws HibernateException {
return this.configure("hibernate.cfg.xml");
}
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>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true&characterEncoding=utf8</property>
<property name="connection.username">root</property>
<property name="connection.password">101323</property>
<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">create</property>
<!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
<mapping resource="com/howie/pojo/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
2.4、通过Hibernate API 编写入门程序
1、新建一个单元测试类,并编写相应测试代码!
@Test
public void test(){
// 1、加载 Hibernate 的核心配置文件
/*
Configuration类用来管理我们的配置文件的信息的,通过它我们可以通过创建一个configuration实例来管理相应的配置文档,
但是通常我们只创建一个configuration实例。
*/
Configuration configuration = new Configuration().configure();
//如果在Hibernate的核心配置文件没有设置加载哪个映射文件,则可手动加载映射文件
//configuration.addResource("com/howie/pojo/User.hbm.xml");
// 2、创建SessionFactory对象,类似JDBC中的链接池
SessionFactory sessionFactory = configuration.buildSessionFactory();
// 3. 通过SessionFactory获取到Session对象,类似于JDBC中的Connection
Session session = sessionFactory.openSession();
// 4、手动开启事务,(最好是手动开启事务)
Transaction transaction = session.beginTransaction();
// 5、执行保存操作
User user = new User();
user.setUsername("alex");
user.setPassword("123");
session.save(user);
// 6、提交事务
transaction.commit();
// 7、关闭 Session
session.close();
// 8、关闭 SessionFactory
sessionFactory.close();
}
2、执行,查看结果!
ok,数据库里已经添加成功,到这里我们已正式入门Hibernate !
2.5、第一个入门程序总结
1、Hibernate工作原理是Configuration读取Hibernate的配置文件和映射文bai件中的信息,即加载配置文件和映射文件,并通过Hibernate配置文件生成一个多线程的SessionFactory对象。
2、然后,多线程SessionFactory对象生成一个线程Session 对象;Session对象生成Query对象或者Transaction对象;可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate( )等方法对PO进行加载、保存、更新、删除等操作。
3、在查询的情况下,可通过Session 对象生成一个Query对象,然后利用Query对象执行查询操作,如果没有异常,Transaction对象将提交这些操作结果到数据库中。
4、关于Session接口:
- Session是应用程序与数据库之间交互操作的一个单线程对象,是Hibernate运作的中心,所有持久化对象必须在session的管理下才可以进行持久化操作。此对象的生命周期很短。
- Session对象有一个一级缓存,显式执行flush之前,所有的持久层操作的数据都缓存在session对象处。相当于JDBC中的Connection。
5、Transaction(事务)
- 代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
- 常用方法:
commit()
提交相关联的session实例rollback()
撤销事务操作wasCommitted()
检查事务是否提交
6、Hibernate配置文件的两个配置项
hbm2ddl.auto:该属性可帮助程序员实现正向工程,即由java代码生成数据库脚本,进而生成具体的表结构。取值create、update、create-drop、validate等
- create:会根据
xx.hbm.xml
文件来生成数据表,但是每次运行都会删除上一次的表,重新生成表,哪怕二次没有任何改变 - create-drop:会根据
xx.hbm.xml
文件生成表,但是SessionFactory一关闭,表就自动删除 - update:最常用的属性值,也会根据
xx.hbm.xml
文件生成表,但若xx.hbm.xml
文件和数据库中对应的数据表的表结构不同,Hiberante将更新数据表结构,但不会删除已有的行和列 - validate :会和数据库中的表进行比较,若
xx.hbm.xml
文件中的列在数据表中不存在,则抛出异常
format_sql:是否将SQL转化为格式良好的SQL,取值true | false
3、Hibernate里的Session接口
3.1、搭建实验环境
1、新建一个数据库,并添加两条数据
USE test;
# 建表
CREATE TABLE `user`(
id int(20) PRIMARY KEY NOT NULL,
username varchar(30) DEFAULT NULL,
password varchar(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
# 添加两条数据
INSERT INTO test.USER(id,username,password) VALUE(1,"alex","123");
INSERT INTO test.USER(id,username,password) VALUE(2,"howie","123");
2、使用maven新建一个Java项目,并导入相关依赖,解决资源导出等问题。
添加依赖
<!--mysql-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
<!--hibernate-core-->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.10.Final</version>
</dependency>
解决资源导出问题
<build>
<finalName>ssm-build</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3、编写实体类与实体类对应的映射文件
User.java
/**
* @description: User实体类
* @author: laizhenghua
* @date: 2020/12/4 13:18
*/
public class User {
private Integer id;
private String username;
private String password;
public User(){} // 提供一个无参的构造器:使用Hibernate可以使用newlnstance()来实例化实体类
...
// getter and setter
// toString 方便测试
}
为了包结构简洁我们把映射文件放在src/main/resources/mapper/User.hbm.xml
User.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">
<!--Mapper映射文件-->
<hibernate-mapping>
<!-- 建立类与表的映射 -->
<class name="com.howie.pojo.User" table="user">
<!-- 建立类中的属性与表中的主键相对应 -->
<id name="id" type="java.lang.Integer">
<column name="id"/>
<!-- 主键的生成策略,后面会讲,native:使用数据库本地生成策略 -->
<generator class="native"/>
</id>
<!-- 建立类中的普通属性和表中的字段相对应 -->
<property name="username" type="java.lang.String">
<column name="username"/>
</property>
<property name="password" type="java.lang.String">
<column name="password"/>
</property>
</class>
</hibernate-mapping>
4、编写Hibernate的核心配置文件
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>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true&characterEncoding=utf8</property>
<property name="connection.username">root</property>
<property name="connection.password">101323</property>
<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略,update最常用 -->
<property name="hbm2ddl.auto">update</property>
<!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
<mapping resource="mapper/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
5、为了方便测试,我们把加载配置文件,获取Session对象、Transaction对象等的代码,封装为一个工具类。
HibernateUtil.java
package com.howie.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
/**
* @description: Hibernate的工具类,用于获取Session对象与Transaction对象
* @author: laizhenghua
* @date: 2020/12/4 17:08
*/
public class HibernateUtil {
/* 实际开发中,为了代码安全Session对象一般不能作为成员变量 */
private static SessionFactory sessionFactory = null;
private static Session session = null;
/**
* @description: 用于获取Session对象
* @author: laizhenghua
* @date: 2020/12/4 17:13
* @param: null
* @return: org.hibernate.Session
*/
public static Session getSession(){
// 1、加载核心配置文件
Configuration configuration = new Configuration().configure();
// 2、创建SessionFactory对象,类似JDBC中的链接池
sessionFactory = configuration.buildSessionFactory();
// 3、返回Session对象
session = sessionFactory.openSession();
return session;
}
/**
* @description: 获取Transaction对象(操作事务的对象)
* @author: laizhenghua
* @date: 2020/12/4 17:26
* @param: session
* @return: org.hibernate.Transaction
*/
public static Transaction getTransaction(Session session){
return session.beginTransaction();
}
/**
* @description: 关闭Session对象
* @author: laizhenghua
* @date: 2020/12/4 17:28
* @param:
* @return: void
*/
public static void closeSession(){
if(session != null){
session.close();
}
if(sessionFactory != null){
sessionFactory.close();
}
}
}
6、新建一个测试类HibernateTest.java,进行添加测试
@Test
public void test(){
// 1、获取Session对象
Session session = HibernateUtil.getSession();
// 2、获取Transaction对象
Transaction transaction = HibernateUtil.getTransaction(session);
// 3、添加一条数据
User user = new User();
user.setId(3);
user.setUsername("无情哈拉少");
user.setPassword("123");
String sql = "insert into test.user(id,username,password) value(?,?,?)";
session.createSQLQuery(sql).setParameter(1,user.getId()).
setParameter(2,user.getUsername()).setParameter(3,user.getPassword()).executeUpdate();
// 4、提交事务
transaction.commit();
// 5、关闭资源
HibernateUtil.closeSession();
}
查看结果!
OK,我们的环境已搭建完毕!
3.2、Session接口概述
1、Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口,它提供了基本的保存,更新,删除和加载Java对象的方法。
2、Session具有一个缓存(自带一级缓存默认开启),位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。Session能够在某些时间点,按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库,这一过程被称为刷新缓存(flush)。
3、站在持久化的角度,Hibernate 把对象分为4种状态:持久化状态,临时状态,游离状态,删除状态。Session的特定方法能使对象从一个状态转换到另一个状态。
3.3、Session缓存
1、在 Session 接口的实现中包含一系列的Java集合,这些Java集合构成了Session 缓存,只要Session 实例没有结束生命周期,且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。
2、Session缓存可减少Hibernate应用程序访问数据库的频率。
3、一级缓存也叫本地缓存(Session级别的缓存),与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库
/* 测试一级缓存 */
@Test
public void test1(){
// 1、获取Session对象
Session session = HibernateUtil.getSession();
// 2、获取数据
User user1 = session.get(User.class, 2);
System.out.println(user1);
// 这里 user2 不在从从数据里读取的,而是从缓存中读取,SQL也只执行了一次
User user2 = session.get(User.class,2);
System.out.println(user2);
System.out.println(user1 == user2);
// 3、关闭资源
HibernateUtil.closeSession();
}
查看输出的日志!
小结:一级缓存默认是开启的,只在一次Session中有效,也就是拿到连接到关闭连接这个区间段 !
4、缓存失效情况:
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
-
查询不同的Mapper.xml
-
手动清理缓存!
@Test
public void test1(){
// 1、获取Session对象
Session session = HibernateUtil.getSession();
// 2、获取数据
User user1 = session.get(User.class, 2);
System.out.println(user1);
session.clear(); // 手动清理缓存
/* 此时SQL语句会执行两次 */
User user2 = session.get(User.class,2);
System.out.println(user2);
System.out.println(user1 == user2); // false
// 3、关闭资源
HibernateUtil.closeSession();
}
3.4、flush缓存
1、操作Session中的缓存
2、flush:Session按照缓存中对象的属性变化来同步更新数据库。默认情况下Session在以下时间点刷新缓存:
- 显式调用Session的flush()方法
- 当应用程序调用Transaction的commit()方法的时,该方法先flush,然后在向数据库提交事务
- 当应用程序执行一些查询(HQL,Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先flush缓存。以保证查询结果能够反映持久化对象的最新状态。
flush缓存的例外情况:
- 如果对象使用native生成器生成OID,那么当调用Session的
save()
方法保存对象时,会立即执行向数据库插入该实体的insert语句。
3、commit()
和flush()
方法的区别:flush 执行一系列sql语句,但不提交事务,commit方法先调用flush()方法,然后提交事务。意味着提交事务意味着对数据库操作永久保存下来。
/**
flush: 使数据表中的记录和 Session 缓存中的对象的状态保持一致. 为了保持一致, 则可能会发送对应的 SQL 语句.
1. 在 Transaction 的 commit() 方法中: 先调用 session 的 flush 方法, 再提交事务
2. flush() 方法会可能会发送 SQL 语句, 但不会提交事务.
3. 注意: 在未提交事务或显式的调用 session.flush() 方法之前, 也有可能会进行 flush() 操作.
1) 执行 HQL 或 QBC 查询, 会先进行 flush() 操作, 以得到数据表的最新的记录
2) 若记录的 ID 是由底层数据库使用自增的方式生成的, 则在调用 save() 方法时, 就会立即发送 INSERT 语句. 因为 save 方法后, 必须保证对象的 ID 是存在的!
*/
@Test
public void testSessionFlush2(){
News news = new News("Java", "SUN", new Date());
session.save(news);
}
@Test
public void testSessionFlush(){
News news = (News) session.get(News.class, 1);
news.setAuthor("Oracle");
// session.flush();
// System.out.println("flush");
News news2 = (News) session.createCriteria(News.class).uniqueResult();
System.out.println(news2);
}
4、reresh()
方法
@Test
public void rereshTest(){
/*User user = new User();
user.setId(4);
user.setUsername("关羽");
user.setPassword("123");*/
Session session = HibernateUtil.getSession();
User user = session.get(User.class, 3);
System.out.println(user);
session.refresh(user);
/*
refresh(): 会强制发送 SELECT 语句, 以使 Session 缓存中对象的状态和数据表中对应的记录保持一致!
*/
System.out.println(user);
HibernateUtil.closeSession();
}
3.5、Session接口的关系图
3.6、Hibernate中设置隔离级别
1、MySQL支持4中事务隔离级别,MySQL默认的事务隔离级别为:REPEATABLE READ
2、REPEATABLE READ(可重复读)
:确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事物对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在。
3、回顾MySQL中设置隔离级别
# 1、每启动一个mysql程序,就会获得一个单独的数据库连接。
# 2、每个数据库连接都有一个全局变量@@txisolation,表示当前的事务隔离级别。
# 3、MySQL默认的隔离级别为RepeatableRead
# 查看当前的隔离级别:
SELECT @@tx...isalation;
# 设置当前mySQL连接的隔离级别:
set transaction isolation level read committed;
# 设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
4、在Hibernate中设置隔离级别
JDBC数据库连接使用数据库系统默认的隔离级别。在Hibernate的配置文件中可以显式的设置隔离级别。每一个隔离级别都对应一个整数:
READ UNCOMMITED == 1
READ COMMITED == 2
REPEATABLE READ == 3
SERIALIZEABLE == 4
而我们使用Hibernate设置数据库隔离级别时,只需在配置文件中添加一个property
标签即可。
<!--设置Hibernate的事务隔离级别-->
<property name="connection.isolation">2</property>
hibernate.connection.isolation
属性来设置事务的隔离级别。
4、持久化对象的状态
4.1、持久化对象的4种状态
1、站在持久化的角度,Hibernate 把对象分为4种状态:
- 持久化状态
- 临时状态
- 游离状态
- 删除状态
Session的特定方法能使对象从一个状态转换到另一个状态。
2、临时对象(Transient)
- 在使用代理主键的情况下,OID通常为null。
- 不处于 Session 的缓存中。
- 在数据库中没有对应的记录。
3、持久化对象(也叫"托管",Persist)
- OID不为null。
- 位于Session缓存中。
- 若在数据库中已经有和其对应的记录,持久化对象和数据库中的相关记录对应。
- Session在 flush缓存时,会根据持久化对象的属性变化,来同步更新数据库。
- 在同一个Session实例的缓存中,数据库表中的每条记录只对应唯一的持久化对象。
4、删除对象(Removed)
- 在数据库中没有其OID对应的记录
- 不在属于 Session 缓存中
- 一般情况下应用程序不该在使用被删除的对象。
5、游离对象(也叫"脱管") (Detached)
- OID不为null。
- 不再处于Session缓存中。
- 一般情况需下,游离对象是由持久化对象转变过来的,因此在数据库中可能还存在与它对应的记录。
- 也可理解为,请假,虽然暂时脱离了,公司的管理,但是你还是公司的人。
4.2、持久化对象状态转换图
4.3、Session的save()方法
首先我们知道 Session里的save()
方法可以把持久化对象从临时状态变为持久化状态!并把对象保存到数据库里。
@Test
public void testSave(){
/*
save()方法
1、使临时对象转变为持久化对象
2、在flush缓存时,会发送一条insert语句
*/
Session session = HibernateUtil.getSession();
User user = new User();
user.setUsername("无情哈拉少");
user.setPassword("123");
System.out.println("临时状态:" + user);
session.save(user); // 保存,注意我们在数据库里设置了 id 字段自增
System.out.println("持久化状态:" + user);
HibernateUtil.closeSession();
}
我们再来看输出的日志!
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
临时状态:User{id=null, username='无情哈拉少', password='123'}
Hibernate:
insert
into
user
(username, password)
values
(?, ?)
持久化状态:User{id=6, username='无情哈拉少', password='123'}
十二月 09, 2020 2:13:23 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
我们发现在Hibernate5版本里save()
方法自动提交了事务,并且发送了一条insert语句!!把数据保存到数据里,把对象从临时对象转变为持久化状态(再次打印user时,id已不为null)。
4.4、Session的persist()方法
persist()
与save()
方法类似,也会执行INSERT语句
,也是把持久化对象从临时状态转变为持久化状态,但是也有一些区别,在Hibernate5里persist()
方法不会自动提交事务! 在调用 persist 方法之前, 若对象已经有 id 了, 则不会执行 INSERT, 而抛出异常!!!
@Test
public void persistTest(){
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session); // 获取操作事务的对象
User user = new User();
user.setUsername("persist");
user.setPassword("123");
System.out.println("临时状态:" + user);
session.persist(user); // 保存对象
System.out.println("持久化状态:" + user);
transaction.commit(); // 提交事务
HibernateUtil.closeSession();
}
/* 输出的部分日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
临时状态:User{id=null, username='persist', password='123'}
Hibernate:
insert
into
user
(username, password)
values
(?, ?)
持久化状态:User{id=7, username='persist', password='123'}
十二月 09, 2020 2:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/
4.5、Session的get()&load()方法
Session中load()
方法和get()
方法的功能类似,也是查询出指定的一条记录,都会发送查询语句,但是get()
和load()
也有一定的区别!!
@Test
public void loadTest(){
Session session = HibernateUtil.getSession();
User user1 = session.load(User.class, 1); // load延迟加载,节省内存(使用就加载,不使用则不加载)
System.out.println(user1.getClass().getName()); // 代理对象:com.howie.pojo.User$HibernateProxy$3aJET1Gx
session.close();
// 若打印对象前,把Session关闭,会抛出一个懒加载异常
System.out.println(user1);
/*User user2 = session.get(User.class, 1);
System.out.println(user1 == user2); // true*/
/*
get() vs load()
1、执行get方法:会立即加载对象,而执行load方法若不使用对象的属性,则不会立即执行查询操作,而返回一个代理对象。
2、get是立即检索,而load是延迟检索
3、若数据库表中没有对应的记录,且 Session 也没有被关闭,同时需要使用对象时。get在返回null。load抛出异常
load方法若不使用该对象也不会抛出异常。
4、load方法可能会抛出懒加载异常:org.hibernate.LazyInitializationException。
在需要初始化代理对象之前,已经关闭Session,此时就会抛出懒加载异常
*/
}
@Test
public void testGet(){
Session session = HibernateUtil.getSession();
User user = session.get(User.class, 1);
session.close();
// 即使Session已经关闭了,但是执行get方法时,对象已经加载了,所以可以打印出对象
System.out.println(user);
}
4.6、Session的update()方法
1、Session的update()
方法使一个游离对象转变为持久化对象,并且计划执行一条update语句。
2、若希望 Session仅当修改了User对象的属性时,才执行update()语句,可以把映射文件中元素的select-before-update设为true,该属性的默认值为false。
3、update()方法关联一个游离对象时,如果在Session的缓存中已经存在相同OID的持久化对象,会抛出异常。
4、当update()方法关联一个游离对象时,如果在数据库中不存在相应的记录,也会抛出异常。
/*
update()方法:
1、前面我们知道,若持久化对象与查询出来的数据表的记录不一样,提交事务后,Hibernate会自动发送一条更新语句,更新最新的记录
2、若更新一个持久化对象,不需要显示调用 update方法,因为提交事务时,会先执行 Session 的flush() 方法。
3、更新一个游离对象,需要显示调用 Session 的 update()方法,可以把一个游离对象变为持久化对象
注意点:
1、无论要更新的游离对象和数据表的记录是否一致,都会发送update语句
如何才能让 update 方法不盲目的发送update语句?
在 xxx.hbm.xml 映射文件中的 class 节点设置一个 select-before-update=true (默认为false,通常不用设置此属性)
2、若数据表中没有对应的记录,但还调用了 update 方法会抛出异常。
3、update()方法关联一个游离对象时,如果在Session的缓存中已经存在相同OID的持久化对象,会抛出异常。
Session缓存中不能有两个 OID 相同的对象
*/
@Test
public void updateTest(){
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session);
User user = session.get(User.class, 1);
transaction.commit();
HibernateUtil.closeSession();
session = HibernateUtil.getSession();
transaction = HibernateUtil.getTransaction(session);
user.setUsername("alex"); // 此时user对象就变为游离状态,需要显示调用 update 方法
session.update(user);
transaction.commit();
System.out.println(user);
}
4.7、Session的saveOrUpate()方法
1、Session的saveQrUpdate()
方法同时包含了save()与update()方法的功能。
2、判断对象为临时对象的标准
- Java对象的 OID 为 null
- 映射文件中为设置了
unsaved-value属性
,并且Java对象的OID取值与这个unsaved-value属性值匹配
/*
注意点:
1、若 OID 不为null,但数据表中还没有和其对应的记录,会抛出异常
2、了解:0ID 值等于id的unsaved-value属性值的对象,也被认为是一个游离对象
*/
@Test
public void saveOrUpdateTest(){
User user = new User();
user.setUsername("哈士奇");
user.setPassword("123");
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session);
session.saveOrUpdate(user); // 执行save()方法,发送 insert 语句
// user.setId(1); 执行update()方法,发送update语句
transaction.commit();
HibernateUtil.closeSession();
}
4.8、Session的delete()方法
1、Session的delete()
方法既可以删除一个游离对象,也可以删除一个持久化对象。
2、Session的delete()
方法处理过程
- 计划执行一条delete语句。
- 把对象从Session缓存中删除,该对象进入删除状态。
/*
delete()方法:
1、执行删除操作,只要OID和数据表中有一条记录对应,就会准备执行delete操作!
2、若OID咋数据表中没有对应的记录则抛出异常
*/
@Test
public void deleteTest(){
User user = new User();
user.setId(8);
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session);
session.delete(user); // 删除游离对象
transaction.commit();
HibernateUtil.closeSession();
}
/* 输出的日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate:
delete
from
user
where
id=?
十二月 11, 2020 11:52:20 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/
3、Hibernate 的cfg.xml配置文件中有一个hibernate.use_identifier_rollback属性
,其默认值为false,若把它设为true,将改变delete()
方法的运行行为:delete()方法会把持久化对象或游离对象的OID设置为null,使它们变为临时对象。
修改hibernate.cfg.xml
配置文件
<!--删除对象后,使其OID置为null-->
<property name="hibernate.use_identifier_rollback">true</property>
测试
@Test
public void deleteTest(){
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session);
User user = session.get(User.class, 6);
session.delete(user);
System.out.println(user);
transaction.commit();
HibernateUtil.closeSession();
}
/* 输出的日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate:
select
user0_.id as id1_0_0_,
user0_.username as username2_0_0_,
user0_.password as password3_0_0_
from
user user0_
where
user0_.id=?
User{id=null, username='无情哈拉少', password='123'}
Hibernate:
delete
from
user
where
id=?
十二月 11, 2020 11:59:07 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/
4.9、Session的evict()方法
1、Session中的evict()
方法,从session缓存中把指定的持久化对象移除。
2、测试
@Test
public void evictTest(){
Session session = HibernateUtil.getSession();
Transaction transaction = HibernateUtil.getTransaction(session);
User user = session.get(User.class, 2);
user.setUsername("哈士奇");
session.evict(user); // 从session缓存中移除user对象,数据表中并没有得到更新
transaction.commit();
HibernateUtil.closeSession();
}
5、Hibernate中的存储过程
5.1、通过Hibernate调用存储过程
1、Hibernate并没有提供操作存储过程的API,在Hibernate里我们只能操作原生jdbc的Connection来操作存储过程等。
2、Session的doWork(Work)
方法用于执行Work 对象指定的操作,即调用Work 对象的execute()
方法。Session会把当前使用的数据库连接传递给execute()方法。
@Test
public void doWorkTest(){
Session session = HibernateUtil.getSession();
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
System.out.println(connection); // com.mysql.cj.jdbc.ConnectionImpl@5327a06e
// 在此我们可以操作原生 jdbc的Connection,从而操作存储过程
}
});
}
5.2、Hibernate与触发器协同工作
1、Hibernate 与数据库中的触发器协同工作时,会造成两类问题:
- 触发器使Session的缓存中的持久化对象与数据库中对应的数据不一致:触发
器运行在数据库中,它执行的操作对Session是透明的。 - Session 的update()方法盲目地激发触发器:无论游离对象的属性是否发生变
化,都会执行update语句,而update语句会激发数据库中相应的触发器。
2、解决方案
- 在执行完Session的相关操作后,立即调用Session 的flush()和refresh()方法,迫使Session的缓存与数据库同步(refresh()方法重新从数据库中加载对象)
- 在映射文件的的元素中设置
select-before-update属性
:当Session的update()
或saveOrUpdate()
方法更新一个游离对象时,会先执行Select语句,获得当前游离对象在数据库中的最新数据,只有在不一致的情况下才会执行update语句。
6、Hibernate配置文件详解
1、Hibernate配置文件主要用于配置数据库连接和Hibernate运行时所需的各种属性。每个Hibernate配置文件对应一个Configuration对象。
2、Hibernate配置文件可以有两种格式:
hibernate.properties
hibernate.cfg.xml
推荐使用
3、下面就介绍hibernate.cfg.xml
常用的属性。
6.1、JDBC连接属性
1、常用属性名
connection.url
:数据库URL
connection.username
:数据库用户名
connection.password
:数据库用户密码
connection.driver_class
:数据库JDBC驱动
dialect
:配置数据库的方言,根据底层的数据库不同产生不同的sgl语句,Hibernate 会针对数据库的特性在访问时进行优化。
2、配置实例
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true&characterEncoding=utf8</property>
<property name="connection.username">root</property>
<property name="connection.password">xxx</property>
<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
6.2、切换数据库连接池
1、Hibernate自带的连接池算法相当不成熟.。它只是为了让你快些上手,并不适合用于产品系统或性能测试中。 出于最佳性能和稳定性考虑你应该使用第三方的连接池。只需要用特定连接池的设置替hibernate.connection.pool_size
即可。这将关闭Hibernate自带的连接池。例如,我们可以使用C3P0/Druid等,这里我们以Druid为例!
2、关于更多数据库连接池的学习,可在网上自行学习!
3、使用步骤:
- 引入maven依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
- 加入配置(这里只列出常用配置)
<?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>
<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!--删除对象后,使其OID置为null-->
<property name="hibernate.use_identifier_rollback">true</property>
<!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
<!-- 配合Druid数据源 -->
<property name="connection.provider_class">com.alibaba.druid.support.hibernate.DruidConnectionProvider</property>
<property name="url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true&characterEncoding=utf8</property>
<property name="username">root</property>
<property name="password">101323</property>
<property name="driverClassName">com.mysql.cj.jdbc.Driver</property>
<property name="filters">stat</property>
<property name="maxActive">20</property>
<property name="initialSize">1</property>
<property name="maxWait">60000</property>
<property name="minIdle">1</property>
<property name="timeBetweenEvictionRunsMillis">60000</property>
<property name="minEvictableIdleTimeMillis">300000</property>
<property name="testWhileIdle">true</property>
<property name="testOnBorrow">false</property>
<property name="testOnReturn">false</property>
<property name="poolPreparedStatements">true</property>
<property name="maxOpenPreparedStatements">20</property>
<property name="asyncInit">true</property>
<mapping resource="mapper/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
更多配置可自行查询!
- 我们来编写测试方法,来查看数据源!
@Test
public void druidTest(){
Session session = HibernateUtil.getSession();
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
System.out.println("数据源:" + connection);
}
});
}
- 输出结果
6.3、其他配置
1、show_sql
:是否将运行期生成的SQL输出到日志以供调试。取值true | false
2、format_sql
:是否将SQL转化为格式良好的SQL。取值true | false
3、hbm2ddl.auto
:在启动和停止时自动地创建,更新或删除数据库模式。取值create | update | create-drop l validate
<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!--删除对象后,使其OID置为null-->
<property name="hibernate.use_identifier_rollback">true</property>
<!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
<mapping resource="mapper/User.hbm.xml"/>
4、hibernate.jdbc.fetch_size
:实质是调用 Statement.setFetchSize() 方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。
/*
例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会1次性把1万条取出来的,而只会取出fetchSize, 条
数,当结果集遍历完了这些记录以后,再去数据库取 fetchSize 条数据。因此大大节省了无谓的内存消耗。
FetchSize设的越大,读数据库的次数越少,速度越快;FetchSize越小,读数据库的次数越多,速度越慢。Oracle
数据库的JDBC驱动默认的FetchSize = 10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍
之多,当fetch Size=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的
数据库都支持Fetch Size特性,例如MlySQL就不支持.
*/
5、hibernate.jdbc.batch_size
:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize越大,批量操作时向数据库发送sql的次数越少,速度就越快。
/*
测试结果是当Batch Size = 0 的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,
Batch Size = 50的时候,删除仅仅需要5秒! Oracle数据库batchSize=30的时候比较合适。
*/
7、对象关系映射文件
7.1、映射文件说明
1、我们知道,在使用Hibernate作为持久层框架时,都需要在hibernate.cfg.xml
配置文件里指明要加载的映射文件,有了映射文件,hibernate才能完成对象与数据表的关系映射。
2、POJO类和关系数据库之间的映射可以用一个XML文档来定义。
3、通过POJO类的数据库映射文件,Hibernate可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系。
4、在运行时Hibernate将根据这个映射文件来生成各种SQL语句。映射文件的扩展名为实体类名.hbm.xml
5、官方中文文档:https://hibernate.net.cn/column/5.html
7.2、hibernate-mapping
1、类层次(class标签)
主键:id
- 被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性,为每一个实例包含唯一的标识。
<id>
元素定义了该属性到数据库表主键字段的映射。
基本类型:property
实体引用类:many-to-one | one-to-one
集合:set | list | map | array
- one-to-many
- many-to-many
子类:subclass | joined-subclass
其它:component | any 等
2、查询语句:query (用来放置查询语句,便于对数据库查询的统一管理和优化)
3、每个Hibernate-mapping中可以同时定义多个类。但更推荐为每个类都创建一个单独的映射文件。
4、hibernate-mapping可配置属性
例如package属性的配置:
我们指明包路径后,建立类与表的映射关系时,只需写明类名即可。
<!--Mapper映射文件-->
<hibernate-mapping package="com.howie.pojo">
<!-- 建立类与表的映射 -->
<class name="User" table="user" select-before-update="true">
....
7.3、映射对象标识符
1、Hibernate使用对象标识符(OID)来建立内存中的对象和数据库表中记录的对应关系.对象的OID和数据表的主键对应,Hibernate通过标识符生成器来为主键赋值。
2、Hibernate 推荐在数据表中使用代理主键,即不具备业务含义的字段,代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。
3、在对象-关系映射文件中,<id>
元素用来设置对象标识符。<generator>
子元素用来设定标识符生成器。
4、Hibernate提供了标识符生成器接口:LdentifierGenerator,并提供了各种内置实现