文章目录
事务概念
事务是一步或几步基本操作组成的逻辑执行单元,这些基本操作作为一个整体执行单元,它们要么全部执行,要么全部取消,绝不能仅仅执行一部分。一般而言,每次用户请求,对应一个业务逻辑方法,一个业务逻辑方法往往具有逻辑上的原子性,应该使用事务。例如一个转账操作,对应修改两个账户的余额,这两个账户的修改要么同时生效,要么同时取消一同时生效是转账成功,同时取消是转账失败,但不可只修改其中一个账户,那将破坏数据库的完整性。
事务特性(ACID)
- 原子性(atomicity): 事务中不可分割最小逻辑执行体
- 一致性(consistency):如果系统运行发生中断,某个事务尚未完成而被迫中断, 而该未完成的事务对数据库所做的修改已被写入数据库, 此时,数据库就处于一种不正确的状态。比如银行在两个账户之间转账:从A账户向B账户转入1000元。系统先减少A账户的1000元, 然后再为B账户增加1000元。如果全部执行成功,数据库处于一致性状态。如果仅执行完A账户金额的修改, 而没有增加B账户的金额,则数据库就处于不一致性状态。 因此,一致性是通过原子性来保证的。
- 隔离性(isolation):事务之间互相不影响。
- 持续性(durability):也称之为持久性,事务一旦提交,就保存到数据库。
Session与事务
Tip
线程安全:在多线程使用该对象的时候,底层是通过锁机制去保证,每次只有一个对象执行。
线程不安全:在多线程使用该对象的时候,多个线程对同时执行,会出现逻辑错乱。
SessionFactory 对象的创建代价很高,它是线程安全的对象,被设计成可以被所有线程所共享。通常,SessionFactory 会在应用程序启动时创建,一旦创建 了SessionFactory 将不会轻易关闭, 只有当应用退出时才关闭SessionFactory。
Session对象是轻量级的,它也是线程不安全的。下面是比较的方法去获取session:
public static final ThreadLocal<Session> session
= new ThreadLocal<Session>();
public static Session currentSession()
throws HibernateException
{
Session s = session.get();
// 如果该线程还没有Session,则创建一个新的Session
if (s == null)
{
s = sessionFactory.openSession();
// 将获得的Session变量存储在ThreadLocal变量session里
// 相当于每一个线程都绑定一个session,你每次线程来拿的时候,都用自己的线程的session,怎么会冲突,就不需要线程同步
session.set(s);
}
return s;
}
对于单个业务进程、单个的工作单元而言,Session只被使用一次。创建Session 时,并不会立即打开与数据库之间的连接,只有需要进行数据库操作时,Session才会获取JDBC连接。因此,打开和关闭Session,并不会对性能造成很大的影响。甚至即使无法确定一个请求是否需要数据访问,也可以打开Session对象,因为如果不进行数据库访问,Session不会获取JDBC连接。
Hibernate 建议一次事务请求,对应一个Session。
长事务
事务持续时间很长,在事务中有多个用户操作。
对于长事务而言:一个比较差的做法就是在用户与服务器会话(session, 通常就是一次HTTP Session)期间,应用程序保持 Session与数据库事务打开,并保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操作。这种数据库锁定会导致应用程序无法扩展并发用户的数量。每次HTTP Session对应一次Hibernate Session的模式会导致应用程序无法扩展并发用户的数量,因此不推荐使用。
Hibernate 处理长事务的三种方式:
- 自动版本化: Hibernate能够自动进行乐观并发控制,如果在用户处理的过程中持久化实体发生并发修改,
Hibernate能够自动检测到。 - 脱管对象:如果采用每次用户请求对应一次Session的模式,那么,前面载入的实例在用户处理的过程中,始终与Session脱离,处于脱管状态。Hibernate允许把脱管对象重新关联到Session上,并且对修改进行持久化。在这种模式下,自动版本化被用来隔离并发修改。这种模式也被称为使用脱管对象的每次请求对应一个Hibernate Session。
- 长生命周期Session: Session可以在数据库事务提交之后,断开和底层的JDBC连接。当新的客户端请求到来时,它又重新连接上底层的JDBC连接。这种模式被称为每个应用程序事务对应一个Session。因为应用程序事务是相当长(跨越多个用户请求)的,所以也被称为长生命周期Session。
在session 关闭之前,如果集合属性,,由于懒加载的原因,一定要装载所有的数据,不然你在后面使用的时候,会报异常。
上下文相关的Session
Hibernate 应用使用Session时都用到了HibermateUtil 工具类,因为该工具类可以保证将线程不安全的Session绑定限制在当前线程内,也就是实现一种“上下文相关的”Session。
Hibernate 管理上下文的三种策略
- org.hibernate.context.JTASessionContext:根据JTA来跟踪和界定上下文相关Session,这和最早的仅支持JTA的方法是完全一样的。
- org.hibernate.context.ThreadLocalSessionContext:通过当前正在执行的线程来跟踪和界定上下文相关Session,也就是和前面的HibernateUtil的Session维护模式相似。
- org.hibernate.context.ManagedSessionContext:通过当前执行的线程来跟踪和界定上下文相关Session。但是程序需要使用这个类的静态方法将Session实例绑定、取消绑定,它并不会自动打开、fush 或者关闭任何Session。
如果使用ThreadLocalSessionContext 策略,Hibermate 的Session会随着getCurrentSession(方法自动打开,并随着事务提交自动关闭,非常方便。对于在容器中使用Hibermate 的场景而言,通常会采用第种方式,对于独立的Hibernate应用而言,通常会采用第二种方式。
我们通常在Hibernate 配置文件中,可以通过hibernate.current_session_context_class去配置该属性。
事件机制
Hibermate执行持久化过程中,应用程序无法参与其中。
通过事件框架,Hibermate 允许应用程序能响应特定的内部事件,从而允许实现某些通用的功能,或者对Hibernate功能进行扩展。
Hibernate的事件框架组成
- 拦截器机制:对于特定动作拦截,回调应用中的特定动作。
- 事件系统:重写Hibernate的事件监听器。
拦截器机制
通过实现Interceptor接口,可以从Session中回调应用程序的特定方法,这种回调机制可让应用程序在持久化对象被保存、更新、删除或加载之前,检查并修改其属性。
使用拦截器按如下步骤进行:
- 定义实现Interceptor接口的拦截器类。
- 通过Session启用拦截器,或者通过Configuration 启用全局拦截器。
在Hibernate 下有Interceptor 接口和EmptyInterceptor 类,EmptyInterceptor 就像是一个适配器类,因为Interceptor 要实现的方法比较多,没有必要全写,所以可以使用EmptyInterceptor 。
public class MyHibernateTestInterceptor extends EmptyInterceptor {
private int count;
//注意这里实现的是Hibernate 下 的拦截器
@Override
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
// TODO Auto-generated method stub
count++;
return super.onSave(entity, id, state, propertyNames, types);
}
@Override
public void afterTransactionCompletion(Transaction tx) {
// TODO Auto-generated method stub
System.out.println(count);
}
}
该拦截器实现简单,在save 实例的时候,统计save 次数。
拦截器种类
- 打开一个带局部拦截器的Session,这个拦截器只是针对该session有效
Session ss = sf.withOptions().interceptor(new MyHibernateTestInterceptor()).openSession();//这里必须要这样写,用链式调用创建session,否则拦截器无效。而且这种,每次都要创建一个sessionfactory,用同一个也无效。
- 通过Configuration的setInterceptor(Interceptor in)方法设置全局拦截器。
事件系统
事件系统可以替代拦截器
基本上,Session 接口的每个方法都有对应的事件,比如LoadEvent、 FlushEvent 等。当Session调用某个方法时,Hibernate Session会生成对应的事件,并激活对应的事件监听器。
使用事件系统按如下步骤进行:
- 实现自己的事件监听器类。
- 注册自定义事件监听器,代替系统默认的事件监听器。
public class MyHibernateTestListener extends DefaultSaveEventListener {
@Override
public void onSaveOrUpdate(SaveOrUpdateEvent arg0) {
// TODO Auto-generated method stub
System.out.println("在save 触发的事件");
super.onSaveOrUpdate(arg0);
}
}
注意:扩展用户自定义监听器时,别忘了在方法中调用父类的对应方法, 否则Hibernate 默认的持久化行为都会失效。 因为这些持久化行为本身就是通过事件来完成的。
注册事件监听器,(所有的事件监听器全在这注册):
public class RegisterService implements Integrator {
@Override
public void disintegrate(SessionFactoryImplementor arg0,
SessionFactoryServiceRegistry arg1) {
// TODO Auto-generated method stub
}
@Override
public void integrate(Metadata arg0, SessionFactoryImplementor arg1,
SessionFactoryServiceRegistry arg2) {
// TODO Auto-generated method stub
EventListenerRegistry eventListenerRegistry =
arg2.getService(EventListenerRegistry.class);
eventListenerRegistry.getEventListenerGroup(EventType.SAVE)
.appendListener(new MyHibernateTestListener());
//EventType 事件枚举类型,包含所有的事件类型
}
}
在代码中应用注册事件类
BootstrapServiceRegistry bootstrapRegistry =
new BootstrapServiceRegistryBuilder()
.applyIntegrator(new RegisterService())
.build();
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapRegistry)
.configure().build();
Metadata metadata = new MetadataSources( serviceRegistry ).getMetadataBuilder().build();
SessionFactory sf = metadata.getSessionFactoryBuilder().build();
还有一种配置事件的方法,在Hibernate配置文件中:
- 拓展系统默认xx事件监听器。
- 在hibernate 配置文件中配置。
<listener type="save" class="com.example.test.listener.MyHibernateTestListener"/>//这里type 自己可以查看hibernate 事件类型,实际上是保存在一个map 中,key 就是这里type ,value 就是hibernate自己事件类,而我们之间拓展默认的事件,一旦注册,就会使用我们默认的,所以上面注意的那句话,就是保证原来hibernate 不会丢了,知道不?