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

【Spring实战】----源码解析SessionFactory及Session的管理及getCurrentSession的使用

2016-12-01 17:23 537 查看
在上一篇Hibernate5集成中当使用sessionFactory.getCurrentSession()时会报错Could not obtain transaction-synchronized Session for current thread,本篇就从源码角度解析下sessionFactory.getCurrentSession()。

一、Contextual sessions(session上下文)

先看下hibernate官方文档对session上下文的解释:
8.4. Contextual sessions
Most applications using Hibernate need some form of contextual session, where a given session is in effect throughout the scope of a given context. However, across applications the definition of what constitutes a context is typically different; different contexts define different scopes to the notion of current. Applications using Hibernate prior to version 3.0 tended to utilize either home-grown ThreadLocal-based contextual sessions, helper classes such as HibernateUtil, or utilized third-party frameworks, such as Spring or Pico, which provided proxy/interception-based contextual sessions.
Starting with version 3.0.1, Hibernate added the SessionFactory.getCurrentSession() method. Initially, this assumed usage of JTA transactions, where the JTA transaction defined both the scope and context of a current session. Given the maturity of the numerous stand-alone JTA TransactionManager implementations, most, if not all, applications should be using JTA transaction management, whether or not they are deployed into a J2EE container. Based on that, the JTA-based contextual sessions are all you need to use.
However, as of version 3.1, the processing behind SessionFactory.getCurrentSession() is now pluggable. To that end, a new extension interface, org.hibernate.context.spi.CurrentSessionContext, and a new configuration parameter, hibernate.current_session_context_class, have been added to allow pluggability of the scope and context of defining current sessions.
See the Javadocs for the org.hibernate.context.spi.CurrentSessionContext interface for a detailed discussion of its contract. It defines a single method, currentSession(), by which the implementation is responsible for tracking the current contextual session. Out-of-the-box, Hibernate comes with three implementations of this interface:
org.hibernate.context.internal.JTASessionContext
current sessions are tracked and scoped by a JTA transaction. The processing here is exactly the same as in the older JTA-only approach. See the Javadocs for more details.
•org.hibernate.context.internal.ThreadLocalSessionContext:current sessions are tracked by thread of execution. See the Javadocs for more details.
•org.hibernate.context.internal.ManagedSessionContext: current sessions are tracked by thread of execution. However, you are responsible to bind and unbind a Session instance with static methods on this class: it does not open, flush, or close a Session. See the Javadocs for details.
Typically, the value of this parameter would just name the implementation class to use. For the three out-of-the-box implementations, however, there are three corresponding short names: jta, thread, and managed.
The first two implementations provide a one session - one database transaction programming model. This is also known and used as session-per-request. The beginning and end of a Hibernate session is defined by the duration of a database transaction. If you use programmatic transaction demarcation in plain Java SE without JTA, you are advised to use the Hibernate Transaction API to hide the underlying transaction system from your code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to Transactions and concurrency control for more information and code examples.
The hibernate.current_session_context_class configuration parameter defines which org.hibernate.context.spi.CurrentSessionContext implementation should be used. For backwards compatibility, if this configuration parameter is not set but a org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform is configured, Hibernate will use the org.hibernate.context.internal.JTASessionContext.
篇幅较长,大概翻译如下:
大多数应用程序使用 Hibernate 时需要某种形式的 session“ 上下文” ,一个特定的 session(会话)影响整体特定的上下文范围。然而,跨应用程序的组建上下文是很难的;目前的观点是:不同的上下文定义了不同的范围。在Hibernate3 以前,应用程序使用 session 有两种方式:一种是利用ThreadLocal(本地线程)加上辅助类如:HibernateUtil,建立 session(会话);另一种是使用第三方框架,如:Spring、Pico,它们提供基于上下文的,session(会话)的代理/拦截。从 Hibernate3.0.1 版开始,加 Sessionfactory.getcurrentsession()方法。最初此方法使用 JTA 的事务处理。在 JTA 自己的范围中与当前会话中一起使用。由此可以使用很成熟的 JTA TransactionManager(JTA 事务管理)实现。但是这要求所有应用程序都用 JTA 事务管理,不管这个应用程序是否部署在 J2EE容器中,你的 session 一定要使用基于 JTA 的上下文。然而,从 Hibernate3.1 以后,SessionFactory.getCurrentSession()可以变成插件式的,为此一个新的扩展接口org.hibernate.context.spi.CurrentSessionContext 与一个新的配置参数hibernate.current_session_context_class 产生了,通过这一变化,允许定
义可插拔的上下文范围与当前会话。参考 JAVA 文档中 org.hibernate.context.spi.CurrentSessionContext 接口,其中详细讨论了这一约定。接口定义了唯一方法 currentSession(),这个方法的实现类可以跟踪当前会话的上下文。在外部 Hibernate 允许用三种方法来实现这一接口:org.hibernate.context.internal.JTASessionContext:由 JTA 事务界定当前会话范围与跟踪当前会话。这种处理方式与原来版本是完全一样。详情见javadocs。org.hibernate.context.internal.ThreadLocalSessionContext:由执行的线程跟踪当前会话。详情见 javadocs。
org.hibernate.context.internal.ManagedSessionContext:由执行的线程跟踪当前会话。然而你自己负责绑定与解绑 Session(会话)实例,这些通常在类的静态方法中,这时 Session(会话)不能打开,关闭,刷新,详情见javadocs。通常,这个参数的值将被命名为使用的实现类。当通过外部插件来实现,这些要分别要对应三个短语:"jta","thread","managed"。前两种实现都提供了“ one session - one database transaction(一会话,一事务)” 开发模式。这也可以称为“session-per-request(每请求一会话)”模式。开始结束一个 Hibernate 会话都是在数据库事务过程中定义的。如果计划使用经典的 JSE 平台的事务处理流程,而不使用 JTA 事务,建议在你的代码中用hibernateTransaction(事务)API 来代替系统底层的事务过程。如果你使用JTA,你可以利用 JTA 接口来定义事务。如果你运行在支持 CMT 的 EJB 容器中,事务以声明的方式定义,你的代码中将不用处理事务与会话的操作。参考第 6 章,事务与并发控制有更多的信息与代码示例。hibernate.current_session_context_class 配置参数为org.hibernate.context.spi.CurrentSessionContext。为了向后兼容,如果没有设定此参数,但是org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform 被设定,hibernate 将使用 org.hibernate.context.internal.JTASessionContext。

另外,说下openSession和getCurrentSession方法的区别:



也就是说getCurrentSession的使用是依赖事务的。在非 Spring环境下,CurrentSessionContext的实现是由 “hibernate.current_session_context_class”属性来控制的。但是在Spring环境 下,CurrentSessionContext的实现则变成了 SpringSessionContext或者[b]SpringJtaSessionContext [/b]。下面看下spring集成hibernate时CurrentSessionContext为什么是SpringSessionContext。

二、从配置文件中的org.springframework.orm.hibernate5.LocalSessionFactoryBean说起

LocalSessionFactoryBean实现了InitializingBean接口,因此属性配置完后会执行afterPropertiesSet方法
@Override
public void afterPropertiesSet() throws IOException {
LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(
this.dataSource, getResourceLoader(), getMetadataSources());

if (this.configLocations != null) {
for (Resource resource : this.configLocations) {
// Load Hibernate configuration from given location.
sfb.configure(resource.getURL());
}
}

if (this.mappingResources != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (String mapping : this.mappingResources) {
Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
sfb.addInputStream(mr.getInputStream());
}
}

if (this.mappingLocations != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (Resource resource : this.mappingLocations) {
sfb.addInputStream(resource.getInputStream());
}
}

if (this.cacheableMappingLocations != null) {
// Register given cacheable Hibernate mapping definitions, read from the file system.
for (Resource resource : this.cacheableMappingLocations) {
sfb.addCacheableFile(resource.getFile());
}
}

if (this.mappingJarLocations != null) {
// Register given Hibernate mapping definitions, contained in jar files.
for (Resource resource : this.mappingJarLocations) {
sfb.addJar(resource.getFile());
}
}

if (this.mappingDirectoryLocations != null) {
// Register all Hibernate mapping definitions in the given directories.
for (Resource resource : this.mappingDirectoryLocations) {
File file = resource.getFile();
if (!file.isDirectory()) {
throw new IllegalArgumentException(
"Mapping directory location [" + resource + "] does not denote a directory");
}
sfb.addDirectory(file);
}
}

if (this.entityInterceptor != null) {
sfb.setInterceptor(this.entityInterceptor);
}

if (this.implicitNamingStrategy != null) {
sfb.setImplicitNamingStrategy(this.implicitNamingStrategy);
}

if (this.physicalNamingStrategy != null) {
sfb.setPhysicalNamingStrategy(this.physicalNamingStrategy);
}

if (this.jtaTransactionManager != null) {
sfb.setJtaTransactionManager(this.jtaTransactionManager);
}

if (this.multiTenantConnectionProvider != null) {
sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
}

if (this.currentTenantIdentifierResolver != null) {
sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
}

if (this.entityTypeFilters != null) {
sfb.setEntityTypeFilters(this.entityTypeFilters);
}

if (this.hibernateProperties != null) {
sfb.addProperties(this.hibernateProperties);
}

if (this.annotatedClasses != null) {
sfb.addAnnotatedClasses(this.annotatedClasses);
}

if (this.annotatedPackages != null) {
sfb.addPackages(this.annotatedPackages);
}

if (this.packagesToScan != null) {
sfb.scanPackages(this.packagesToScan);
}

// Build SessionFactory instance.
this.configuration = sfb;
this.sessionFactory = buildSessionFactory(sfb);
}
代码那么长,其实就是创建了LocalSessionFactoryBuilder,以及根据LocalSessionFactoryBuilder创建SessionFactory,buildSessionFactory。其实从这里就可以看出spring集成hibernate时,就是将Hibernate中用到的数据源DataSource、SessionFactory实例及事务管理器都交由Spring容器管理,由Spring向开发人员提供统一的模板化操作。这里再熟悉下单纯Hibernate的工作原理:



言归正传,首先看下LocalSessionFactoryBuilder,LocalSessionFactoryBuilder.java/**
* Create a new LocalSessionFactoryBuilder for the given DataSource.
* @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
* (may be {@code null})
* @param resourceLoader the ResourceLoader to load application classes from
* @param metadataSources the Hibernate MetadataSources service to use (e.g. reusing an existing one)
* @since 4.3
*/
public LocalSessionFactoryBuilder(DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) {
super(metadataSources);

getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); //这里设置了SpringSessionContext
if (dataSource != null) {
getProperties().put(Environment.DATASOURCE, dataSource);
}

// Hibernate 5.2: manually enforce connection release mode ON_CLOSE (the former default)
getProperties().put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_HOLD");

getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader()));
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}注意这里设置的SpringSessionContext,后面用到的就是这里设置的,因此如果配置文件中没有设置
 String CURRENT_SESSION_CONTEXT_CLASS = "hibernate.current_session_context_class";Spring集成Hibernate会使用SpringSessionContext。如果设置了会使用设置的,并且设置的要为org.springframework.orm.hibernate5.SpringSessionContext,并且要用spring的事物管理,否则会报错,比如用spring集成却设置为<prop key="hibernate.current_session_context_class">thread</prop>,会报错createQuery is not valid without active transaction(原因是设置为thread时用的是ThreadLocalSessionContext,这个时候需要手动开启事务)。
再看buildSessionFactory/**
* Subclasses can override this method to perform custom initialization
* of the SessionFactory instance, creating it via the given Configuration
* object that got prepared by this LocalSessionFactoryBean.
* <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
* A custom implementation could prepare the instance in a specific way (e.g. applying
* a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
* @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
* @return the SessionFactory instance
* @see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
sfb.buildSessionFactory());
}这里bootstrapExecutor没有配置,走sfb.buildSessionFactory()
其中过程省略,最终创建的是SessionFactoryImpl实例@Override
public SessionFactory build() {
metadata.validate();
return new SessionFactoryImpl( metadata, buildSessionFactoryOptions() );
}看SessionFactoryImpl构造函数,很长,只关注buildCurrentSessionContext();public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOptions options) {
LOG.debug( "Building session factory" );

...
currentSessionContext = buildCurrentSessionContext();
}
private CurrentSessionContext buildCurrentSessionContext() {
String impl = (String) properties.get( Environment.CURRENT_SESSION_CONTEXT_CLASS );
// for backward-compatibility
if ( impl == null ) {
if ( canAccessTransactionManager() ) {
impl = "jta";
}
else {
return null;
}
}

if ( "jta".equals( impl ) ) {
//			if ( ! transactionFactory().compatibleWithJtaSynchronization() ) {
//				LOG.autoFlushWillNotWork();
//			}
return new JTASessionContext( this );
}
else if ( "thread".equals( impl ) ) {
return new ThreadLocalSessionContext( this );
}
else if ( "managed".equals( impl ) ) {
return new ManagedSessionContext( this );
}
else {
try {
Class implClass = serviceRegistry.getService( ClassLoaderService.class ).classForName( impl );
return (CurrentSessionContext)
implClass.getConstructor( new Class[] { SessionFactoryImplementor.class } )
.newInstance( this );
}
catch( Throwable t ) {
LOG.unableToConstructCurrentSessionContext( impl, t );
return null;
}
}
}

看第一句String impl = (String) properties.get( Environment.CURRENT_SESSION_CONTEXT_CLASS );是不是很熟悉
这里就是将前面加入的SpringSessionContext。

三、为什么要用spring的事务管理器

如果不设置spring的事务管理器会报错Could not obtain transaction-synchronized Session for current thread。看其使用
SessionFactoryImpl.javapublic Session getCurrentSession() throws HibernateException {
if ( currentSessionContext == null ) {
throw new HibernateException( "No CurrentSessionContext configured!" );
}
return currentSessionContext.currentSession();
}SpringSessionContext.java
/**
* Retrieve the Spring-managed Session for the current thread, if any.
*/
@Override
@SuppressWarnings("deprecation")
public Session currentSession() throws HibernateException {
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 (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
return session;
}

if (this.transactionManager != null) {
try {
if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
Session session = this.jtaSessionContext.currentSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session));
}
return session;
}
}
catch (SystemException ex) {
throw new HibernateException("JTA TransactionManager found but status check failed", ex);
}
}

if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
}
else {
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
}
}这里value的值为null,因此条件都不满足,this.transactionManager也为null,因此走最后一个if语句,如果再不满足则抛异常。
条件如何才能满足TransactionSynchronizationManager.java/**
* Return if transaction synchronization is active for the current thread.
* Can be called before register to avoid unnecessary instance creation.
* @see #registerSynchronization
*/
public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
只需要synchronizations不为空即可,即ThreadLocal中包含TransactionSynchronization实例。
看其中的initSynchronization()/**
* Activate transaction synchronization for the current thread.
* Called by a transaction manager on transaction begin.
* @throws IllegalStateException if synchronization is already active
*/
public static void initSynchronization() throws IllegalStateException {
if (isSynchronizationActive()) {
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
}
logger.trace("Initializing transaction synchronization");
synchronizations.set(new LinkedHashSet<TransactionSynchronization>());
}看注释,和事务开启有关,这个在下一篇会详细分析。

四、总结

在Spring托管中,使用getCurrentSession()获得的session并不是程序员自己控制的,session的生命周期交由Spring管理。而且要配置Spring事务管理器,并且使用getCurrentSession()的方法要加入事务管理器中。
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

比如:
@Override
public List list(String querySql) {
Query<?> query = currentSession().createQuery(querySql);
return query.getResultList();
}


必须将使用list的方法加入到事务管理中,list*使用了上述方法,加入了就没问题,get*使用了上述方法没有加入(注释掉了,就会抛上述异常),因为事务管理器没有检测是否加入事务,从而没有调用initSynchronization()所致。因此使用sessionFactory.getCurrentSession(),需要将使用的方法加入事务管理中。
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="list*" propagation="SUPPORTS" read-only="true"/>
<!--<tx:method name="get*" propagation="SUPPORTS" read-only="true"/> -->
</tx:attributes>
</tx:advice>

当然也可以使用openSession(),自己来管理事务好session的生命周期,这样会有很多冗余代码,也没有应用Spring的特性:尽最大可能便捷Java应用开发


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息