Hibernate学习札记总结(一)
Hibernate学习笔记总结(一)
Hibernate学习总结:
hibernate:
缓存是存在范围的:hibernate的缓存通常可以分为三大类:
1.事务范围
1)缓存只能被当前事务访问。
2)缓存的声明周期依赖于事务的生命周期,当事务结束,缓存也就结束生命周期。
3)缓存的物理介质为内存。
4)这里的事务可以是数据库事务或者应用事务。
5)每一个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。
在同一个事务的缓存中,持久化类的每个对象具有唯一的OID,不会出现力量哥OID都为1的对象。
2.进程范围
1)缓存被进程内的所有事务共享。这些事务有可能并发访问缓存,因此必须对缓存采用必要的事务隔离机制。
2)缓存的生命周期依赖于进程的生命周期,当进程结束,缓存也就结束生命周期。
3)进程范围的缓存可能会存放大量数据,它的物理介质可以是内存或硬盘。
4)缓存内的数据既可以采用相互关联的对象形式,也可以采用对象的散装数据形式。
备注:散装数据有点类似于对象的序列化数据,但是把对象分解为散装数据的算法通常比对象的序列化算法快。
*在进程范围的缓存中,如果数据按照相互关联的对象形式存放,那么持久化类的每个对象都具有唯一的OID。
不同的事务到缓存中查询OID为1的某个对象,将获得同一个该对象。
这样的方式有一个优点,就是节约内存,但是在并发环境中,当执行不同事务的各个线程同时长时间的操纵同一个OID
为1的该对象时,必须对这些线程进行同步,而同步会影响并发性能,并且很容易导致死锁,所以不提倡这种方式。
*如果缓存中的数据采用对象的散装数据形式,那么当不同的事务到缓存中查询OID为1的该对象时,获得的是该对象
的散装数据,每个事务都必须分别 根据散装数据重新构造出该对象的实例。
也就是说,每个事务都会获得不同的该对象。(这里的不同指的是内存地址不同。)
例:当不同的事务同时操作OID为1的对象时,仅仅在它们同时从进程范围的缓存中读取该对象的散装数据的时刻,
需要对进程范围的缓存采取事务隔离措施。接下来,每个事务操纵个自己的该对象,无须对执行这些事务的线程同步。
缺点:内存消耗较大。
3.集群范围
1.事务范围的缓存是持久化层的第一级缓存,通常它是必须的,由session进行管理。
2.进程范围或集群范围的缓存是持久化层的第二级缓存,通常它是可选的,由sessionFactory管理。
3.hibernate还为查询结果提供了一个查询缓存,它是依赖于二级缓存的。
补充:二级缓存适合存放的数据类型:
1.很少被修改的数据
2.不是很重要的数据,允许出现偶尔的并发问题。
3.不会被并发访问的数据
二级缓存绝对不适合存放的数据:
1.经常被修改的数据
2.安全要求高的数据(例:财务)
3.与其他应用共享的数据。原因:因为当使用二级缓存的hibernate应用与其他应用共享数据库中的某种数据时,如果其他应用
修改了数据库中的数据,hibernate无法自动保证第二季缓存中的数据与数据库保持一致。
二级缓存是可以配置4种类型的并发访问策略。
1.事务型
2.读写型
3.非严格读写型
4.只读型
Hibernate解决高并发时脏读不可重复读等并发问题的方法:
一:使用Hibernate设置数据库隔离级别
在Hibernate的配置文件中可以显示的配置数据库事务隔离级别。每一个隔离级别用一个整数表示:
8 - Serializable 串行化
4 - Repeatable Read 可重复读
2 - Read Commited 可读已提交
1 - Read Uncommited 可读未提交
在hibernate.cfg.xml中使用hibernate.connection.isolation参数配置数据库事务隔离级别。
此种方式最直接有效,但是带来的效果大大的降低了数据库的性能。
二:使用悲观锁解决事务并发问题
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)
修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,
往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,
否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的依赖数据库的悲观锁调用:select * from account where name=”Erica” for update
这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
悲观锁,也是基于数据库的锁机制实现。
在Hibernate使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性:
查看API可以看到get方法第三个参数"lockMode"或"lockOptions",
注意在Hibernate3.6以上的版本中"LockMode"已经不建议使用。
方法的第三个参数就是用来设置悲观锁的,使用第三个参数之后,
我们每次发送的SQL语句都会加上"for update"用于告诉数据库锁定相关数据。
三:使用乐观锁解决事务并发问题
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。
悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。
但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,
如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate为乐观锁提供了3中实现:
1. 基于version
2. 基于timestamp
3. 为遗留项目添加添加乐观锁
配置基于version的乐观锁:
示例1:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE
hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<!-- version标签用于指定表示版本号的字段信息 -->
<version name="version" column="version" type="integer"></version>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
示例2:
配置基于timestamp的乐观锁:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<!-- timestamp标签用于指定表示版本号的字段信息 -->
<timestamp name="updateDate" column="updateDate"></timestamp>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
示例3:
遗留项目,由于各种原因无法为原有的数据库添加"version"或"timestamp"字段,
这时不可以使用上面两种方式配置乐观锁,Hibernate为这种情况提供了一个"optimisitic-lock"属性,
它位于<class>标签上:
代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people" optimistic-lock="all">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
将该属性的值设置为all,让该记录所有的字段都为版本控制信息
Hibernate 视频总结:
一、JDBC的优点和缺点
JDBC的优点:
直接底层操作,提供了很简单、便捷的访问数据库的方法,跨平台性比较强。灵活性比较强,可以写很复杂的SQL语句。
JDBC的缺点:
因为JAVA是面向对象的,JDBC没有做到使数据能够面向对象的编程,使程序员的思考仍停留在SQL语句上。
操作比较繁琐,很多代码需要重复写很多次。
如果遇到批量操作,频繁与数据库进行交互,容易造成效率的下降。
JDBC的程序操作可以封装一些什么内容?又不可以封装哪些内容
二、为什么要用Hibernate
1.Hibernate实现了面向对象的数据库编程
2.Hibernate比起JDBC来,在代码的书写上比较简单化了。
3.Hibernate提出了缓存机制,这样可以使访问数据的效率提高很大。
三、第一个hibernate程序
*知识点:宁愿建一张200个字段的表,也不要建50张表。
* 真实开发中要尽量减少外键的关联,外键只是一种约束,并不是没有外键就无法查到关联的信息。
* 例如:只有索引没有主键、外键。关联关系利用表中的字段来实现关联。
第一个hibernate例子:
配置文件:
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--
数据库的用户名
-->
<property name="connection.username">root</property>
<!--
密码
-->
<property name="connection.password">root</property>
<!--
url
-->
<property name="connection.url">
jdbc:mysql://localhost:3306/hibernate_itheima03
</property>
<!--
方言
告诉hibernate,要操作的数据库是mysql,使用的数据库不同,方言也不同。
-->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<!--
导入驱动
-->
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!--
validate
只检查结构
update
检查结构,更新或者创建表
create
每次启动hibernate时,都要创建表
create-drop
启动hibernate时创建表,当hibernate关闭时,删除表
-->
<property name="hbm2ddl.auto">update</property>
<mapping resource="cn/itheima03/hibernate/domain/Person.hbm.xml" />
</session-factory>
</hibernate-configuration>
Hibernate.connection.url 表示要链接的数据库地址
Hibernate.connection.driver_class 表示要链接的数据库的驱动类
Hibernate.connection.username 要连接的数据库的用户名
Hibernate.connection.password 要连接的数据库的密码
Hibernate.dialect 表示要使用的数据库的类型
org.hibernate.dialect.MySQL5Dialect mysql数据库
org.hibernate.dialect.Oracle9Dialect oracle数据库
org.hibernate.dialect.SQLServerDialect SQLServer数据库
hibernate.hbm2ddl.auto
validate:加载hibernate时验证创建表结构
update:加载hibernate时自动更新数据库结构,如果表存在不用创建,如果不存在就创建。
create:每一次加载hibernate时都创建表结构
create-drop:加载hibernate时创建,退出时删除
持久化类:
public class Customer{
private Integer id;
private String name;
private Integer age;
private java.sql.Date birthday;
private boolean married;
private byte[] pic;
.....set&get method()......
}
映射文件:
Customer.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.access.Customer" table="cusomer">
<id name="id" column="id" type="integer">
<generator class ="increment"/>
</id>
<property name="name" length="5" column="name" type="String"/>
<property name="age" column="age" type="integer"/>
<property name="birthday" column="birthday" type="date"/>
<property name="married" column="married" type="boolean"/>
<property name="pic" column="pic" type="binary"/>
<property name="des" column="des" type="text" access="noop"/>
</class>
</hibernate-mapping>
<!--
建立表与类的关联
字段和属性的名称的关联
字段和属性的类型的关联
-->
<!--
class为一个持久化类
name为类的全名
table 表名 默认为类名
catalog 数据库的名字 一般不写
-->
<!--
id 的属性代表数据库表中的字段,该字段为主键
column 对应的字段的名称
length 指的是数据库的字段的长度
-->
服务器端调用:
Configuration configuration = new Configuration();
SessionFactory sessionFactory = configuration.buildSessionFactory();
sessionFactory.openSession();
当hibernate的配置文件存放在其他目录下时,可以使用configuration.configure("hibernate.cfg.xml的路径");
常用的方法:
session.get();
session.load();
session.delete();
session.update();
session.save();
Cnfiguration 类负责管理 Hibernate 的配置信息。包括如下内容:
Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,
数据库连接池等(对应 hibernate.cfg.xml 文件)。
持久化类与数据表的映射关系(*.hbm.xml 文件)
创建 Configuration 的两种方式
属性文件(hibernate.properties):
Configuration cfg = new Configuration();
Xml文件(hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
第二种创建方式较为常见。Hibernate.cfg.xml文件默认的目录为系统的根目录。
(展示源代码以得到证实)
也可以利用config.config方法来指定配置文件所在的目录。
Configuration对象根据当前的配置信息生成 SessionFactory 对象。
SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息
(SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。
同时,SessionFactory还负责维护Hibernate的二级缓存)。
Configuration cfg = new Configuration().configure();
SessionFactory sf = cfg.buildSessionFactory();
是线程安全的。
SessionFactory是生成Session的工厂:
Session session = sf.openSession();
构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
在Hibernate中:
Transaction tx = session.beginTransaction()相当于给数据库操作起事务。Session.commit()则为提交事务。
Hibernate的整个运行过程如下:
1、应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,
2、并用这些信息生成一个SessionFactory对象,
3、然后从SessionFactory对象生成一个Session对象,
4、并用Session对象生成Transaction对象;
A、可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载、
保存、更新、删除、等操作;
B、在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;
如果没有异常,Transaction对象将提交这些操作到数据库中。
主键生成机制:
1、配置为:Increment 代表:由hibernate自动以递增的方式生成表识符,每次增量为1。
2、配置为:Identity 代表:由底层数据库生成表识符。条件是数据库支持自动增长数据类型。
3、配置为:Sequence 代表:Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列
4、配置为:Native 代表:根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo
5、配置为:Uuid.hex 代表:Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,
这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。
如果主键用字符类型,并且不代表任何含义。。
6、配置为:assigned 代表:适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为Private的。尽
量避免使用自然主键。
increment 标识符生成器
increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.(带走+1)
适用范围:
由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
适用于只有单个 Hibernate 应用进程访问同一个数据库的场合
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型(加1带走)
适用范围:
由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence 标识符生成器
sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
适用范围:
由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2 Oracle 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
native 标识符生成器
native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
适用范围:
由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
assigned 标识符生成器
hibernate和底层数据库都不帮助你生成主键,也就是说得自己在程序中手动的设置主键的值。
适用范围:
主键有一定的含义,需要根据业务产生的情况。
Uuid标识符生成器
Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间
使用范围:
主键是字符串,而且必须是唯一
Session缓存:
Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口,
它提供了基本的保存, 更新, 删除和加载Java 对象的方法.
理解session的缓存:
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存.
只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期 。
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,
只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,
有则返回。没有在查询数据库。
清理session的缓存:
Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应.
Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库,
这一过程被称为清理缓存(flush)。
默认情况下 Session 在以下时间点清理缓存:
当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),
然后在向数据库提交事务(tx.commit())。
当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,
以保证查询结果能够反映持久化对象的最新状态显式调用 Session 的 flush() 方法。
区别:
flush: 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务;
commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
reresh:刷新,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.
clear:清空缓存,等价于list.removeAll();
利用Session缓存读取持久化对象的数据:
1 Customer c = new Customer(“TOM”,new HashSet());
2 session.save(c); //customer对象被持久化,并且加入到session的缓存中
3 Long id = c.getId();
4 c = null; //c变量不再引用customer对象
5 //从session缓存中读取customer对象,使c2变量引用customer对象
6 Customer c2 = (Customer)session.load(Customer.class,id);
7 tx.commit(); //缓存中的对象和数据库同步
8 session.close(); //关闭session 清空缓存
9 System.out.println(c2.getName()); //访问customer对象
10 C2 = null; //C2对象不再引用customer对象,customer对象结束生命周期
缓存的作用:
1。减少访问数据库的频率。
2。保证缓存中的对象与数据库中的相关记录保持同步。
Session执行批量操作
可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:
For(int i=0;i<10000;i++){
Session.save(object);
}
这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。
所以应该定期清理session的缓存,也就是flush一下,这样内存才能保证足够的空间。
session.load和 session.get方法的区别:
不同场合的不同解决方案
场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。
场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,
也许存取的是一个很大的视频文件。
两种场合的取数据的方法一样吗?是用load还是用get方法?
延迟加载
类的延迟加载
lazy 为true或者为false
集合的延迟加载
True false extra
extra为更进一步的延迟加载策略。
当调用getStudents()时不会加载hql语句,当加载student的属性的时候才会发出SQL语句。
调用getStudents().size()方法的时候,会触发类似于:
Hibernate: select count(id) from T_Student where classid =? 这样的SQL查询语句
(这是一种很聪明的做法,如果lazy=”true”,getStudents().size()
将会使得hibernate加载所有集合的数据到内存中)。
调用getStudents().contains()方法的时候(即判断是否包含某个对象),
会触发类似于:select 1 from T_Student where classid =? and id =? 这样的SQL查询语句。
单端关联
False proxy no-proxy
proxy:当前对象的単值相关对象只有在调用它的主键外的其他属性的get方法时才加载它。
no-proxy:当前对象的単值相关对象只有在调用它的属性时才加载,需要字节码增强。
Hibernate的检索策略:
1.类级别检索策略:
1、立即检索
2、延迟检索
默认的检索策略是立即检索。在Hibernate映射文件中,通过在<class>上配置 lazy属性来确定检索策略。
对于Session的检索方式,类级别检索策略仅适用于load方法;也就说,对于get、qurey检索,
持久化对象都会被立即加载而不管lazy是false还是true.
一般来说,我们检索对象就是要访问它,因此立即检索是通常的选择。
由于load方法在检索不到对象时会抛出异常(立即检索的情况下),因此我个人并不建议使用load检索;
而由于<class>中的lazy属性还影响到多对一及一对一的检索策略,因此使用load方法就更没必要了。
Hibernate学习总结:
hibernate:
缓存是存在范围的:hibernate的缓存通常可以分为三大类:
1.事务范围
1)缓存只能被当前事务访问。
2)缓存的声明周期依赖于事务的生命周期,当事务结束,缓存也就结束生命周期。
3)缓存的物理介质为内存。
4)这里的事务可以是数据库事务或者应用事务。
5)每一个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。
在同一个事务的缓存中,持久化类的每个对象具有唯一的OID,不会出现力量哥OID都为1的对象。
2.进程范围
1)缓存被进程内的所有事务共享。这些事务有可能并发访问缓存,因此必须对缓存采用必要的事务隔离机制。
2)缓存的生命周期依赖于进程的生命周期,当进程结束,缓存也就结束生命周期。
3)进程范围的缓存可能会存放大量数据,它的物理介质可以是内存或硬盘。
4)缓存内的数据既可以采用相互关联的对象形式,也可以采用对象的散装数据形式。
备注:散装数据有点类似于对象的序列化数据,但是把对象分解为散装数据的算法通常比对象的序列化算法快。
*在进程范围的缓存中,如果数据按照相互关联的对象形式存放,那么持久化类的每个对象都具有唯一的OID。
不同的事务到缓存中查询OID为1的某个对象,将获得同一个该对象。
这样的方式有一个优点,就是节约内存,但是在并发环境中,当执行不同事务的各个线程同时长时间的操纵同一个OID
为1的该对象时,必须对这些线程进行同步,而同步会影响并发性能,并且很容易导致死锁,所以不提倡这种方式。
*如果缓存中的数据采用对象的散装数据形式,那么当不同的事务到缓存中查询OID为1的该对象时,获得的是该对象
的散装数据,每个事务都必须分别 根据散装数据重新构造出该对象的实例。
也就是说,每个事务都会获得不同的该对象。(这里的不同指的是内存地址不同。)
例:当不同的事务同时操作OID为1的对象时,仅仅在它们同时从进程范围的缓存中读取该对象的散装数据的时刻,
需要对进程范围的缓存采取事务隔离措施。接下来,每个事务操纵个自己的该对象,无须对执行这些事务的线程同步。
缺点:内存消耗较大。
3.集群范围
1.事务范围的缓存是持久化层的第一级缓存,通常它是必须的,由session进行管理。
2.进程范围或集群范围的缓存是持久化层的第二级缓存,通常它是可选的,由sessionFactory管理。
3.hibernate还为查询结果提供了一个查询缓存,它是依赖于二级缓存的。
补充:二级缓存适合存放的数据类型:
1.很少被修改的数据
2.不是很重要的数据,允许出现偶尔的并发问题。
3.不会被并发访问的数据
二级缓存绝对不适合存放的数据:
1.经常被修改的数据
2.安全要求高的数据(例:财务)
3.与其他应用共享的数据。原因:因为当使用二级缓存的hibernate应用与其他应用共享数据库中的某种数据时,如果其他应用
修改了数据库中的数据,hibernate无法自动保证第二季缓存中的数据与数据库保持一致。
二级缓存是可以配置4种类型的并发访问策略。
1.事务型
2.读写型
3.非严格读写型
4.只读型
Hibernate解决高并发时脏读不可重复读等并发问题的方法:
一:使用Hibernate设置数据库隔离级别
在Hibernate的配置文件中可以显示的配置数据库事务隔离级别。每一个隔离级别用一个整数表示:
8 - Serializable 串行化
4 - Repeatable Read 可重复读
2 - Read Commited 可读已提交
1 - Read Uncommited 可读未提交
在hibernate.cfg.xml中使用hibernate.connection.isolation参数配置数据库事务隔离级别。
此种方式最直接有效,但是带来的效果大大的降低了数据库的性能。
二:使用悲观锁解决事务并发问题
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)
修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,
往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,
否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的依赖数据库的悲观锁调用:select * from account where name=”Erica” for update
这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
悲观锁,也是基于数据库的锁机制实现。
在Hibernate使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性:
查看API可以看到get方法第三个参数"lockMode"或"lockOptions",
注意在Hibernate3.6以上的版本中"LockMode"已经不建议使用。
方法的第三个参数就是用来设置悲观锁的,使用第三个参数之后,
我们每次发送的SQL语句都会加上"for update"用于告诉数据库锁定相关数据。
三:使用乐观锁解决事务并发问题
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。
悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。
但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,
如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate为乐观锁提供了3中实现:
1. 基于version
2. 基于timestamp
3. 为遗留项目添加添加乐观锁
配置基于version的乐观锁:
示例1:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE
hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<!-- version标签用于指定表示版本号的字段信息 -->
<version name="version" column="version" type="integer"></version>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
示例2:
配置基于timestamp的乐观锁:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<!-- timestamp标签用于指定表示版本号的字段信息 -->
<timestamp name="updateDate" column="updateDate"></timestamp>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
示例3:
遗留项目,由于各种原因无法为原有的数据库添加"version"或"timestamp"字段,
这时不可以使用上面两种方式配置乐观锁,Hibernate为这种情况提供了一个"optimisitic-lock"属性,
它位于<class>标签上:
代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people" optimistic-lock="all">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
将该属性的值设置为all,让该记录所有的字段都为版本控制信息
Hibernate 视频总结:
一、JDBC的优点和缺点
JDBC的优点:
直接底层操作,提供了很简单、便捷的访问数据库的方法,跨平台性比较强。灵活性比较强,可以写很复杂的SQL语句。
JDBC的缺点:
因为JAVA是面向对象的,JDBC没有做到使数据能够面向对象的编程,使程序员的思考仍停留在SQL语句上。
操作比较繁琐,很多代码需要重复写很多次。
如果遇到批量操作,频繁与数据库进行交互,容易造成效率的下降。
JDBC的程序操作可以封装一些什么内容?又不可以封装哪些内容
二、为什么要用Hibernate
1.Hibernate实现了面向对象的数据库编程
2.Hibernate比起JDBC来,在代码的书写上比较简单化了。
3.Hibernate提出了缓存机制,这样可以使访问数据的效率提高很大。
三、第一个hibernate程序
*知识点:宁愿建一张200个字段的表,也不要建50张表。
* 真实开发中要尽量减少外键的关联,外键只是一种约束,并不是没有外键就无法查到关联的信息。
* 例如:只有索引没有主键、外键。关联关系利用表中的字段来实现关联。
第一个hibernate例子:
配置文件:
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--
数据库的用户名
-->
<property name="connection.username">root</property>
<!--
密码
-->
<property name="connection.password">root</property>
<!--
url
-->
<property name="connection.url">
jdbc:mysql://localhost:3306/hibernate_itheima03
</property>
<!--
方言
告诉hibernate,要操作的数据库是mysql,使用的数据库不同,方言也不同。
-->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<!--
导入驱动
-->
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!--
validate
只检查结构
update
检查结构,更新或者创建表
create
每次启动hibernate时,都要创建表
create-drop
启动hibernate时创建表,当hibernate关闭时,删除表
-->
<property name="hbm2ddl.auto">update</property>
<mapping resource="cn/itheima03/hibernate/domain/Person.hbm.xml" />
</session-factory>
</hibernate-configuration>
Hibernate.connection.url 表示要链接的数据库地址
Hibernate.connection.driver_class 表示要链接的数据库的驱动类
Hibernate.connection.username 要连接的数据库的用户名
Hibernate.connection.password 要连接的数据库的密码
Hibernate.dialect 表示要使用的数据库的类型
org.hibernate.dialect.MySQL5Dialect mysql数据库
org.hibernate.dialect.Oracle9Dialect oracle数据库
org.hibernate.dialect.SQLServerDialect SQLServer数据库
hibernate.hbm2ddl.auto
validate:加载hibernate时验证创建表结构
update:加载hibernate时自动更新数据库结构,如果表存在不用创建,如果不存在就创建。
create:每一次加载hibernate时都创建表结构
create-drop:加载hibernate时创建,退出时删除
持久化类:
public class Customer{
private Integer id;
private String name;
private Integer age;
private java.sql.Date birthday;
private boolean married;
private byte[] pic;
.....set&get method()......
}
映射文件:
Customer.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.access.Customer" table="cusomer">
<id name="id" column="id" type="integer">
<generator class ="increment"/>
</id>
<property name="name" length="5" column="name" type="String"/>
<property name="age" column="age" type="integer"/>
<property name="birthday" column="birthday" type="date"/>
<property name="married" column="married" type="boolean"/>
<property name="pic" column="pic" type="binary"/>
<property name="des" column="des" type="text" access="noop"/>
</class>
</hibernate-mapping>
<!--
建立表与类的关联
字段和属性的名称的关联
字段和属性的类型的关联
-->
<!--
class为一个持久化类
name为类的全名
table 表名 默认为类名
catalog 数据库的名字 一般不写
-->
<!--
id 的属性代表数据库表中的字段,该字段为主键
column 对应的字段的名称
length 指的是数据库的字段的长度
-->
服务器端调用:
Configuration configuration = new Configuration();
SessionFactory sessionFactory = configuration.buildSessionFactory();
sessionFactory.openSession();
当hibernate的配置文件存放在其他目录下时,可以使用configuration.configure("hibernate.cfg.xml的路径");
常用的方法:
session.get();
session.load();
session.delete();
session.update();
session.save();
Cnfiguration 类负责管理 Hibernate 的配置信息。包括如下内容:
Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,
数据库连接池等(对应 hibernate.cfg.xml 文件)。
持久化类与数据表的映射关系(*.hbm.xml 文件)
创建 Configuration 的两种方式
属性文件(hibernate.properties):
Configuration cfg = new Configuration();
Xml文件(hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
第二种创建方式较为常见。Hibernate.cfg.xml文件默认的目录为系统的根目录。
(展示源代码以得到证实)
也可以利用config.config方法来指定配置文件所在的目录。
Configuration对象根据当前的配置信息生成 SessionFactory 对象。
SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息
(SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。
同时,SessionFactory还负责维护Hibernate的二级缓存)。
Configuration cfg = new Configuration().configure();
SessionFactory sf = cfg.buildSessionFactory();
是线程安全的。
SessionFactory是生成Session的工厂:
Session session = sf.openSession();
构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
在Hibernate中:
Transaction tx = session.beginTransaction()相当于给数据库操作起事务。Session.commit()则为提交事务。
Hibernate的整个运行过程如下:
1、应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,
2、并用这些信息生成一个SessionFactory对象,
3、然后从SessionFactory对象生成一个Session对象,
4、并用Session对象生成Transaction对象;
A、可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载、
保存、更新、删除、等操作;
B、在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;
如果没有异常,Transaction对象将提交这些操作到数据库中。
主键生成机制:
1、配置为:Increment 代表:由hibernate自动以递增的方式生成表识符,每次增量为1。
2、配置为:Identity 代表:由底层数据库生成表识符。条件是数据库支持自动增长数据类型。
3、配置为:Sequence 代表:Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列
4、配置为:Native 代表:根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo
5、配置为:Uuid.hex 代表:Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,
这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。
如果主键用字符类型,并且不代表任何含义。。
6、配置为:assigned 代表:适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为Private的。尽
量避免使用自然主键。
increment 标识符生成器
increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.(带走+1)
适用范围:
由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
适用于只有单个 Hibernate 应用进程访问同一个数据库的场合
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型(加1带走)
适用范围:
由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence 标识符生成器
sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
适用范围:
由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2 Oracle 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
native 标识符生成器
native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
适用范围:
由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
assigned 标识符生成器
hibernate和底层数据库都不帮助你生成主键,也就是说得自己在程序中手动的设置主键的值。
适用范围:
主键有一定的含义,需要根据业务产生的情况。
Uuid标识符生成器
Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间
使用范围:
主键是字符串,而且必须是唯一
Session缓存:
Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口,
它提供了基本的保存, 更新, 删除和加载Java 对象的方法.
理解session的缓存:
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存.
只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期 。
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,
只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,
有则返回。没有在查询数据库。
清理session的缓存:
Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应.
Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库,
这一过程被称为清理缓存(flush)。
默认情况下 Session 在以下时间点清理缓存:
当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),
然后在向数据库提交事务(tx.commit())。
当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,
以保证查询结果能够反映持久化对象的最新状态显式调用 Session 的 flush() 方法。
区别:
flush: 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务;
commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
reresh:刷新,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.
clear:清空缓存,等价于list.removeAll();
利用Session缓存读取持久化对象的数据:
1 Customer c = new Customer(“TOM”,new HashSet());
2 session.save(c); //customer对象被持久化,并且加入到session的缓存中
3 Long id = c.getId();
4 c = null; //c变量不再引用customer对象
5 //从session缓存中读取customer对象,使c2变量引用customer对象
6 Customer c2 = (Customer)session.load(Customer.class,id);
7 tx.commit(); //缓存中的对象和数据库同步
8 session.close(); //关闭session 清空缓存
9 System.out.println(c2.getName()); //访问customer对象
10 C2 = null; //C2对象不再引用customer对象,customer对象结束生命周期
缓存的作用:
1。减少访问数据库的频率。
2。保证缓存中的对象与数据库中的相关记录保持同步。
Session执行批量操作
可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:
For(int i=0;i<10000;i++){
Session.save(object);
}
这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。
所以应该定期清理session的缓存,也就是flush一下,这样内存才能保证足够的空间。
session.load和 session.get方法的区别:
不同场合的不同解决方案
场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。
场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,
也许存取的是一个很大的视频文件。
两种场合的取数据的方法一样吗?是用load还是用get方法?
延迟加载
类的延迟加载
lazy 为true或者为false
集合的延迟加载
True false extra
extra为更进一步的延迟加载策略。
当调用getStudents()时不会加载hql语句,当加载student的属性的时候才会发出SQL语句。
调用getStudents().size()方法的时候,会触发类似于:
Hibernate: select count(id) from T_Student where classid =? 这样的SQL查询语句
(这是一种很聪明的做法,如果lazy=”true”,getStudents().size()
将会使得hibernate加载所有集合的数据到内存中)。
调用getStudents().contains()方法的时候(即判断是否包含某个对象),
会触发类似于:select 1 from T_Student where classid =? and id =? 这样的SQL查询语句。
单端关联
False proxy no-proxy
proxy:当前对象的単值相关对象只有在调用它的主键外的其他属性的get方法时才加载它。
no-proxy:当前对象的単值相关对象只有在调用它的属性时才加载,需要字节码增强。
Hibernate的检索策略:
1.类级别检索策略:
1、立即检索
2、延迟检索
默认的检索策略是立即检索。在Hibernate映射文件中,通过在<class>上配置 lazy属性来确定检索策略。
对于Session的检索方式,类级别检索策略仅适用于load方法;也就说,对于get、qurey检索,
持久化对象都会被立即加载而不管lazy是false还是true.
一般来说,我们检索对象就是要访问它,因此立即检索是通常的选择。
由于load方法在检索不到对象时会抛出异常(立即检索的情况下),因此我个人并不建议使用load检索;
而由于<class>中的lazy属性还影响到多对一及一对一的检索策略,因此使用load方法就更没必要了。