Hibernate的核心组件
在基于MVC设计模式的JAVA WEB应用中,Hibernate可以作为模型层/数据访问层。它通过配置文件(hibernate.properties或hibernate.cfg.xml)和映射文件(***.hbm.xml)把JAVA对象或PO(Persistent Object,持久化对象)映射到数据库中的数据库,然后通过操作PO,对数据表中的数据进行增,删,改,查等操作。
除配置文件,映射文件和持久化类外,Hibernate的核心组件包括以下几部分:
a)Configuration类:用来读取Hibernate配置文件,并生成SessionFactory对象。
b)SessionFactory接口:产生Session实例工厂。
c)Session接口:用来操作PO。它有get(),load(),save(),update()和delete()等方法用来对PO进行加载,保存,更新及删除等操作。它是Hibernate的核心接口。
d)Query接口:用来对PO进行查询操。它可以从Session的createQuery()方法生成。
e)Transaction接口:用来管理Hibernate事务,它主要方法有commit()和rollback(),可以从Session的beginTrancation()方法生成。
Persistent Object
持久化对象可以是普通的Javabeans,惟一特殊的是它们与(仅一个)Session相关联。JavaBeans在Hibernate中存在三种状态:
1.临时状态(transient):当一个JavaBean对象在内存中孤立存在,不与数据库中的数据有任何关联关系时,那么这个JavaBeans对象就称为临时对象(Transient Object)。
2.持久化状态(persistent):当一个JavaBean对象与一个Session相关联时,就变成持久化对象(Persistent Object)
3.脱管状态(detached):在这个Session被关闭的同时,这个对象也会脱离持久化状态,就变成脱管状态(Detached Object),可以被应用程序的任何层自由使用,例如可以做与表示层打交道的数据舆对象(Data Transfer Object)。
Hibernate的运行过程
Hibernate的运行过程如下:
A:应用程序先调用Configration类,该类读取Hibernate的配置文件及映射文件中的信息,并用这些信息生成一个SessionFactpry对象。
B:然后从SessionFactory对象生成一个Session对象,并用Session对象生成Transaction对象;可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载,保存,更新,删除等操作;在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;如果没有异常,Transaction对象将 提交这些操作结果到数据库中。
Hibernate的运行过程如下图:
Hibernate配置方式
Hibernate给人的感受是灵活的,要达到同一个目的,我们可以使用几种不同的办法。就拿Hibernate配置来说,常用的有如下三种方式,任选其一。
- 在 hibernate.cfg.xml 中加入元素 <property>、<mapping>,放置在类路径(classpath)的根目录下。
- 将 hibernate.properties 放置放在类路径的根目录下。
- 可编程的配置方式,即在程序中配置Hibernate的启动参数、加载映射文件,需要用Configuration接口来实现这一方式。
使用hibernate.cfg.xml是我比较喜欢的方式,一方面xml天生的优势——良好的可读性,让配置的意图一目了然。另一方面这是官方推荐使用的,如果同时在hibernate.cfg.xml和hibernate.properties对Hibernate进行了配置,那么前者将覆盖后者。
hibernate.properties可以非常的简洁明了,并且有一种linux配置文件的风格。以#开始一行的注释,用键值对的方式存储配置参数。
对于这两种方式,结果都是一样的。只是看个人喜好。关于配置参数我们稍后讨论。
Configuration类
org.hibernate.cfg.Configuration实例的作用是对Hibernate进行配置,以及对它进行启动。在Hibernate的启动过程中,Configuration类的实例首先读取Hibernate配置文件,加载配置信息,然后加载映射文件,创建一个SessionFactory对象。
实例被设计成启动期间(startup-time)对象,一旦SessionFactory 创建完成它就被丢弃了。
要使用一个Configuration对象,要为它设置两个方面的内容:
- 数据库连接属性
- hbm.xml或pojo类
Configuration常用操作函数
1.加载Hibernate配置文件
Configuration cfg=new Configuration().configure("/etc/hibernate.cfg.xml");
或者
Configuration cfg=new Configuration().configure("/etc/hibernate.properties");
2.为Configuration指定映射文件
cfg.addResource("test/User.hbm.xml");
3.为Configuration指定POJO类,Order.hbm.xml根Order.java一个目录
cfg.addClass(test.Order.class);
4.为Configuration指定Hibernate配置属性,当然我们加载了配置文件就不能使用这个方法了。
1
2
3
4
5
|
Configuration cfg = new Configuration() .addClass(test.User. class ) .setProperty( "hibernate.dialect" , "org.hibernate.dialect.MySQLDialect" ) .setProperty( "hibernate.connection.datasource" , "java:comp/env/jdbc/test" ) .setProperty( "hibernate.order_updates" , "true" ); |
5.获得SessionFactory
SessionFactory sessions = cfg.buildSessionFactory();
当所有映射定义被 Configuration 解析后,应用程序必须获得一个用于构造org.hibernate.Session 实例的工厂SessionFactory。这个工厂将被应用程序的所有线程共享,线程安全的全局对象,只需要被实例化一次。单例模式。
Hibernate 允许你的应用程序创建多个SessionFactory 实例。这对 使用多个数据库的应用来说很有用。
hibernate.cfg.xml
hibernate.cfg.xml在文档开头的DTD(文档类型定义)是很复杂的。我们并不需要去理会和记忆他。你可以直接copy它。
hibernate.cfg.xml文档以<hibernate-configuration>为根元素,你可以在其子元素<session-factory>中
- 加入<property>元素来配置各种参数。
- 加入<mapping>加载映射文件,resource代表映射文件的路径。
一个hibernate.cfg.xml例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<?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.url" >jdbc:mysql: //localhost:3306/test</property> <property name= "connection.username" >root</property> <property name= "connection.password" >klguang @mysql </property> <property name= "connection.driver_class" >com.mysql.jdbc.Driver</property> <!-- SQL dialect --> <property name= "dialect" >org.hibernate.dialect.MySQLDialect</property> <!-- JDBC connection pool (use the built-in) --> <property name= "connection.pool_size" > 10 </property> <!-- Enable Hibernate's automatic session context management --> <property name= "current_session_context_class" >thread</property> <!-- Disable the second-level cache --> <property name= "cache.provider_class" > org.hibernate.cache.NoCacheProvider </property> <!-- Echo all executed SQL to stdout --> <property name= "show_sql" > true </property> <!-- Drop and re-create the database schema on startup --> <property name= "hbm2ddl.auto" >update</property> <property name= "javax.persistence.validation.mode" >none</property> <mapping resource= "hbm/User.hbm.xml" /> <mapping resource= "hbm/Event.hbm.xml" /> <mapping resource= "hbm/Person.hbm.xml" /> </session-factory> </hibernate-configuration> <br> |
hibernate.properties
对于hibernate.properties作为配置文件的方式,我是不推荐新手使用的。因为,其可读性差,另外众多的配置参数会让初学者不知道如何下手。
在Hibernate发布包的project/etc/,提供了一个hibernate.properties文件,该文件列出了Hibernate 的所有配置参数,但都是用#注释掉了。每一行是一个配置参数,以键值对的方式存在,空格前是key,空格后是value,我们应该将空格改为等号。
对每一个配置参数,文件里都有详细的解释。我们只需要将见面#去掉,并修改其value就可以了。
一个hibernate.properties例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#数据库使用的驱动类 hibernate.connection.driver_class=com.mysql.jdbc.Driver #数据库连接串 hibernate.connection.url=jdbc:mysql: //localhost:3306/db #数据库连接的用户名 hibernate.connection.username=user #数据库连接的密码 hibernate.connection.password=password #数据库使用的方言 hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect #是否打印SQL语句 hibernate.show_sql= true javax.persistence.validation.mode=none |
hibernate.properties没有提供加载映射文件的方式。因此需要通过Configuration的.addResource()方法来加载映射文件或POJO类,Hibernate会自动找到另一方,前提映射文件和POJO类在同一包(目录)中。
1
2
3
4
|
Configuration cfg = new Configuration(); cfg.configure( "/etc/hibernate.properties" ); cfg.addResource( "test/User.hbm.xml" ); cfg.addClass(test.Order. class ); |
Hibernate配置参数详解
Hibernate JDBC 属性
属性名 |
用途 |
hibernate.connection.driver_class |
JDBC driver class |
hibernate.connection.url |
JDBC URL |
hibernate.connection.username |
database user |
hibernate.connection.password |
数据库用户密码 |
hibernate.connection.pool_size |
maximum number of pooled connections |
Hibernate 数据源属性
属性名 |
用途 |
hibernate.connection.datasource |
数据源 JNDI 名字 |
hibernate.jndi.url JNDI |
提供者的 URL(可选) |
hibernate.jndi.class JNDI |
InitialContextFactory 类(可选) |
hibernate.connection.username |
数据库用户(可选) |
hibernate.connection.password |
数据库密码(可选) |
可选的配置属性
有大量属性能用来控制 Hibernate 在运行期的行为。它们都是可选的,并拥有适当的默认值。
属性名 |
用途 |
可选值 ()内为默认 |
hibernate.dialect |
允许 Hibernate 针对特定的关系数据库生成优化的 SQL 的org.hibernate.dialect.Dialect 的类名。 例如:org.hibernate.dialect.MySQLDialect |
|
hibernate.show_sql |
输出所有 SQL 语句到控制台。 |
true|false (false) |
hibernate.format_sql |
在 log 和 console 中打印出更漂亮的 SQL。 |
true|false (false) |
hibernate.default_catalog |
在生成的 SQL 中,将给定的 catalog 附加于非全限定名的表名上 |
|
hibernate.session_factory_name |
org.hibernate.SessionFactory 创建后,将自动使用这个名字绑定到 JNDI 中。 |
|
hibernate.max_fetch_depth |
为单向关联(一对一,多对一)的外连接抓取(outer join fetch)树设置最大深度。 |
0到3 |
hibernate.default_batch_fetch_size |
为 Hibernate 关联的批量抓取设置默认数量。 |
4、8、16 |
hibernate.default_entity_mode |
为由这个 SessionFactory 打开的所有 Session指定默认的实体表现模式。 |
dynamic-map,dom4j,pojo |
hibernate.order_updates |
强制 Hibernate 按照被更新数据的主键,为SQL 更新排序。这么做将减少在高并发系统中事务的死锁。 |
true|false
|
hibernate.generate_statistics |
如果开启,Hibernate 将收集有助于性能调节的统计数据。 |
true|false
|
hibernate.use_identifier_rollback |
如果开启,在对象被删除时生成的标识属性将被重设为默认值。 |
true|false
|
hibernate.use_sql_comments |
如果开启,Hibernate 将在 SQL 中生成有助于调试的注释信息,默认值为 false。 |
true|false (false) |
Hibernate JDBC 和连接(connection)属性、Hibernate 缓存属性、Hibernate 事务属性等主要用于提升性能,并且Hibernate有适当的默认值。入门者可以忽略这些设置,等学到一定阶段,你可以参考官方文档进行适当配置。
package cn.curry.test; import cn.curry.entity.Student; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Created by Curry on 2017/1/8. */ public class MyTest { public static void main (String [] args){ } Session session; Transaction transaction; @Before public void before(){ Configuration cfg=new Configuration().configure(); SessionFactory factory=cfg.buildSessionFactory(); session = factory.getCurrentSession(); transaction= session.beginTransaction(); } @Test public void add(){ Student student=new Student(); //student.setId(1); student.setName("哇哇哇"); student.setAge(20); session.save(student); } @Test public void update(){ Student stu=(Student)session.load(Student.class,new Integer(2)); stu.setName("嘿嘿"); //session.saveOrUpdate(stu); session.merge(stu); } @Test public void delete(){ Student stu=(Student)session.load(Student.class,new Integer(21)); session.delete(stu); } @Test public void select(){ Student stu=(Student)session.load(Student.class,new Integer(2)); System.out.println(stu.getName()); } @After public void after(){ transaction.commit(); //System.out.println(stu.getName()); //session.close(); } }
Hibernate——脏检查和缓存清理机制
脏检查
Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照。当Session清理缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照,来判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session会根据脏对象的最新属性来执行相关的SQL语句,从而同步更新数据库。
缓存清理机制
当Session缓存中对象的属性每次发生了变化,Session并不会立即清理缓存和执行相关的SQL update语句,而是在特定的时间点才清理缓存,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,一遍减少访问数据库的次数,从而提高应用程序的数据访问性能。
在默认情况下,Session会在以下时间点清理缓存。
- 当应用程序调用org.hibernate.Transaction的commit()方法的时候.commit方法先清理缓存,然后再向数据库提交事务。Hibernate之所以把清理缓存的时间点安排在事务快结束时,一方面是因为可以减少访问数据库的频率,还有一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间。
- 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,就会清理缓存,使得Session缓存与数据库已经进行了同步,从而保证查询结果返回的是正确的数据。
- 当应用程序显示调用Session的flush()方法的时候。
Session进行清理缓存的例外情况是,如果对象使用native生成器来生成OID,那么当调用Session的save()方法保存该对象时,会立即执行向数据库插入该实体的insert语句。
主键生成策略
increment
identity
sequence
native
uuid
assigned
1) increment
由hibernate完成 主键递增,
原理:select max(id) , insert时max(id)+1 ,完成主键递增
优点:跨数据库
缺点:多线程并发访问问题(第一个线程执行成功,第二个线程报错)
2) identity
由底层数据库来完成自增 ,要求数据库必须支持自增主键 mysql支持 ,oracle不支持
3) sequence
编号列生成由底层数据库提供序列,来完成主键自增,要求数据库必须支持序列 mysql不支持,oracle支持
create sequence myseq; 创建序列
insert into customer values (myseq.nextval); 插入数据时调用序列,序列+1
4) native
采用数据库支持自增策略, mysql就用identity 、oracle就用sequence
策略1) ---> 策略4) 要求数据库主键必须为数字 ,因为只有数字才能自增
5) uuid
32位 唯一字符串, 主键使用varchar 类型
真实开发中,用程序提供uuid值
6) assigned
手动指定主键的值,该主键一般有实际意义,例如订单单号(20160114-A002)20160114-B001 20160114-C002。
复合主键,是一种特殊 assigned类型 自然主键 (通常需要手动指定),PO类必须实现Serializable接口
<class name="cn.happy.entity.Person" table="person">
<composite-id>
<key-property name="firstname"></key-property>
<key-property name="secondname"></key-property>
</composite-id>
</class>
7)foreign
使用另外一个相关联的对象的标识符。它通常和 <one-to-one> 联合起来使用。
8)hilo
使用一个高/低位算法高效的生成 long,short 或者 int 类型的标识符。给定一个表和字段(默认分别是hibernate_unique_key 和 next_hi)作为高位值的来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。
9)select
通过数据库触发器选择一些唯一主键的行并返回主键值来分配一个主键。
Hibernate中Java对象的三种状态
01.三种状态
瞬时状态(Transient)
持久状态(Persistent) Persistent 持久
游离状态(Detached)
02.三种状态之间的转换
该图从类型上划分为“活动图”
开始●:对象声明的开始。
结束:对象销毁了。
关于load和get方法区别的说明:
Session.load/get方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象。
其区别在于:
如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。
Load方法可返回实体的代理类实例,而get方法永远直接返回实体类。
/** *//**
* get()方法的执行顺序如下:
* a):首先通过id在session缓存中查找对象,如果存在此id的对象,直接将其返回
* b):在二级缓存中查找,找到后将 其返回。
* c):如果在session缓存和二级缓存中都找不到此对象,则从数据库中加载有此ID的对象
* 因此get()方法并不总是导致SQL语句,只有缓存中无此数据时,才向数据库发送SQL!
*/
/** *//**
* 与get()的区别:
* 1:在立即加载对象(当hibernate在从数据库中取得数据组装好一个对象后
* 会立即再从数据库取得数据此对象所关联的对象)时,如果对象存在,
* load()和get()方法没有区别,都可以取得已初始化的对象;但如果当对
* 象不存在且是立即加载时,使用get()方法则返回null,而使用load()则
* 抛出一个异常。因此使用load()方法时,要确认查询的主键ID一定是存在
* 的,从这一点讲它没有get方便!
* 2:在延迟加载对象(Hibernate从数据库中取得数据组装好一个对象后,
* 不会立即再从数据库取得数据组装此对象所关联的对象,而是等到需要时,
* 都会从数据库取得数据组装此对象关联的对象)时,get()方法仍然使用
* 立即加载的方式发送SQL语句,并得到已初始化的对象,而load()方法则
* 根本不发送SQL语句,它返回一个代理对象,直到这个对象被访问时才被
* 初始化。
*/
load和个体方法都可以充分利用内部缓存和二级缓存中的现有数据。