Hibernate
一、准备 Hibernate 环境
导入 Hibernate 必须的 jar 包:
加入数据库驱动的 jar 包:
二、创建持久化 Java 类
Hibernate 不要求持久化类继承任何父类或实现接口,这可以保证代码不被污染。这就是Hibernate被称为低侵入式设计的原因
三. 创建配置文件
1.创建对象-关系映射文件
- Hibernate 采用 XML 格式的文件来指定对象和关系数据之间的映射. 在运行时 Hibernate 将根据这个映射文件来生成各种 SQL 语句
- 映射文件的扩展名为 .hbm.xml
(1).主键生成策略generator:
(2).Java 类型, Hibernate 映射类型及 SQL 类型之间的对应关系 :
(3).Java 时间和日期类型的 Hibernate 映射:
- 在 Java 中, 代表时间和日期的类型包括: java.util.Date 和 java.util.Calendar. 此外, 在 JDBC API 中还提供了 3 个扩展了 java.util.Date 类的子类: java.sql.Date, java.sql.Time 和 java.sql.Timestamp, 这三个类分别和标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP 类型对应
- 在标准 SQL 中, DATE 类型表示日期, TIME 类型表示时间, TIMESTAMP 类型表示时间戳, 同时包含日期和时间信息.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2018-3-12 19:02:02 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
<!-- 设置 dynamic-update="true" 可以实现动态生成 update 语句,只更新修改的字段。 -->
<class name="cn.edu.pzhu.cg.helloword.New" table="NEW" dynamic-update="true">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<!-- 指定主键的生成方式,native :使用数据库本地方式 -->
<generator class="native" />
</id>
<property name="title" type="java.lang.String">
<column name="TITLE" />
</property>
<property name="author" type="java.lang.String">
<column name="AUTHOR" />
</property>
<property name="date" type="java.sql.Date">
<column name="DATE" />
</property>
</class>
</hibernate-mapping>
2.创建 Hibernate 配置文件
- 默认名称:hibernate.cfg.xml
- JDBC 连接属性
- connection.url:数据库URL
- connection.username:数据库用户名
- connection.password:数据库用户密码
- connection.driver_class:数据库JDBC驱动
- dialect:配置数据库的方言,根据底层的数据库不同产生不同的 sql 语句,Hibernate 会针对数据库的特性在访问时进行优化:
- C3P0 数据库连接池属性
- hibernate.c3p0.max_size: 数据库连接池的最大连接数
- hibernate.c3p0.min_size: 数据库连接池的最小连接数
- hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
- hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
- hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
- hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
hibernate.jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定 JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数。
-例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会 1 次性把1万条取出来的,而只会取出 fetchSize 条数,当结果集遍历完了这些记录以后,再去数据库取 fetchSize 条数据。因此大大节省了无谓的内存消耗。Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。Oracle数据库的JDBC驱动默认的Fetch Size = 10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当 fetchSize=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。
- 测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!Oracle数据库 batchSize=30 的时候比较合适。
3.Hibernate 配置文件的两个配置项:
<?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.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.connection.url">jdbc:mysql:///test2?useUnicode=true&characterEncoding=UTF-8</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 配置hibernate的基本信息 -->
<!-- hibernate 所使用的数据库的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- 执行操作时,是否在控制台打印 SQL 语句 -->
<property name="show_sql">true</property>
<!-- 是否对 SQL 进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!-- 设置 Hibernate 的事务隔离级别 (读-提交)-->
<property name="connection.isolation">2</property>
<!-- 执行 delete 操作后,将其 OID 置为 null -->
<property name="use_identifier_rollback">true</property>
<!-- 配置 C3P0 -->
<property name="c3p0.max_size">10</property>
<property name="c3p0.min_size">5</property>
<!-- 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接 -->
<property name="c3p0.acquire_increment">2</property>
<!-- 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时 -->
<property name="c3p0.idle_test_period">2000</property>
<!-- 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁 -->
<property name="c3p0.timeout">2000</property>
<!-- 缓存 Statement 对象的数量 -->
<property name="c3p0.max_statements">10</property>
<!-- 设定对数据库进行批量删除、更新、插入的时候的批次大小 -->
<property name="jdbc.batch_size">30</property>
<!-- 指定关联的 .hbm.xml 文件 -->
<mapping resource="cn/edu/pzhu/cg/helloword/New.hbm.xml"/>
</session-factory>
</hibernate-configuration>
四、Hibernate 编码基本步骤
1.编写持久化类: POJO + 映射文件
2.获取 SessionFactory 对象
- 针对单个数据库映射关系经过编译后的内存镜像,是线程安全的。
- SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息
- SessionFactory是生成Session的工厂
- 构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
- 创建 SessionFactory 的步骤:
SessionFactory sessionFactory = null;
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
3.获取 Session
Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,所有持久化对象必须在 session 的管理下才可以进行持久化操作。此对象的生命周期很短。Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久层操作的数据都缓存在 session 对象处。相当于 JDBC 中的 Connection。
持久化类与 Session 关联起来后就具有了持久化的能力
1.Session 类的方法:
以下单元测试方法运行在如下代码基础上:
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init(){
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory(){
transaction.commit();
session.close();
sessionFactory.close();
}
(1).开启事务: beginTransaction().
(2).save方法:
/**
* save() 方法:
* 1.使一个临时对象变为一个持久化对象。
* 2.为对象分配 ID。
* 3.在 flush 缓存时,会发送一条 INSERT 语句。
* 4.在 save 方法之前的 id 是无效的。
* 5.持久化对象的 id 是不能被修改的。
*/
@Test
public void testSave(){
New new1 = new New();
new1.setTitle("Java 基础");
new1.setAuthor("BB");
new1.setDate(new Date(new java.util.Date().getTime()));
new1.setId(15); //没有作用
System.out.println(new1);
session.save(new1);
//new1.setId(111); //修改不了
System.out.println(new1);
}
(3).persist()
/*
* Persist():也会执行一个 INSERT 语句。
*
* 与 save() 区别:在 调用 persist() 方法前,如果已经有了 id 那么将不会执行,并且抛出异常。
*/
@Test
public void testPersist(){
New new1 = new New();
new1.setTitle("J2ee");
new1.setAuthor("aa");
new1.setDate(new Date(new java.util.Date().getTime()));
new1.setId(15); //没有作用
System.out.println(new1);
session.persist(new1);
}
(4).flush()
/**
* flush:使数据库表中的记录和 Session 缓存中的对象的状态保持一致,为了保持一致,则可能会发送对应的 SQL 语句。
* 1.在 Transaction 的 commit() 方法中:先调用 session 的 flush 方法,在提交事务。
* 2.flush 方法可能会发送 SQL 语句,但不会提交事务。
* 注意:在未提交事务或显示的调用session.flush() 方法之前,也可能会进行 flush 操作。
* 1).执行 HQL 或 QBC 查询,会先进行 flush() 操作,以得到最新的数据。
* 2).若记录的 ID 是由数据库底层自增产生的,则调用 save() 方法时,就会立即发送 INSERT 语句,因为 save 方法后
* 必须保证对象的 ID 是存在的。
*/
@Test
public void testSessionFlush(){
New new1 = session.get(New.class, 1);
new1.setAuthor("ZhangChao");
//会将数据库中对应的属性值改变。
//System.out.println(new1);
}
(5).flush()
/**
* clear():清理缓存
*/
@Test
public void testClear(){
New new1 = session.get(New.class, 1);
session.clear();
New new2 = session.get(New.class, 1);
//打印两条 SELECT 语句
}
(6).load() 和 get()
/*
* Load VS Get
* 1.执行 get 方法会立即加载该对象,使用 load 方法,若不使用该对象,则不会立即执行查询操作,而返回一个代理对象
* get 是立即检索, load 是延迟检索。
*
* 2.load 方法可能会抛出 LazyInitializationException 异常:在需要初始化代理对象之前就已经关闭了 session。
*
* 3.若数据表中没有对应的记录,session 也没有关闭,同时需要使用对象时:
* get 方法返回 null;
* load 方法:若不使用该对象的任何属性,没问题! 若需要初始化时,会抛出异常。
*
*/
@Test
public void testLoad(){
New new1 = session.load(New.class, 1);
session.close();
/*会抛出 LazyInitializationException 异常,因为在未初始化 new1 之前就关闭了 session,
导致在初始化时,找不到资源。
*/
System.out.println(new1);
}
@Test
public void TestGet(){
New new1 = session.get(New.class, 1);
System.out.println(new1);
}
(7).update()
/*
* update:
* 1.若更新一个持久化对象,不需要显示的调用 update 方法,因为在调用 transaction 的 commit 方法时
* 会先执行 session 的 flush 方法。
* 2.更新一个游离对象,需要显示的调用 session 的 update 方法。可以把一个游离对象转为持久化对象。
* 3.同一个 session 中不能存在两个 OID 相同的对象。
*/
@Test
public void testUpdate(){
New new1 = session.get(New.class, 1);
transaction.commit();
session = sessionFactory.openSession();
session.beginTransaction();
new1.setAuthor("CG");
System.out.println(new1);
session.update(new1);
}
(8).saveOrUpdate()
/*
* 注意:如果 OID 不为空,但数据库中没有对应的记录,执行 update 操作会抛出异常。
*/
@Test
public void testSaveOrUpdate(){
New new1 = new New("Java", "bbb",new Date(new java.util.Date().getTime()));
new1.setId(5);
session.saveOrUpdate(new1);
}
(9).delete()
/*
* delete:删除操作,只要 OID 和数据库里面的一条记录对应就准备执行 delete 操作,不管是游离对象还是持久化对象。
* 若 OID 在数据库中没有记录对应,就会抛出异常。
* 可以通过设置 use_identifier_rollback 为 true,让其执行 delete 方法后,将 OID 置为 null。
*/
@Test
public void testDelete(){
// New new1 = new New();
// new1.setId(5); //删除 id = 5 的游离对象
New new1 = session.get(New.class, 4); //删除持久化对象
session.delete(new1);
}
(10).evict()
/*
* evict:把缓存对象从 session 中移除。
*/
@Test
public void testEvict(){
New new1 = session.get(New.class, 1);
New new2 = session.get(New.class, 2);
new1.setAuthor("AAA");
new2.setAuthor("BBB");
session.evict(new2);
}
五、Hibernate 主键生成策略
六、数据库的隔离级别
- 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
- 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
- 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
- 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
- 一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
数据库提供的 4 种事务隔离级别:
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED
Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ
在 MySql 中设置隔离级别:
- 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
- 查看当前的隔离级别: SELECT @@tx_isolation;
- 设置当前 mySQL 连接的隔离级别:
- set transaction isolation level read committed ;
- 设置数据库系统的全局的隔离级别:
- set global transaction isolation level read committed;
在 Hibernate 中设置隔离级别:
- JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
-
- READ UNCOMMITED
-
- READ COMMITED
-
- REPEATABLE READ
-
- SERIALIZEABLE
-
- Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别
七、映射关联关系
1.基于外键映射的一对一
Department 实体类:
public class Department {
private Integer departId;
private String departName;
private Manager manager;
public Integer getDepartId() {
return departId;
}
public void setDepartId(Integer departId) {
this.departId = departId;
}
public String getDepartName() {
return departName;
}
public void setDepartName(String departName) {
this.departName = departName;
}
public Manager getManager() {
return manager;
}
public void setManager(Manager manager) {
this.manager = manager;
}
@Override
public String toString() {
return "Department [departId=" + departId + ", departName=" + departName + "]";
}
}
Manager 实体类:
public class Manager {
private Integer managerId;
private String managerName;
private Department department;
public Integer getManagerId() {
return managerId;
}
public void setManagerId(Integer managerId) {
this.managerId = managerId;
}
public String getManagerName() {
return managerName;
}
public void setManagerName(String managerName) {
this.managerName = managerName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
@Override
public String toString() {
return "Manager [managerId=" + managerId + ", managerName=" + managerName + "]";
}
}
Department.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2018-3-17 10:27:58 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="cn.edu.pzhu.cg.one_to_one">
<class name="Department" table="DEPARTMENTS">
<id name="departId" type="java.lang.Integer">
<column name="DEPART_ID" />
<generator class="native" />
</id>
<property name="departName" type="java.lang.String">
<column name="DEPART_NAME" />
</property>
<!-- 通过外键实现 1-1 关联关系 -->
<!-- 使用 many-to-one 配置 1 对 1 映射 -->
<many-to-one name="manager" class="Manager" column="MANAGER_ID" unique="true"></many-to-one>
</class>
</hibernate-mapping>
Manager.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.edu.pzhu.cg.one_to_one">
<class name="Manager" table="MANAGERS">
<id name="managerId" type="java.lang.Integer">
<column name="MANAGER_ID" />
<generator class="native" />
</id>
<property name="managerName" type="java.lang.String">
<column name="MANAGER_NAME" />
</property>
<!-- 映射 1-1 的关联关系;在对应的数据表中已经有了外键,当前持久化类中使用 one-to-one 进行映射 -->
<!-- property-ref:指定使用被关联实体主键以外的字段作为关联字段 -->
<one-to-one name="department" class="Department" property-ref="manager"></one-to-one>
</class>
</hibernate-mapping>
2.基于主键映射的一对一
3.映射多对一
Hibernate 使用 元素来映射 set 类型的属性
Customer实体类:
import java.util.HashSet;
import java.util.Set;
public class Customer {
private int customerId;
private String customerName;
/*
*1. 声明集合类型时,需要使用接口类型,因为 hibernate 在获取集合类型时,返回的是hibernate
* 内置的集合类型,而不是 JaveSE 一个标准的集合实现。
* (不能写成 HashSet<Order> orders = new HashSet<>();)
*2. 可以把集合进行初始化,可以防止空指针发生。
*/
private Set<Order> orders = new HashSet<>();
public Customer() {
super();
}
public int getCustomerId() {
return customerId;
}
public void setCustomerId(int customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Customer [customerId=" + customerId + ", customerName=" + customerName + "]";
}
}
Order 实体类:
public class Order {
private int orderId;
private String orderName;
private Customer customer;
public Order() {
super();
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "Order [orderId=" + orderId + ", orderName=" + orderName + "]";
}
}
Customer.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.edu.pzhu.cg.n_to_1_both">
<class name="Customer" table="CUSTOMERS">
<id name="customerId" type="int">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
<!-- 指定由哪一方来维护关联关系,通常设置 true 由多的一方来维护 -->
<!-- table:set 中的元素对应的记录放在哪一个数据表中,该值需要和多对一的多的那个表的名字一致 -->
<!-- cascade:设置级联操作 ,开发时不建议使用该属性-->
<!-- order-by:在查询时对集合中的元素进行排序,使用的是表中的字段名,而不是持久化类的属性名 -->
<set name="orders" inverse="true" table="ORDERS" cascade="" order-by="ORDER_NAME ASC" >
<!-- 执行多的表中的外键的列名 -->
<key column="CUSTOMER_ID"/>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
Order.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package = "cn.edu.pzhu.cg.n_to_1_both">
<class name="Order" table="ORDERS">
<id name="orderId" type="int">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>
<!-- 映射多对一关联关系 -->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID" />
</class>
</hibernate-mapping>
4.映射多对多
**
1.要实现多对多的映射,必须建立一个中间表,用来存放要映射的两张表的 ID
2.必须有一方放弃维护关联关系。
**
Teacher 实体类:
import java.util.HashSet;
import java.util.Set;
public class Teather {
private Integer id;
private String name;
private Set<Student> students = new HashSet<>();
public Teather() {
super();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teather [id=" + id + ", name=" + name + "]";
}
}
Student 实体类:
import java.util.HashSet;
import java.util.Set;
public class Student {
private Integer id;
private String name;
private Set<Teather> teathers = new HashSet<>();
public Set<Teather> getTeathers() {
return teathers;
}
public void setTeathers(Set<Teather> teathers) {
this.teathers = teathers;
}
public Student() {
super();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
Teacher.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.edu.pzhu.cg.many_to_many">
<class name="Teather" table="TEATHERS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<!-- table : 指定中间表 -->
<!-- 在多对多时,必须要有一方放弃维护关联关系,即必须有一方设置 inverse="true"-->
<set name="students" table="TEATHERS_STUDENTS" inverse="true">
<key>
<column name="T_ID" />
</key>
<!-- 使用 many-to-many 指定多对多的关联关系,column 执行 Set 集合中的持久化类在中间表的外键列 -->
<many-to-many class="Student" column="S_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Student.hbm.xml :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.edu.pzhu.cg.many_to_many">
<class name="Student" table="STUDENTS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<set name="teathers" table="TEATHERS_STUDENTS">
<key column="S_ID"></key>
<many-to-many class="Teather" column="T_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
八、Hibernate 检索方式
1.HQL 检索方式
- HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
- 在查询语句中设定各种查询条件
- 支持投影查询, 即仅检索出对象的部分属性
- 支持分页查询
- 支持连接查询
- 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
- 提供内置聚集函数, 如 sum(), min() 和 max()
- 支持子查询
- 支持动态绑定参数
- 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
/*
*将参数和持久化类绑定
*/
@Test
public void testEntity(){
String hql = "from Employee e where e.department = ?";
Query query = session.createQuery(hql);
Department department = new Department();
department.setDepartId(1);
List<Employee> list = query.setParameter(0, department).list();
System.out.println(list.get(0));
}
/**
* 设置占位符参数的方法统一为setParameter(int position, Object value)
*/
@Test
public void testHql(){
//1.创建 Query 对象
String hql = "from Employee e where e.salary >= ? and name like ?";
Query query = session.createQuery(hql);
//2.绑定参数:支持方法链的编程风格
query.setParameter(0, 7000).setParameter(1, "%张%");
//3.执行查询
List<Employee> list = query.list();
System.out.println(list.get(0).getName());
}
/*分页查询*/
@Test
public void testPageQuery(){
String hql = "from Employee ";
Query query = session.createQuery(hql);
int pageNo = 3;
int pageSize = 4;
List<Employee> list = query.setFirstResult((pageNo - 1) * pageSize)
.setMaxResults(pageSize).list();
for (Employee employee : list) {
System.out.println(employee);
}
}
投影查询:
/*投影查询*/
@Test
public void testFieldQuery(){
String hql = "SELECT e.id,e.name,e.department from Employee e where e.salary >= ?";
Query query = session.createQuery(hql);
//返回值是一个包含各种类型值的数组
List<Object[]> list = query.setParameter(0, 7000).list();
for (Object[] objects : list) {
System.out.println(Arrays.asList(objects));
}
}
/*
* 通过实体类的构造器,将查询的将结果集包装。
*/
@Test
public void testFieldQuery2(){
String hql = "SELECT new Employee(e.id,e.name,e.department) from Employee e where e.salary >= ?";
Query query = session.createQuery(hql);
List<Employee> list = query.setParameter(0, 7000).list();
for (Employee emp : list) {
System.out.println(emp);
}
}
报表查询:
@Test
public void testGroupBy(){
String hql = "SELECT MIN(salary),MAX(salary) ,department FROM Employee e GROUP BY e.department "
+ "HAVING MIN(salary) > ?";
Query query = session.createQuery(hql);
List<Object[]> result = query.setParameter(0, 4000).list();
for (Object[] objects : result) {
System.out.println(Arrays.asList(objects));
}
}
2.本地 SQL 检索方式
/*
* 使用本地 SQL 语句时,设置参数的下标从 1 开始。
*/
@Test
public void testNativeSQL(){
String sql = "INSERT INTO departments(name) VALUES(?)";
Query query = session.createNativeQuery(sql);
query.setParameter(1, "办公室").executeUpdate();
}
九、Hiberna 的二级缓存
Hibernate 缓存:
- 缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存.
- Hibernate中提供了两个级别的缓存
- 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的
- 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
SessionFactory 级别的缓存:
- SessionFactory 的缓存可以分为两类:
- 内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据(.hbm.xml 文件中的数据)的复制. 该内置缓存是只读的.
- 外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘
使用 Hibernate 的二级缓存:
- 适合放入二级缓存中的数据:
- 很少被修改
- 不是很重要的数据, 允许出现偶尔的并发问题
- 不适合放入二级缓存中的数据:
- 经常被修改
- 财务数据, 绝对不允许出现并发问题
- 与其他应用程序共享的数据
二级缓存的并发访问策略:
在 hibernate.hbm.xml 开启二级缓存:
<!-- 启用二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置使用的二级缓存产品 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 配置那个类 需要设置二级缓存,单独写在该类的 .hbm.xml 文件中也可以 -->
<class-cache usage="read-write" class="cn.edu.pzhu.cg.hql.Employee"/>
<!-- 设置集合应用二级缓存 -->
<collection-cache usage="read-write" collection="cn.edu.pzhu.cg.hql.Department.employees"/>
ehcache.xml 配置文件:
- cache 元素的属性
- name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
- maxInMemory:设置基于内存的缓存中可存放的对象最大数目
- eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
- timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
- timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
- overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
ehcache.xml:
<?xml version="1.0"?>
<ehcache>
<!-- 磁盘的存储路径:当数据需要写到硬盘上时,就写在这个目录上 -->
<diskStore path="d:\\TempDirectory"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!-- name:设置缓存名字,取值为类的全限名或类的集合的名字 -->
<!-- maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目 -->
<!-- eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false -->
<!-- timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。
如果此值为0,表示对象可以无限期地处于空闲状态。 -->
<!-- timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中,
该属性值必须大于或等于 timeToIdleSeconds 属性值。 -->
<!-- overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 -->
<cache name="cn.edu.pzhu.cg.hql.Employee"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="cn.edu.pzhu.cg.hql.Department.employees"
maxElementsInMemory="1"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"
/>
</ehcache>
查询缓存:
/*
*二级缓存
*/
@Test
public void testSecondCache(){
Employee employee = session.get(Employee.class, 1);
System.out.println(employee.getName());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
/*
* 二级缓存属于 sessionFactory 级别,即使提交了事务,重新获取了 session 缓存还是在,只会发送一条 SQL 语句
*/
Employee employee2 = session.get(Employee.class, 1);
System.out.println(employee2.getName());
}
@Test
public void testConnectionCache(){
Department department = session.get(Department.class, 1);
System.out.println(department.getDepartName());
System.out.println(department.getEmployees().size());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Department department2 = session.get(Department.class, 1);
System.out.println(department2.getDepartName());
System.out.println(department2.getEmployees().size());
}
十、Session 对象的生命周期与本地线程绑定
- 如果把 Hibernate 配置文件的 hibernate.current_session_context_class 属性值设为 thread, Hibernate 就会按照与本地线程绑定的方式来管理 Session
- Hibernate 按以下规则把 Session 与本地线程绑定
- 当一个线程(threadA)第一次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法会创建一个新的 Session(sessionA) 对象, 把该对象与 threadA 绑定, 并将 sessionA 返回
- 当 threadA 再次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法将返回 sessionA 对象
- 当 threadA 提交 sessionA 对象关联的事务时, Hibernate 会自动flush sessionA 对象的缓存, 然后提交事务, 关闭 sessionA 对象. 当 threadA 撤销 sessionA 对象关联的事务时, 也会自动关闭 sessionA 对象
- 若 threadA 再次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法会又创建一个新的 Session(sessionB) 对象, 把该对象与 threadA 绑定, 并将 sessionB 返回
SessionUtils:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
/*
*单例模式
*/
public class HibernateUtils {
private static SessionFactory sessionFactory;
private static HibernateUtils instance = new HibernateUtils();
public HibernateUtils() {
}
public static HibernateUtils getInstance() {
return instance;
}
public static SessionFactory getSessionFactory() {
//初始化 sessionFactory
if(sessionFactory == null){
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}
return sessionFactory;
}
public Session getSession(){
return getSessionFactory().getCurrentSession();
}
}
完整的 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.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.connection.url">jdbc:mysql:///test2?useUnicode=true&characterEncoding=UTF-8</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 配置hibernate的基本信息 -->
<!-- hibernate 所使用的数据库的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- 执行操作时,是否在控制台打印 SQL 语句 -->
<property name="show_sql">true</property>
<!-- 是否对 SQL 进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!-- 设置 Hibernate 的事务隔离级别 (读-提交)-->
<property name="connection.isolation">2</property>
<!-- 执行 delete 操作后,将其 OID 置为 null -->
<property name="use_identifier_rollback">true</property>
<!-- 配置将 session 和本地线程绑定 -->
<property name="current_session_context_class">thread</property>
<!-- 配置 C3P0 -->
<property name="c3p0.max_size">10</property>
<property name="c3p0.min_size">5</property>
<!-- 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接 -->
<property name="c3p0.acquire_increment">2</property>
<!-- 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时 -->
<property name="c3p0.idle_test_period">2000</property>
<!-- 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁 -->
<property name="c3p0.timeout">2000</property>
<!-- 缓存 Statement 对象的数量 -->
<property name="c3p0.max_statements">10</property>
<!-- 设定对数据库进行批量删除、更新、插入的时候的批次大小 -->
<property name="jdbc.batch_size">30</property>
<!-- 启用二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置使用的二级缓存产品 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 指定关联的 .hbm.xml 文件 -->
<mapping resource="cn/edu/pzhu/cg/hql/Department.hbm.xml"/>
<mapping resource="cn/edu/pzhu/cg/hql/Employee.hbm.xml"/>
<!-- 配置那个类 需要设置二级缓存,单独写在该类的 .hbm.xml 文件中也可以 -->
<class-cache usage="read-write" class="cn.edu.pzhu.cg.hql.Employee"/>
<class-cache usage="read-write" class="cn.edu.pzhu.cg.hql.Department"/>
<!-- 设置集合应用二级缓存 -->
<collection-cache usage="read-write" collection="cn.edu.pzhu.cg.hql.Department.employees"/>
</session-factory>
</hibernate-configuration>