2019独角兽企业重金招聘Python工程师标准>>>
1. Hibernate检索
1.1. hibernate检索方式说明
Hibernate 提供了以下几种检索对象的方式
- 导航对象图检索方式: 根据已经加载的对象导航到其他对象
- OID 检索方式: 按照对象的 OID 来检索对象
- HQL 检索方式: 使用面向对象的 HQL 查询语言
- QBC 检索方式: 使用 QBC(Query By Criteria) API 来检索对象. 这种 API封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口.
- 本地 SQL 检索方式: 使用本地数据库的 SQL 查询语句
1.2. HQL 检索方式
HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL是使用最广的一种检索方式. 它有如下功能:
- 在查询语句中设定各种查询条件
- 支持投影查询, 即仅检索出对象的部分属性
- 支持分页查询
- 支持连接查询
- 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
- 提供内置聚集函数, 如 sum(), min() 和 max()
- 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
- 支持子查询
- 支持动态绑定参数
HQL检索方式包括以下步骤:
- 通过 Session 的 createQuery() 方法创建一个 Query 对象, 它包括一个 HQL 查询语句. HQL 查询语句中可以包含命名参数
- 动态绑定参数
- 调用 Query 的 list() 方法执行查询语句. 该方法返回 java.util.List 类型的查询结果, 在 List集合中存放了符合查询条件的持久化对象.
Qurey 接口支持方法链编程风格, 它的 setXxx() 方法返回自身实例, 而不是 void 类型
HQL vs SQL:
- HQL 查询语句是面向对象的, Hibernate 负责解析 HQL 查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句. HQL查询语句中的主体是域模型中的类及类的属性
- SQL 查询语句是与关系数据库绑定在一起的. SQL查询语句中的主体是数据库表及表的字段.
HQL的语法类似SQL语法。
1.2.1. 绑定参数
- Hibernate 的参数绑定机制依赖于 JDBC API 中的 PreparedStatement 的预定义 SQL语句功能.
- HQL 的参数绑定由两种形式:
- 按参数名字绑定: 在 HQL 查询语句中定义命名参数, 命名参数以 “:” 开头.
- 按参数位置绑定: 在 HQL 查询语句中用 “?” 来定义参数位置
- 相关方法:
- setEntity(): 把参数与一个持久化类绑定
- setParameter(): 绑定任意类型的参数. 该方法的第三个参数显式指定 Hibernate 映射类型
绑定参数的形式,按参数名称绑定
绑定参数的形式,按参数位置绑定
1.2.2. 使用别名
通过HQL检索一个类的实例时,如果查询语句的其他地方需要引用它,应该为这个类指定一个别名
from Customer as c where c.name=:custname
as 可省略
1.2.3. 排序
- HQL 采用 ORDER BY 关键字对查询结果排序
hql 查询:
Query query = session.createQuery("from Customer c order by c.id");
QBC查询:
Criteria criteria = session.createCriteria(Customer.class);
criteria.addOrder(org.hibernate.criterion.Order.asc(“id"));
1.2.4. 分页查询:
- setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0. 默认情况下, Query从查询结果中的第一个对象开始检索
- setMaxResult(int maxResults): 设定一次最多检索出的对象的数目. 在默认情况下, Query和 Criteria 接口检索出查询结果中所有的对象
1.2.5. 投影查询
- 投影查询: 查询结果仅包含实体的部分属性. 通过 SELECT 关键字实现.
- Query 的 list() 方法返回的集合中包含的是数组类型的元素,每个对象数组代表查询结果的一条记录
- 可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录,使程序代码能完全运用面向对象的语义来访问查询结果集.
- 可以通过 DISTINCT 关键字来保证查询结果不会返回重复元素
使用构造函数
1.2.6. 分组与聚集函数
报表查询用于对数据分组和统计, 与 SQL 一样, HQL 利用 GROUP BY关键字对数据分组, 用 HAVING 关键字对分组数据设定约束条件.
在 HQL 查询语句中可以调用以下聚集函数
- count()
- min()
- max()
- sum()
- avg()
1.2.7. 在映射文件中定义命名查询语句
1.2.8. 其他的查询
动态查询:
session.createCriteria(Customer.class)
.add(Expression.like(“name”,name.toLowerCase()),MatchMode.ANYWHERE)
.add(Expression.eq(“age”,new Integer(11)));
集合过滤:
hql:
createQuery(“from Order o where o.customer=:customer and o.price>100
order by o.price”).setEntity(“customer”,customer).list();
使用集合过滤:
session.createFilter(customer.getOrders(),”where this.price > 100
order by this.price”).list();
子查询:
from Customer c where 1 < (select count(o) from c.orders o);
本地SQL查询:
String sql=“select cs.ID as {c.id},cs.NAME as {c.name},cs.AGE
as {c.age} from CUSTOMERS cs where cs.ID = 1”;
Query query = session.createSQLQuery(sql,”c”,Customer.class);
第二个参数是类的别名,在SQL语句中,每个字段的别名必须位于大括号内。
1.2.9. DML[t1] (数据操作语言)风格的操作
,Hibernate 提供通过 Hibernate 查询语言(HQL)来执行大
批量 SQL 风格的 DML 语句的方法。
UPDATE 和 DELETE 语句的伪语法为:( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?。
要注意的事项:
•在 FROM 子句(from-clause)中,FROM 关键字是可选的
•在 FROM 子句(from-clause)中只能有一个实体名,它可以是别名。如果实体名是别名,那么任何被引用的属性都必须加上此别名的前缀;如果不是别名,那么任何有前缀的属性引用都是非法的。
•不能在大批量 HQL 语句中使用 joins 连接(显式或者隐式的都不行)。不过在WHERE 子句中
可以使用子查询。可以在 where 子句中使用子查询,子查询本身可以包含 join。
•整个 WHERE 子句是可选的。
使用 Query.executeUpdate() 方法执行一个 HQL UPDATE语句
由 Query.executeUpdate() 方法返回的整型值表明了受此操作影响的记录数量
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
1.3. QBC 检索方式
短语 | 含义 |
Restrictions.eq | 等于= |
Restrictions.allEq | 使用Map,使用key/value进行多个等于的判断 |
Restrictions.gt | 大于> |
Restrictions.ge | 大于等于>= |
Restrictions.lt | 小于< |
Restrictions.le | 小于等于<= |
Restrictions.between | 对应sql的between子句 |
Restrictions.like | 对应sql的like子句 |
Restrictions.in | 对应sql的in子句 |
Restrictions.and | and 关系 |
Restrictions.or | or关系 |
Restrictions.sqlRestriction | Sql限定查询 |
1.4. 本地 SQL 检索方式(了解)
SQLQuery sqlquery = session
.createSQLQuery("select {c.*} from CUSTOMERS c where"+
" c.name like :customerName and c.age=:customerAge");
// 动态绑定参数
sqlquery.setString("customerName", “%t%");
sqlquery.setLong("customerAge", 21);
“c”用来引用数据表的别名,例如以上代码中{c.*}表示使用c来作为
customers表别名。 把sql查询返回的关系数据映射为对象
sqlquery.addEntity("c", Customer.class);
// 执行sql select语句,返回查询结果。
List list = sqlquery.list();
1.5. 简单的查询
//使用hql查询
Query query=session.createQuery("from Customer c where c.name = 'tom1' ");
List list=query.list();
//使用qbc查询
主要由Criteria、Criterion接口和Restrictions类组成,他支持在运行时动态生成查询语句。
Criteria criteria=session.createCriteria(Customer.class);
//设定查询条件,每个Criterion实例代表一个查询条件
Criterion cn1=Restrictions.eq("name", "tom1");
criteria.add(cn1);
list=criteria.list();
//方法链编程:
session.createCriteria(Customer.class)
.add(Restrictions.eq("name", "tom1"))
.list();
1.6. HQL和QBC支持的各种运算
运算类型 | HQL运算符 | QBC运算方法 |
比较运算 | = | Restrictions.eq() |
| <> | Restrictions.not(Restrictions.eq()) |
| >= | Restrictions.ge() |
| < | Restrictions.lt() |
| <= | Restrictions.le() |
| is null | Restrictions.isNull() |
| is not null | Restrictions.isNotNull() |
范围运算符 | in | Restrictions.in() |
| not in | Restrictions.not(Restrictions.in()) |
| between | Restrictions.between() |
| not between | Restrictions.not(Restrictions.between()) |
字符串模式匹配 | like | Restrictionsion.like() |
逻辑 | and | Restrictionsion.and()| Restrictionsion.conjunction() |
or | Restrictionsion.or()| Restrictionsion.disjunction() | |
not | Restrictionsion.not() |
1.6.1. 小结
比较方面 | HQL检索 | QBC检索 |
可读性 | 优点:和sql相近,易读 | 将语句肢解成一组criteria,较差 |
功能 | 支持各种查询 | 不支持报表查询和子查询。有限的连接查询 |
查询语句形式 | 基于字符串形式的sql | 更加面向对象 |
何时被解析 | 运行时被解析 | 编译时被解析,更易排错 |
可扩展性 | 不具扩展性 | 用户可扩展criteria接口 |
对动态查询语句的支持 | 支持动态查询,编程麻烦 | 适合动态生成查询语句 |
2. 事务[t2] 和并发
2.1. 数据库的隔离级别[t3]
- 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后,若 T2 回滚, T1读取的内容就是临时且无效的.
- 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段.之后, T1再次读取同一个字段, 值就不同了.
- 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
- 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响, 避免各种并发问题.
- 一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
SQL ANSI SQL 标准定义了 4 种事务隔离级别,级别越高,成本越高:
注:
- Oracle 只支持 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle默认的事务隔离级别为: READ COMMITED
- Mysql 支持4中事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ
2.2. 在 MySql 中设置隔离级别
- 每启动一个 mysql 程序, 就会获得一个单独的数据库连接.每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL默认的隔离级别为 Repeatable Read
- 查看当前的隔离级别:
- 当前连接:SELECT @@tx_isolation;
- 全局:SELECT @@global.tx_isolation;
- 设置当前 mySQL 连接的隔离级别:
- set transaction isolation level read committed;
- 或set session transaction isolation level read uncommitted;
- 设置数据库系统的全局的隔离级别:
- set global transaction isolation level read committed;
2.3. 在 Hibernate 中设置隔离级别
JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
隔离级别 | 对应的整数表示 |
READ UNCOMMITED | 1 |
READ COMMITED | 2 |
REPEATABLE READ | 4 |
SERIALIZEABLE | 8 |
Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation属性来设置事务的隔离级别。例:
hibernate.connection.isolation = 4
注意
- Hibernate不可能改变在受管环境下由应用服务器提供的数据库连接的隔离级别,只能通过改变应用服务器配置的方式来改变.
- 设置隔离级别是全局选项,会影响所有的连接和事务
2.4. 管理session(CurrentSessionContext)
2.4.1. 关于管理Session
- 尽管让程序自主管理 Session 对象的生命周期也是可行的, 但是在实际 Java 应用中, 把管理Session 对象的生命周期交给 Hibernate 管理, 可以简化 Java 应用程序代码和软件架构
- Hibernate 3 自身提供了三种管理 Session 对象的方法
- Session 对象的生命周期与本地线程绑定
- Session 对象的生命周期与 JTA 事务绑定
- Hibernate 委托程序管理 Session 对象的生命周期
- 在 Hibernate 的配置文件中, hibernate.current_session_context_class 属性用于指定Session 管理方式, 可选值包括
- thread: Session 对象的生命周期与本地线程绑定
- jta*: Session 对象的生命周期与 JTA 事务绑定
- managed: Hibernate 委托程序来管理 Session 对象的生命周期
2.4.2. Session 对象的生命周期与本地线程绑定(通过Thread-local传播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 会自动清理 sessionA对象的缓存, 然后提交事务, 关闭 sessionA 对象. 当 threadA 撤销 sessionA对象关联的事务时, 也会自动关闭 sessionA 对象
- 若 threadA 再次调用 SessionFactory 对象的 getCurrentSession() 方法时,该方法会又创建一个新的 Session(sessionB) 对象, 把该对象与 threadA 绑定, 并将sessionB 返回
配置:
- 在hibernate.cfg.xml文件中增加
<!-- 配置session的线程本地化 threadLocal -->
<property name="current_session_context_class">thread</property>
- 不是调用sessionFactory.openSession().而是调用sessionFactory. getCurrentSession().获取session对象.从当前的线程提取session:
- 当前线程如果存在session对象,取出直接使用
- 当前线程如果不存在session对象,获取一个新的session对象和当前的线程绑定
测试代码:
3. 提升性能
3.1. 使用连接池
配置c3p0连接池
* 引入c3p0-0.9.1.jar
3.2. Hibernate的检索策略
3.2.1. hibernate的检索策略简介
检索策略的作用域 | 可选的检索策略 | 默认的检索策略 | 运行时行为受影响的session的检索方法 |
类级别 | 立即 延迟 | 延迟 | load |
关联级别 | 立即 延迟 迫切左外连接检索 | 延迟 | load get |
类级别和关联级别可选的检索策略及默认的检索策略
3.2.2. 区分立即检索和延迟检索
3.2.3. 理解延迟检索中的代理
代理对象的类名如下所示:
Hibernate使用javassist-3.9.0.GA.jar包创建代理。Customer对象代理对象(初始化oid的值,其他的值不初始化)
3.2.4. 初始化延迟检索中的代理
3.2.5. 区分类级别和关联级别的检索
3.2.5.1. 类级别检索策略
- 类级别可选的检索策略包括立即检索和延迟检索, 默认为延迟检索
- 类级别的检索策略可以通过 <class> 元素的 lazy 属性进行设置
- 如果程序加载一个对象的目的是为了访问它的属性, 可以采取立即检索.如果程序加载一个持久化对象的目的是仅仅为了获得它的引用, 可以采用延迟检索
- 无论 <class> 元素的 lazy 属性是 true 还是 false, Session 的 get() 方法及 Query 的 list()方法在类级别总是使用立即检索策略
- 若 <class> 元素的 lazy 属性为 true 或取默认值, Session 的 load()方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例,该代理类实例有如下特征:
- 由 Hibernate 在运行时采用 javassist 工具动态生成
- Hibernate 创建代理类实例时, 仅初始化其 OID 属性
- 在应用程序第一次访问代理类实例的非OID属性时, Hibernate 会初始化代理类实例
3.2.5.2. 关联级别的检索策略
在映射文件中, 用 <set> 元素来配置一对多关联及多对多关联关系. <set> 元素有lazy 和 fetch 属性。
- lazy: 主要决定 orders 集合被初始化的时机. 即到底是在加载 Customer对象时就被初始化, 还是在程序访问 orders 集合时被初始化
- fetch: 取值为 “select” 或 “subselect” 时, 决定初始化 orders 的查询语句的形式;若取值为”join”, 则决定 orders 集合被初始化的时机
- 若把 fetch 设置为 “join”, lazy 属性将被忽略
3.2.5.2.1. 一对多和多对多关联的检索策略(set)
<set> 元素的 lazy 和 fetch 属性
fetch (默认值select) | Lazy (默认值是true) | 策略 |
Join | false | 采用迫切左外联接检索。 |
Join | true | 采用迫切左外联接检索。 |
join | extra | 采用迫切左外联接检索。 |
select | false | 采用立即检索 |
select | True | 采用延迟检索 |
select | extra | 采用延迟检索 c.getOrders().size() 执行 select count(id) from orders where customer_id =? for(Order o:set){ o.getOrderNumber();} 将执行: select customer_id , id,order_number ,price from orders where customer_id=? |
subselect | false/true/extra 也分为3中情况 | 嵌套子查询(检索多个customer对象时) Lazy属性决定检索策略) select customer_id,order_number,price from orders where customer_id in (select id from customers) |
3.2.5.2.2. 延迟检索和增强延迟检索
- 在延迟检索(lazy 属性值为 true) 集合属性时, Hibernate在以下情况下初始化集合代理类实例
- 应用程序第一次访问集合属性: iterator(), size(), isEmpty(), contains()等方法
- 通过 Hibernate.initialize() 静态方法显式初始化
- 增强延迟检索(lazy 属性为 extra): 与 lazy=“true” 类似.主要区别是增强延迟检索策略能进一步延迟 Customer 对象的 orders集合代理实例的初始化时机:
- 当程序第一次访问 orders 属性的 iterator() 方法时, 会导致 orders集合代理类实例的初始化
- 当程序第一次访问 order 属性的 size(), contains() 和 isEmpty() 方法时, Hibernate不会初始化 orders 集合类的实例, 仅通过特定的 select 语句查询必要的信息,不会检索所有的 Order 对象
3.2.5.2.3. 用带子查询的 select 语句整批量初始化 orders 集合(fetch属性为“subselect”)
- <set> 元素的 fetch 属性: 取值为 “select” 或 “subselect” 时, 决定初始化 orders的查询语句的形式; 若取值为”join”, 则决定 orders 集合被初始化的时机.默认值为 select
- 当 fetch 属性为 “subselect” 时
- 假定 Session 缓存中有 n 个 orders 集合代理类实例没有被初始化, Hibernate能够通过带子查询的 select 语句, 来批量初始化 n 个 orders 集合代理类实例
3.2.5.2.4. 迫切左外连接检索(fetch 属性值设为 “join”)
当 fetch 属性为 “join” 时:
- 检索 Customer 对象时, 会采用迫切左外连接(通过左外连接加载与检索指定的对象关联的对象)策略来检索所有关联的 Order 对象
- lazy 属性将被忽略
- Query 的list() 方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用延迟或立即加载策略
3.2.5.3. 多对一和一对一关联的检索策略
<many-to-one> 元素也有一个 lazy 属性和 fetch 属性
fetch (默认值select) | Lazy (默认值是proxy) | 策略 |
Join | false | 采用迫切左外联接检索。 |
Join | proxy | 采用迫切左外联接检索。 |
join | no-proxy | 采用迫切左外联接检索。 |
select | false | 采用立即检索 |
select | proxy | 1. 如果对端Customer.hbm.xml中类级别的检索是立即检索 则为立即检索 2. 如果对端Customer.hbm.xml中类级别的检索是延迟检索 则为延迟检索 |
select | no-proxy | ----(不研究[t4] ) |
- 和 <set> 一样, <many-to-one> 元素也有一个 lazy 属性和 fetch 属性.
- 若 fetch 属性设为 join, 那么 lazy 属性被忽略
- 迫切左外连接检索策略的优点在于比立即检索策略使用的 SELECT 语句更少.
- 无代理延迟检索需要增强持久化类的字节码才能实现
- Query 的 list 方法会忽略映射文件配置的迫切左外连接检索策略, 而采用延迟检索策略
- 如果在关联级别使用了延迟加载或立即加载检索策略, 可以设定批量检索的大小,以帮助提高延迟检索或立即检索的运行性能.
- Hibernate 允许在应用程序中覆盖映射文件中设定的检索策略.
3.2.6. 比较三种检索策略
检索策略 | 优点 | 缺点 | 优先考虑使用的场合 |
立即检索 | 对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 | (1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 | (1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存 |
延迟检索 | 由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 | 应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 | (1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象 |
迫切左外连接检索 | (1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 | (1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 | (1)多对一或一对一关联 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
3.3. 二级缓存
3.3.1. Hibernate的缓存
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
- Hibernate中提供了两个级别的缓存
- 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate 管理的,一般情况下无需进行干预
- 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
- SessionFactory 的缓存可以分为两类:
- 内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中,映射元数据是映射文件中数据的复制, 而预定义 SQL 语句时 Hibernate根据映射元数据推到出来的. 该内置缓存是只读的.
- 外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘
3.3.2. hibernate二级缓存的结构
3.3.3. 理解二级缓存的并发访问策略
- 两个并发的事务同时访问持久层的缓存的相同数据时, 也有可能出现各类并发问题.
- 二级缓存可以设定以下 4 种类型的并发访问策略, 每一种访问策略对应一种事务隔离级别
- 只读型(Read-Only):提供 Serializable 数据隔离级别, 对于从来不会被修改的数据,可以采用这种访问策略
- 读写型(Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读
- 非严格读写(Nonstrict-read-write): 不保证缓存与数据库中数据的一致性. 提供 Read Uncommited 事务隔离级别, 对于极少被修改, 而且允许脏读的数据, 可以采用这种策略
- 事务型(Transactional): 仅在受管理环境下适用. 它提供了 Repeatable Read事务隔离级别. 对于经常读但是很少被修改的数据, 可以采用这种隔离类型,因为它可以防止脏读和不可重复读
- 适合放入二级缓存中的数据:
- 很少被修改
- 不是很重要的数据, 允许出现偶尔的并发问题
- 不适合放入二级缓存中的数据:
- 经常被修改
- 财务数据, 绝对不允许出现并发问题
- 与其他应用数据共享的数据
3.3.4. 缓存提供的供应商
- Hibernate 的二级缓存是进程或集群范围内的缓存, 缓存中存放的是对象的散装数据
- 二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
- EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 对 Hibernate的查询缓存提供了支持
- OpenSymphony OSCache:可作为进程范围内的缓存,存放数据的物理介质可以使内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate的查询缓存提供了支持
- SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
- JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
- 4 种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)
3.3.5. 配置ehcache缓存
3.3.5.1. 配置步骤
- 拷贝ehcache-1.5.0.jar到当前工程的lib目录下
- 开启二级缓存
<property name="hibernate.cache.use_second_level_cache">true</property>
- 指定缓存的供应商
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
- 指定使用二级缓存的类
- 方法一 在使用类的*.hbm.xml配置
选择需要使用二级缓存的持久化类, 设置它的二级缓存的并发访问策略, <class> 元素的cache 子元素表明 Hibernate 会缓存对象的简单属性, 但不会缓存集合属性,若希望缓存集合属性中的元素, 必须在 <set> 元素中加入 <cache> 子元素
- 方法二 在hibernate.cfg.xml文件中配置(建议)
<!-- 指定使用二级缓存的类 放在maping下面 -->
<!-- 配置类级别的二级缓存 -->
<class-cache class="cn.itcast.c3p0.Customer" usage="read-write"/>
<class-cache class="cn.itcast.c3p0.Order" usage="read-write"/>
<!-- 配置集合级别的二级缓存 -->
<collection-cache collection="cn.itcast.c3p0.Customer.orders"
usage="read-write"/>
- 配置ehcache默认的配置文件ehcache.xml(名字固定)(放在类路径下)
ehcache.xml文件
<diskStore path="D:\cache" />
<cache name=" “
maxElementsInMemory="100“
eternal="false"
timeToIdleSeconds="300“
timeToLiveSeconds="600"
overflowToDisk="true“
diskPersistent="false“ />
</ehcache>
3.3.5.2. ehcache.xml文件配置说明
- <diskStore>:指定一个目录, 当 EHCache 把数据写到硬盘上时,将把数据写到这个文件目录下. 默认是C:\WINDOWS\Temp
- <defaultCache>: 设置缓存的默认数据过期策略
- <cache> 设定具体的命名缓存的数据过期策略
- 每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
- cache元素的属性
- name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
- maxElementsInMemory :设置基于内存的缓存中可存放的对象最大数目
- eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和timeToLiveSeconds属性; 默认值是false
- timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
- timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于timeToIdleSeconds 属性值 - overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
- diskPersistent 当jvm结束时是否持久化对象 true false 默认是false
- diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
3.3.5.3. 测试
需要引入两个jar包
在srping下能找到
..\lib\concurrent\backport-util-concurrent.jar
..\lib\commons-logging.jar
3.3.5.3.1. 测试二级缓存和散装数据
3.3.5.3.2. 测试一级缓存更新数据会同步到二级缓存
3.3.5.3.3. 测试二级缓存的数据存放到临时目录
3.3.6. 时间戳缓存区域
默认的查询缓存区域以及用户自定义的查询缓存区域都用于存放查询结果。而时间戳缓存区域存放了对与查询结果相关的表进行插入、更新或删除操作的时间戳。Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期。所以,当应用程序对数据库的相关数据做了修改,Hibernate会自动刷新缓存的查询结果。
- Hibernate 提供了和查询相关的缓存区域:
- 时间戳缓存区域: org.hibernate.cahce.UpdateTimestampCache
- 时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
- T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
- T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在UpdateTimestampCache 区域.
- T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果,重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从QueryCache 中获得查询结果
测试时间戳缓存区域
3.3.7. Query 接口的 iterator() 方法
- Query 接口的 iterator() 方法
- 同 list() 一样也能执行查询操作
- list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
- Iterator() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
- 当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID的对象, 如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select语句到数据库中加载特定的实体对象
- 大多数情况下, 应考虑使用 list() 方法执行查询操作. iterator()方法仅在满足以下条件的场合, 可以稍微提高查询性能:
- 要查询的数据表中包含大量字段
- 启用了二级缓存, 且二级缓存中可能已经包含了待查询的对象
3.3.8. 查询缓存
- 对于经常使用的查询语句, 如果启用了查询缓存, 当第一次执行查询语句时, Hibernate会把查询结果存放在查询缓存中. 以后再次执行该查询语句时,只需从缓存中获得查询结果, 从而提高查询性能
- 查询缓存使用于如下场合:
- 应用程序运行时经常使用查询语句
- 很少对与查询语句检索到的数据进行插入, 删除和更新操作
- 使用查询缓存的步骤
- 配置二级缓存, 因为查询缓存依赖于二级缓存
- 在 hibernate 配置文件中启用查询缓存
<property name="cache.use_query_cache">true</property> - 对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable(true) 方法
[t1] [t1]DML = Data Manipulation Language,数据操纵语言,命令使用户能够查询数据库以及操作已有数据库中的数据的计算机语言。具体是指是SELECT查询、UPDATE更新、INSERT插入、DELETE删除。
[t2]事务的概念
事务可以看作是由对数据库的若干操作组成的一个单元,这些操作要么都完成,要么都取消,从而保证数据满足一致性的要求。事务的一个典型例子是银 行中的转帐操作,帐户A把一定数量的款项转到帐户B上,这个操作包括两个步骤,一个是从帐户A上把存款减去一定数量,二是在帐户B上把存款加上相同的数 量。这两个步骤显然要么都完成,要么都取消,否则银行就会受损失。显然,这个转帐操作中的两个步骤就构成一个事务。
数据库中的事务还有如下ACID特征。
ACID分别是四个英文单词的首写字母,这四个英文单词是Atomicity、Consistency、Isolation、Durability,分别翻译为原子性、一致性、隔离性、持久性。
原子性:指事务中的操作,或者都完成,或者都取消。
一致性:指事务中的操作保证数据库中的数据不会出现逻辑上不一致的情况,一致性一般会隐含的包括在其他属性之中。
隔离性:指当前的事务与其他未完成的事务是隔离的。在不同的隔离级别下,事务的读取操作,可以得到的结果是不同的。
持久性:指对事务发出COMMIT命令后,即使这时发生系统故障,事务的效果也被持久化了。与此相反的是,当在事务执行过程中,系统发生故障,则事务的操作都被回滚,即数据库回到事务开始之前的状态。
对数据库中的数据修改都是在内存中完成的,这些修改的结果可能已经写到硬盘也可能没有写到硬盘,如果在操作过程中,发生断电或系统错误等故障,数据库可以保证未结束的事务对数据库的数据修改结果即使已经写入磁盘,在下次数据库启动后也会被全部撤销;而对于结束的事务,即使其修改的结果还未写入磁 盘,在数据库下次启动后会通过事务日志中的记录进行“重做”,即把丢失的数据修改结果重新生成,并写入磁盘,从而保证结束事务对数据修改的永久化。这样也 保证了事务中的操作要么全部完成,要么全部撤销。
[t3]在SQL92标准中,事务隔离级别分为四种,分别为:Read Uncommitted、Read Committed、Read Repeatable、Serializable,其中Read Uncommitted与Read Committed为语句级别的,而Read Repeatable与Serializable是针对事务级别的。
在Oracle和SQL Server中设置事务隔离级别的语句是相同的,都使用SQL92标准语法,即:
Set Transaction Isolation Level Read Committed
上面示例中的Read Committed可以被替换为其他三种隔离级别中的任意一种。
在SQL Server中提供了所有这四种隔离级别。
Read Uncommitted:一个会话可以读取其他事务未提交的更新结果,如果这个事务最后以回滚结束,这时的读取结果就可能是错误的,所以多数的数据库应用都不会使用这种隔离级别。
Read Committed:这是SQL Server的缺省隔离级别,设置为这种隔离级别的事务只能读取其他事务已经提交的更新结果,否则,发生等待,但是其他会话可以修改这个事务中被读取的记 录,而不必等待事务结束,显然,在这种隔离级别下,一个事务中的两个相同的读取操作,其结果可能不同。
Read Repeatable:在一个事务中,如果在两次相同条件的读取操作之间没有添加记录的操作,也没有其他更新操作导致在这个查询条件下记录数增多,则两次 读取结果相同。换句话说,就是在一个事务中第一次读取的记录保证不会在这个事务期间发生改变。SQL Server是通过在整个事务期间给读取的记录加锁实现这种隔离级别的,这样,在这个事务结束前,其他会话不能修改事务中读取的记录,而只能等待事务结 束,但是SQL Server不会阻碍其他会话向表中添加记录,也不阻碍其他会话修改其他记录。
Serializable:在一个事务中,读取操作的结果是在这个事务开始之前其他事务就已经提交的记录,SQL Server通过在整个事务期间给表加锁实现这种隔离级别。在这种隔离级别下,对这个表的所有DML操作都是不允许的,即要等待事务结束,这样就保证了在一个事务中的两次读取操作的结果肯定是相同的。