您的位置:首页 > 编程语言 > Java开发

Spring @Transactional 声明式事务管理 getCurrentSession

2014-10-11 11:17 633 查看
Spring @Transactional声明式事务管理 getCurrentSession

在Spring @Transactional声明式事务管理的配置中,hibernate.current_session_context_class=thread…

这一句是不能加的…加了就会出错,具体错误为:

org.hibernate.HibernateException: save is not valid without active transaction

at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:341)

at com.sun.proxy.$Proxy12.save(Unknown Source)

at cn.javass.h4.hello.UserDao.save(UserDao.java:18)


..那为什么不能加呢?

那是因为在Spring声明式事务管理中, current Session是绑定到SpringSessionContext中的,而不是ThreadLocalSessionContext中的

先结合Hibernate4.0说说:

从Hibernate3.1开 始,SessionFactory.getCurrentSession()的后台实现是可拔插的。因此,我们引入了新的扩展接口 (org.hibernate.context.spi.CurrentSessionContext)和

新的配置参数(hibernate.current_session_context_class),以便对什么是“当前session”的范围和上下文(scope and context)的定义进行拔插。

它定义 了单一的方法,currentSession(),特定的实现用它来负责跟踪当前的上下文session, SessionFactory.getCurrentSession()通过调用sessionContext.currentSession()方法获得当前session。

首先我们看看org.hibernate.context.spi.CurrentSessionContext

这个接口仅有一个方法:

SessioncurrentSession()

throws HibernateException

Retrieve thecurrent session according to the scoping defined by this implementation.

currentSession()表示 根据当前CurrentSessionContext的实现及定义返回”当前的Session”

这个接口…Hibernate中有3个类实现了这个接口

All Known Implementing Classes:

JTASessionContext, ManagedSessionContext, ThreadLocalSessionContext

1: org.hibernate.context.internal.ThreadLocalSessionContext - 当前session通过当前执行的线程来跟踪和界定。

2: org.hibernate.context.internal.JTASessionContext- 当前session根据JTA来跟踪和界定。这和以前的仅支持JTA的方法是完全一样的。

3: org.hibernate.context.internal.ManagedSessionContext..

Spring为事务管理,也实现了此接口:

1: org.springframework.orm.hibernate4.SpringSessionContext– 当前Session根据Spring和事务管理器来跟踪和界定.

这几种实现都提供了“每数据库事务对应一个session”的编程模型,也称作每次请求一个session。Hibernate session的起始和终结由数据库事务的生存来控制。

hibernate.current_session_context_class 配置参数定义了应该采用哪个org.hibernate.context.spi.CurrentSessionContext实现。

一般而言,此参数的值指明了要使用的实 现类的全名,但那两个内置的实现可以使用简写,即"jta"和"thread"。

hibernate.current_session_context_class=thread

实质是:

hibernate.current_session_context_class= org.hibernate.context.internal.ThreadLocalSessionContext

同理:

hibernate.current_session_context_class=jta

实质是:

hibernate.current_session_context_class= org.hibernate.context.internal.JTASessionContext

而在Spring @Transactional声明式事务管理,”currentSession”的定义为: 当前被 Spring事务管理器 管理的Session,此时应配置:

hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext

spring 整合hibernate管理事务后,由Spring的TransactionManager管理事务后, currentSession是绑定到SpringSessionContext的,而不是thread。

此时hibernate.current_session_context_class应该是SpringSessionContext,而它又会在使用LocalSessionFactoryBean时自动的设置。

所以就不需要你去设置current_session_context_class

- - - -- -

下面我们来分析一下SessionFactoryImpl, org.hibernate.context.spi.CurrentSessionContext

org.hibernate.context.internal.ThreadLocalSessionContext

org.springframework.orm.hibernate4.SpringSessionContext

这些类的源代码

1: 分析sessionFactory.getCurrentSession() 我们跟进去

来到SessionFactoryImpl.getCurrentSession()方法:

[java] view
plaincopy

public final class SessionFactoryImpl

implements SessionFactoryImplementor {

. . .

private final transient CurrentSessionContext currentSessionContext;

. . .

public Session getCurrentSession() throws HibernateException {

if ( currentSessionContext == null ) {

throw new HibernateException( "No CurrentSessionContext configured!" );

}

return currentSessionContext.currentSession();

}

. . .

}

SessionFactoryImpl 的currentSessionContext属性的实际类型就是

由hibernate.current_session_context_class决定的…

2:首先设置: hibernate.current_session_context_class= org.hibernate.context.internal.ThreadLocalSessionContext

到这一句,currentSessionContext.currentSession()跟进去

[java] view
plaincopy

public class ThreadLocalSessionContext implements CurrentSessionContext {

. . .

private static final ThreadLocal<Map> context = newThreadLocal<Map>();

. . .

//打开一个”事务提交后自动关闭”的Session

protected Session buildOrObtainSession() {

return factory.withOptions()

.autoClose( isAutoCloseEnabled() )

.connectionReleaseMode( getConnectionReleaseMode() )

.flushBeforeCompletion( isAutoFlushEnabled() )

.openSession();

}

public final Session currentSession() throws HibernateException {

//从线程局部量context中尝试取出已经绑定到线程的Session

Session current = existingSession( factory );

//如果没有绑定到线程的Session

if (current == null) {

//打开一个”事务提交后自动关闭”的Session

current = buildOrObtainSession();

current.getTransaction().registerSynchronization(buildCleanupSynch() );

// wrap the session in thetransaction-protection proxy

if ( needsWrapping( current ) ) {

current = wrap( current );

}

//将得到的Session绑定到线程中:即以<SessionFactory,Session>键值对方式设置到线程局部量context

doBind( current, factory );

}

return current;

}

. . .

}

现在对于hibernate.current_session_context_class= thread时的getCurrentSession()就很清楚了:

1:尝试取出绑定到线程的Session

2:如果没有,则开启一个”事务提交后自动关闭”的Session,并将此Session加入到ThreadLocal的Map中.

3:返回Session

3:然后再分析:hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext

[java] view
plaincopy

Public UserService

{

@Transactional

public void addUser(User user) throws Exception

{

Session session = sessionFactory.getCurrentSession();

session.save(user);

}

}

因为加入了@Transactional,执行addUser()方法时,Spring的TransactionManager会自动 Open Sesion, 自动 开启事务,并且 将此Sesion绑定到SpringSessionContext(实际上是TransactionSynchronizationManager的ThreadLocal的Map)中..

然后到SessionFactoryImpl.getCurrentSesssion()的currentSessionContext.currentSession()这一句,跟进去

[java] view
plaincopy

public class SpringSessionContext implements CurrentSessionContext {

private final SessionFactoryImplementor sessionFactory;

- - - - - -

public Session currentSession() throws HibernateException {

//关键就是这一句,Spring实际上会去TransactionSynchronizationManager里查找”currentSession”

Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);

if (value instanceof Session) {

return (Session) value;

}

else if (value instanceof SessionHolder) {

SessionHolder sessionHolder = (SessionHolder) value;

Session session = sessionHolder.getSession();

if (TransactionSynchronizationManager.isSynchronizationActive()&&

!sessionHolder.isSynchronizedWithTransaction()) {

TransactionSynchronizationManager.registerSynchronization(

new SpringSessionSynchronization(sessionHolder, this.sessionFactory));

sessionHolder.setSynchronizedWithTransaction(true);

FlushMode flushMode = session.getFlushMode();

if (FlushMode.isManualFlushMode(flushMode)&&

!TransactionSynchronizationManager.isCurrentTransactionReadOnly()){

session.setFlushMode(FlushMode.AUTO);

sessionHolder.setPreviousFlushMode(flushMode);

}

}

return session;

}

else if (this.jtaSessionContext != null) {

Session session = this.jtaSessionContext.currentSession();

if (TransactionSynchronizationManager.isSynchronizationActive()){

TransactionSynchronizationManager.registerSynchronization(newSpringFlushSynchronization(session));

}

return session;

}

else {

throw new HibernateException("No Session found for current thread");

}

}

}

Object value =TransactionSynchronizationManager.getResource(this.sessionFactory); 关键是这一句,跟进去:

[java] view
plaincopy

public abstract class TransactionSynchronizationManager {

. . .

private static final ThreadLocal<Map<Object, Object>> resources;

public static Object getResource(Object key) {

Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

//在ThreadLocal的属性resources里查找Session, resources里以<SessionFactory,SessionHolder>或 <SessionFactory,Session>的键值对存放到ThreadLocal的Map中

Object value = doGetResource(actualKey);

if (value != null && logger.isTraceEnabled()) {

logger.trace("Retrievedvalue [" + value + "] for key [" + actualKey + "] bound to thread [" +

Thread.currentThread().getName() + "]");

}

return value;

}

. ..

}

根据以上代码,我们可以发现,如果我们通过@Transactional注解配置事务,那么SpringSessionContext中就不会存在当前的session, Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);

这个值会是null,这个时候Spring 就会返回"No Session found for current thread"。所以对于SpringSessionContext
来说, @Transactional注解(或者通过tx标签拦截器等其他方式配置的事务应该也起了同样的作用,未验证)很关键, getCurrentSession()在SpringSessionContext的情况下(默认情况)必须配置事务才有效。

现在对于hibernate.current_session_context_class= org.springframework.orm.hibernate4.SpringSessionContext时的getCurrentSession()就很清楚了:

1: @Transactional声明的方法执行时,Spring的TransactionManager会自动Open Sesion,自动开启事务,并且将此Sesion绑定到SpringSessionContext(实际上是TransactionSynchronizationManager的ThreadLocal的Map)中..

2:SessionFactory.getCurrentSession()方法执行时,调用SpringSessionContext.currentSession()从TransactionSynchronizationManager的上下文中查找 当前的Session



3:找到后返回当前的Session,找不到,则返回HibernateException("No Sessionfound for current thread")

PS: 从中,我们也知道了,执行SessionFactoryImpl.openSession()时,只是简单地new 一个SessionBuilder,然后调用SessionBuilder.openSession(),得到的Session是不会绑定到任何 org.hibernate.context.spi.CurrentSessionContext 在上下文中的.



////////////////////////////////////////////////////////////////---------------------------------------------------------------------------------------------------------------------------------------

总结: hibernate.current_session_context_class=thread(org.hibernate.context.internal.ThreadLocalSessionContext)

与 hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext 时的SessionFactory.getCurrentSession()的不同之处在于:

前者在ThreadLocalSessionContext里的线程局部的Map中查找Session,

而后者在SpringSessionContext的上下文(TransactionSynchronizationManager里的线程局部的Map)中查找...

最终,你会发觉,无论是ThreadLocalSessionContext 或 SpringSessionContext 查找的"currentSession",都是以类似键值对<SessionFactory,Session>的形式存放到ThreadLocal的Map中,也就是说这两者的上下文都是一个ThreadLocal的Map...查找时以SessionFactory为键来查找对应的Session,所以在同一线程中,一个SessionFactory只能有一个currentSession
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: