Hibernate官方中文文档
优化Hibernate所鼓励的7大措施
- 尽量使用many-to-one,避免使用单项one-to-many
- 灵活使用单向one-to-many
- 不用一对一,使用多对一代替一对一
- 配置对象缓存,不使用集合缓存
- 一对多使用Bag 多对一使用Set
- 继承使用显示多态 HQL:from object polymorphism=”exlicit” 避免查处所有对象
- 消除大表,使用二级缓存
Hibernate 介绍
什么是Hibernate?
- 首先,hibernate 是数据持久层的一个轻量级框架。
- 并且 hibernate 是一个开源的 orm (object relations model) 框架,提供了查询获取数据的方法,用面向对象的思想来操作数据库,节省了我们开发处理数据的时间。
优点
- 使用简洁的 hql (hibernate query language)。可以不使用传统的 insert,update等 sql 语句。比如 insert 一个对象,原来的做法是:
insert into table_name value (value1, value2, value3...)
,而现在的做法是:save(obj)
。 - 使用 or 映射。对象到关系数据库之间的映射,是从对象的角度操作数据库,在此体现了面向对象的思想。原来的实体抽取方法:首先有了表,然后表映射实体对象。而现在 hibernate 的做法是:直接由对象映射到表。
- 没有侵入性,移植性比较好。什么是没有侵入性?就是hibernate 采用了 pojo 对象。所有pojo 对象就是没有继承 hibernate 类或实现 hibernate 接口。这样的话,此类就是一个普通的 Java 类,所以移植性比较好。
- 支持透明持久化。透明是针对上层而言的。三层架构的理念是上层对下层的依赖,只是依赖接口不依赖具体实现。而 hibernate 中的透明是指对业务逻辑层提供了一个接口 session ,而其他的都是封装隐藏。持久化是指把内存中的数据存放到磁盘的文件中去。
缺点
- 若是大量数据批量操作,不适合使用hibernate。
- 一个持久化对象不能映射到多张表中。
核心接口
Configuration
:负责配置及启动 hibernate,用来创建SessionFactory
。SessionFactory
:一个SessionFactory
对应一个数据源存储,也就是一个数据库对应一个SessionFactory
。SessionFactory
用来创建Session
对象。并且SessionFactory
是线程安全的,可以由多个线程访问SessionFactory
共享。Session
:这个接口是 hibernate 中常用的接口,主要用于对数据的操作(增删查改)。而这个Session
对象不是线程安全的。不能共享。Query
:用于数据库的查询对象。Transaction
:hibernate 事务接口。它封装了底层的事务操作,比如JTA (java transaction architecture) 所有的数据操作,不如增删查改都写在事务中。
Hibernate 使用步骤
- 配置
hibernate.cfg.xml
配置文件 - 建立实体类
- 建立实体类配置文件
*.hbm.xml
- 添加
*.hbm.xml
映射文件路径到hibernate.cfg.xml
悲观锁和乐观锁
悲观锁:在查询时加
Student s = session.get(Student.class, id, LockMode.UPGRADE);
五种模式
LockMode.NONE
:查询时先在 cache (缓存)里找,如果没有,再到 db 里加载无锁机制。LockMode.READ
:不管 cache 有没有,都查询数据库,hibernate 在读取记录的时候会自动获取。LockMode.UPGRADE
:不管cache 有没有,都查询数据库,并且对查询的数据加锁,如果锁被其它事务拿走,当前事务会一直等到加上锁为止。利用数据库的for update
子句加锁。LockMode.UPGRADE_NOWAIT
:不管 cache 有没有,都查询数据库,并且对查询的数据加锁,如果锁被其它事务拿走,当前事务会立即返回。hibernate Oracle 的特定实现,利用 Oracle 的for update nowait
子句实现加锁。LockMode.WRITE
:在做insert, update, delete
会自动使用模式,内部使用。
什么时候用悲观锁(查询数据时,就给数据加锁)
- 数据源被多个事务并发访问的机会很大
- 修改数据所需时间非常短
乐观锁:大多基于数据版本记录机制实现
- 在javabean 中加上 version 的属性提供 set() 和 get()方法
- 在数据库表上加上 version 列
- 在映射文件的
<id></id>
后加上<version name="version" column="version" />
实现原理
- 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行对比,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
事务的并发控制
多个事务运行时的并发问题
并发问题归纳为以下几类:
- 第一类丢失更新:撤销一个事务时,把其它事务已经提交的更新数据覆盖。
- 脏读:一个事务读到另一个事务未提交的更新数据。
- 虚读:一个事务读到另一个事务提交的新插入的数据。
- 不可重复读:一个事务读到另一个事务已经提交的更新数据。事务A对统一数据重复读两次却得到不同的结果,有可能在A读取数据的时候事务B对统一数据做了修改。
- 第二类丢失更新:这是不可重复读中的特例,一个事务付给另一个事务已提交的更新事务。
数据库系统提供了四中事务隔离级别供用户选择
Serializable
(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新。Repeatable Read
(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。Read Commited
(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新。Read Uncommitted
(读未提交数据):一个事务在执行过程中可以拷打其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以有优先考虑把数据库系统的隔离级别设为
Read Commited
,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制
在hibernate.cfg.xml
中设置隔离级别:
<session-factory>
<!-- 设置格力层次,并发,缺省时Read Commited:2 -->
<property name="connection.isolation">2</property>
<!-- 配置事务实现类 -->
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
</session-factory>
对象状态
关系映射
- 这里的关系是指对象之间的关系,不是数据库之间的关系。
简化问题:
a) 怎么写Annotation
b) 增删查改CRUD怎么写
三种关系(一对一,一对多,多对多),如果在程序里就有(单向,多向)七种。
常用的主键关联的单向的外键关联。
一对一的单向外键关联:
如果在类里面 new 另一个对象,则是类的单向引用。多对多,一对一都是多对多的特殊例子。
- Annotation
@OneToOne
@JoinColumn(name="wifeId")
public Wife getWife() {
return wife;
}
- xml文件
<many-to-one name="student" column="studentId" unique="true">
</many-to-one>
<!-- 不能按照字面理解。unique="true"就变成了一对一的单向关联,
column关联字段,注意在建立关联的时候,每个表格的配置都正确,
因为出错了不会报错。
-->
一对一的双向外键关联:
两个关联
- Annotation
@OneToOne(mappedBy="wife")
/*
* 相当于告诉 hibernate 它跟该对象是一个一对一的关联
* 告诉它在 Wife 已经在 husband 类的一个属性 wife上做了映射。
*/
- xml文件
i
<one-to-one name="stuldCard" property-ref="student">
</one-to-one>
ii
<many-to-one name="student" column="studentId" unique="true">
</many-to-one>
一对一的单向主键关联(少用)
- Annotation
@OneToOne
@PrimaryKeyJoinColumn
- xml文件
<one-to-one name="student" constrained="true" column="student">
</one-to-one>
<!-- 可以设置主键约束,加上:constrained="true"
主键也要改
-->
<id name="id" column="id">
<generator class="foregin">
<param name="property">
student
</param>
</generator>
</id>
双向的主键关联(少用)
在设置主键关联的类的主键字段需要单独建立一个类,字段就是实体类需要主键关联的主键,WifePK.java
。这个类不是实体类,不做任何操作。
- Annotation
@IdClass(WifePK.class)
:设置联合的类,包含联合的字段,WifePK需要实现Serializable
和重写equals()
和hashCode()
方法。在每一个联合的属性上加@Id
就可以了。如果要更改字段名字,在联合主键里用JoinColumns
@JoinColumn(
(
@JoinColumn(name="wifeId", referencedColumnName="id")
@JoinColumn(name="wifeName", referencedColumnName="name")
)
只要有双向关联mappedBy
必设(不应该在两边都插数据)
多对一单向(重点)
- 项目名称
- 多个User可以是一个Group
- 在多那一方加外键。
@OneToMany
加在get()方法 - 多对一的映射关联用在映射类的get()方法上
@ManyToOne
,如果类名与表明不一样,用`@Table(name=”tableName”) - xml
<many-to-one name="student" column="studentId"></many-to-one>
一对多单向
- Annotation
Set<User> user = new HashSet<User>();
@OneToMany
@JoinColumn(name="hb")
public Set<User> getUser() {
return user;
}
- xml文件
<set name="user">
<key column="groupId" />
</set>
<one-to-many class="*.User">
</one-to-many>
Demo
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.username">root</property>
<!--密码 -->
<property name="hibernate.connection.password">root</property>
<!--url信息 -->
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/test
</property>
<!--数据库驱动信息 -->
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!--数据库方言信息 -->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<!-- 数据库表生成策略
create创建
update更新
-->
<property name="hbm2ddl.auto">update</property>
<!-- 是否打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否格式化SQL语句 -->
<property name="format_sql">true</property>
<!-- sessionFactory.getCurrentSession()所需 -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 是否打开二级缓存,默认关闭
<property name="cache.provider_class">
net.sf.ehcache.hibernate.EhCacheProvider
</property>
指定二级缓存的实现类 -->
<!--指定Hibernate映射文件需要关联的映射对象 -->
<mapping resource="com/foo/hibernate/bean/student.hbm.xml" />
</session-factory>
</hibernate-configuration>
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>
<!-- 对应表STUDENT -->
<class name="com.foo.hibernate.bean.Student" table="STUDENT">
<!-- 开启缓存
<cache usage="read-only" />
-->
<!-- hibernate方言会将java类型转换成数据库数据类型 -->
<id name="id" type="int">
<!-- 字段 -->
<column name="ID" />
<!-- 生成策略
native由数据库生成,如果创建时设置值也会忽视
-->
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="int">
<column name="AGE" />
</property>
</class>
</hibernate-mapping>
Student.java
package com.lee.hibernate.bean;
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
Hibernate API
SessionFactory
SessionFactory
就是一个用于创建Hibernate的Session
对象的工厂。SessionFactory
通常是在应用启动时创建好的,应用程序中的代码用它来获得Session
对象。作为一个单个的数据存储,它也是 线程安全的,所以多个线程可同时使用同一个SessionFactory。Java JEE应用一般只有一个SessionFactory,服务于客户请求的各线程都通过这个工厂来获得Hibernate的Session实例,这也是为什么SessionFactory接口的实现必须是线程安全的原因。还有,SessionFactory的内部状态包含着同对象关系影射有关的所有元数据,它是 不可变的,一旦创建好后就不能对其进行修改了。
Session
Session
代表着Hibernate所做的一小部分工作,它负责维护者同数据库的链接而且 不是线程安全的,也就是说,Hibernage中的Session不能在多个线程间进行共享。虽然Session会以主动滞后的方式获得数据库连接,但是Session最好还是在用完之后立即将其关闭。
创建方式
//每次创建新的session对象
//需要手动关闭
//多次创建未关闭会导致连接池溢出
session = sessionFactory().openSession()
//使用现有的session对象,事务提交或回滚之后会自动关闭.
//需要在hibernate.cfg.xml配置
//本地事务( jdbc事务 )
//<property name="hibernate.current_session_context_class">thread</property>
//全局事务( jta事务 )
//<property name="hibernate.current_session_context_class">jta</property>
session = sessionFactory.getCurrentSession()
get()与load()获得持久化对象
//get()不支持延迟检索策略
//当缓存与数据库不存在与OID对应的记录返回null
object = session.get(id);
//支持延迟加载策略
//当缓存和数据库不存在与OID对应的记录时,抛出
//ObjectNotFoundException异常
object = session.load(id);
save、persist和saveOrUpdate这三个方法的不同之处
这三个方法,也就是save()、saveOrUpdate()和persist()都是用于将对象保存到数据库中的方法,但其中有些细微的差别。例如,save()只能INSERT记录,但是saveOrUpdate()可以进行 记录的INSERT和UPDATE。还有,save()的返回值是一个Serializable对象,而persist()方法返回值为void。你还可以访问 save、persist以及saveOrUpdate,找到它们所有的不同之处。
命名SQL查询
命名查询指的是用标签在影射文档中定义的SQL查询,可以通过使用Session.getNamedQuery()方法对它进行调用。命名查询使你可以使用你所指定的一个名字拿到某个特定的查询。 Hibernate中的命名查询可以使用注解来定义,也可以使用我前面提到的xml影射问句来定义。在Hibernate中,@NameQuery用来定义单个的命名查询,@NameQueries用来定义多个命名查询。
一级缓存(Session缓存)
一级缓存无法取消,用两个方法管理。
evict()
用于将某个对象从session中清除。
clear()
用于将一级缓存中所有对象全部清除。
事例
HibernateUtils
package com.lee.hibernate.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public final class HibernateUtils {
/**
* configure()读取默认的hibernate.cfg.xml文件
* 若不是则需configure("filename.cfg.xml")
*/
private static Configuration config = new Configuration().configure();
/**
*针对某个数据库映射关系经过编译后的内存镜像, 是线程安全的.
*SessionFactory对象一旦构造完毕, 即被赋予特定的配置信息.
*/
private static SessionFactory sessionFactory = null;
//私有化构造器, 不可被new
private HibernateUtils() {
}
public static SessionFactory getSessionFactory() {
if (sessionFactory == null) {
sessionFactory = config.buildSessionFactory();
}
return sessionFactory;
}
public static Session getSession() {
/*
*两种方法创建session
*sessionFactory.openSession()
*sessionFactory.getCurrentSesssion()
*/
return getSessionFactory().getCurrentSession();
}
}
Test.java
package com.lee.hibernate.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.lee.hibernate.bean.Student;
import com.lee.hibernate.util.HibernateUtils;
public class Test {
public static void main(String[] args) {
Session session = null;
Transaction transacton = null;
try {
session = HibernateUtils.getSession();
transaction = session.beginTransaction();
Student student = new Student(0, "jack", 20);
session.save(student);
transacton.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
} finally {
if (transacton != null) {
transacton = null;
}
}
}
}
与 Spring 集成
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<!-- 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="3"></property>
<!--连接池中保留的最小连接数。Default: 3 -->
<property name="minPoolSize" value="3"></property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="5"></property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="3"></property>
<!-- 控制数据源内加载的PreparedStatements数量。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:
0 -->
<property name="maxStatements" value="8"></property>
<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
<property name="maxStatementsPerConnection" value="5"></property>
<!--最大空闲时间,1800秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="1800"></property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- Hibernate配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<!-- 与spring整合会发生错误 save is not valid without active transaction
<prop key="hibernate.current_session_context_class">thread</prop>
-->
</props>
</property>
<!-- 扫描映射文件 -->
<property name="mappingLocations">
<value>classpath:com/lee/hibernate/entity/*.hbm.xml</value>
</property>
</bean>
<!-- 事务 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 采用注解方式 -->
<tx:annotation-driven/>
</beans>