Spring @Transactional(Propagation.NEVER)应该创建hibernate会话吗?

sni*_*guu 25 java spring hibernate jpa

让我们假设我们在spring(4.2.7)中正确配置了由hibernate(4.3.11)支持的jpa.启用了Hibernate一级缓存.我们使用声明式交易.我们有OuterBean

@Service
public class OuterBean {

    @Resource
    private UserDao userDao;

    @Resource
    private InnerBean innerBean;

    @Transactional(propagation = Propagation.NEVER)
    public void withoutTransaction(){
        User user = userDao.load(1l);
        System.out.println(user.getName());//return userName
        innerBean.withTransaction();
        user = userDao.load(1l);
        System.out.println(user.getName());//return userName instead of newUserName
    }

}
Run Code Online (Sandbox Code Playgroud)

从OuterBean调用的InnerBean:

@Service
public class InnerBean {

    @Resource
    private UserDao userDao;

    @Transactional
    public void withTransaction(){
        User user = userDao.load(1l);
        user.setName("newUserName");
    }
Run Code Online (Sandbox Code Playgroud)

}

user.getName()OuterBean 中的方法返回相同的值两次(第二次是在数据库中更新名称之后)是否正确?

换句话说,是否正确的行为@Transactional(propagation = Propagation.NEVER)为方法创建了hibernate会话withoutTransaction(),导致第二个调用user.getName()从hibernate读取第一级缓存而不是数据库?

编辑

为了更好地解释问题,我从hibernate会话的创建中附加了跟踪

TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@c17285e
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@715c48ca
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl  - before transaction completion
TRACE org.hibernate.internal.SessionImpl  - after transaction completion
TRACE org.hibernate.internal.SessionImpl  - Closing session
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionImpl  - Closing session
Run Code Online (Sandbox Code Playgroud)

现在让我们在删除时比较跟踪 @Transactional(propagation = Propagation.NEVER)

TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@4ebd2c5f
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Closing session
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@5af84083
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl  - before transaction completion
TRACE org.hibernate.internal.SessionImpl  - after transaction completion
TRACE org.hibernate.internal.SessionImpl  - Closing session
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl  - Opening Hibernate Session.  tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@35f4f41f
TRACE org.hibernate.internal.SessionImpl  - Opened session at timestamp: 14689203906
TRACE org.hibernate.internal.SessionImpl  - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl  - Closing session
newUserName
Run Code Online (Sandbox Code Playgroud)

@Transactional(propagation = Propagation.NEVER)从userDao每次调用方法时,请注意省略单独的会话是crete.

所以我的问题也可以制定为不应该@Transactional(propagation = Propagation.NEVER)在春天实施作为监护人,阻止我们意外使用交易,没有任何副作用(会话创建)?

Mil*_*vić 14

行为是正确的 - Hibernate将始终创建一个会话(您希望它如何执行任何操作?),并通过加载与该会话关联的实体.由于withoutTransaction未参与事务,因此所做的更改withTransaction将在新事务中发生,除非您调用refresh,否则不应显示,这将强制从数据库重新加载.

我引用了Hibernate的官方文档:

Session的主要功能是为映射实体类的实例提供创建,读取和删除操作.实例可能存在以下三种状态之一:

  • transient:永不持久,不与任何Session相关联
  • persistent:与分离的唯一Session相关联:之前
  • 持久性,与任何Session无关

通过调用或save(),可以使瞬态实例持久化.通过调用可以使持久实例变为瞬态.方法返回的任何实例都是持久的.persist()saveOrUpdate()delete()get()load()

摘自Java Persistence With Hibernate,第二版由Christian Bauer,Gavin King和Gary Gregory撰写:

持久化上下文充当第一级缓存; 它会记住您在特定工作单元中处理的所有实体实例.例如,如果您要求Hibernate使用主键值(按标识符查找)加载实体实例,Hibernate可以首先检查持久化上下文中的当前工作单元.如果Hibernate在持久化上下文中找到实体实例,则不会发生数据库命中 - 这是应用程序的可重复读取.em.find(Item.class, ITEM_ID)具有相同持久性上下文的连续调用将产生相同的结果.

也来自Java Persistence With Hibernate,第二版:

持久性上下文缓存始终打开 - 它无法关闭.它确保了以下内容:

  • 在对象图中的循环引用的情况下,持久层不容易受到堆栈溢出的影响.
  • 在工作单元结束时,永远不会存在相同数据库行的冲突表示.提供程序可以安全地将对实体实例所做的所有更改写入数据库.
  • 同样,在特定持久化上下文中所做的更改始终对该工作单元及其持久性上下文中执行的所有其他代码立即可见.JPA保证可重复的实体实例读取.

关于交易,这里摘录自官方Hibernate的文档:

定义从配置的基础事务管理方法中抽象应用程序的合同.允许应用程序定义工作单元,同时保持对底层事务实现的抽象(例如,JTA,JDBC).

因此,总结一下,withTransactionwithoutTransaction不会共享UnitOfWork,因此不会共享第一级缓存,这就是第二次加载返回原始值的原因.

至于为什么这两种方法不共享工作单元的原因,你可以参考Shailendra的答案.

编辑:

你似乎误解了一些东西.必须始终创建一个会话 - 这就是Hibernate的工作方式,周期.您没有创建会话的期望等于期望在没有JDBC连接的情况下执行JDBC查询:)

两个示例之间的区别在于,@Transactional(propagation = Propagation.NEVER)您的方法被Spring拦截并代理,并且只为查询创建了一个会话withoutTransaction.删除注释时,会从Spring的事务拦截器中排除方法,因此将为每个与DB相关的操作创建一个新会话.我再说一遍,我不能强调这一点 - 你必须有一个开放的会话来执行任何查询.

至于保护 - 尝试通过withTransaction使用Propagation.NEVER 交换两种方法的注释并withoutTransaction使用默认@Transactional注释,看看会发生什么(剧透:你会得到一个IllegalTransactionStateException).

EDIT2:

至于为什么会话在外部bean中的两个负载之间共享 - 这JpaTransactionManager正是应该做的事情,并且通过注释你的方法@Transactional已经通知Spring它应该使用配置的事务管理器来包装你的方法.以下是官方文档中关于JpaTransactionManager预期行为的说明:

单个JPA EntityManagerFactory的PlatformTransactionManager实现.将JPA EntityManager从指定的工厂绑定到线程,可能允许每个工厂使用一个线程绑定的EntityManager.SharedEntityManagerCreator和@PersistenceContext知道线程绑定的实体管理器并自动参与此类事务.支持此事务管理机制的JPA访问代码需要使用其中任何一个.

另外,要了解Spring如何处理声明式事务管理(即@Transactional方法注释),请参阅官方文档.为了便于导航,我将包含一个引用:

关于Spring Framework的声明式事务支持,最重要的概念是通过AOP代理启用此支持,并且事务性建议由元数据(当前基于XML或基于注释)驱动.AOP与事务元数据的组合产生AOP代理,该代理使用TransactionInterceptor与适当的PlatformTransactionManager实现相结合来驱动围绕方法调用的事务.


Gab*_*ica 5

首先,当您在JPA API后使用休眠模式时,我将使用术语EntityManager而不是会话(严格来说是同一件事,仅是术语问题)。

使用JPA对数据库的每次访问都将涉及一个EntityManager,您要获取实体,需要一个EntityManager(EM)。所谓的一级缓存仅是EM管理实体状态。

从理论上讲,EM的生命周期很短,并且绑定到一个工作单元(因此通常绑定到一个事务,请参阅“ 努力了解EntityManager的用法”)。

现在,可以以不同的方式使用JPA:容器管理的或用户管理的持久性。当EM由容器(您的情况,这里是弹簧)进行管理时,最后一个负责管理EM范围/生命周期(为您创建,刷新和销毁它)。由于EM绑定到事务/工作单元,因此此任务委托给TransactionManager(处理@Transactional注释的对象)。

当您使用注释方法时@Transactional(propagation = Propagation.NEVER),您将创建一个Spring逻辑事务作用域,该作用域将确保没有绑定到最终存在的EM的现有基础JDBC事务,该事务将不会创建并使用JDBC自动提交模式,会创建一个EM此逻辑事务作用域(如果尚不存在)。

关于在未定义事务逻辑作用域的情况下为每个DAO调用创建一个新的EM实例的事实,您必须记住您无法在EM之外使用JPA访问数据库。AFAIK休眠曾no session bound to thread在这种情况下引发错误,但是在以后的发行版中可能会演变为错误,否则您的DAO可能会带有注释,@Transactional(propagation = Propagation.SUPPORT)如果没有封闭的逻辑范围,它也会自动创建EM。这是一个坏习惯,因为交易应在工作单元中进行定义,例如。服务级别而不是DAO。