java - Spring @Transactional(Propagation.NEVER) 应该创建 Hibernate session 吗?

标签 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
    }

}

以及从OuterBean调用的InnerBean:

@Service
public class InnerBean {

    @Resource
    private UserDao userDao;

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

}

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

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


编辑

为了解释更多问题,我附上了创建 hibernate session 的跟踪

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

现在让我们比较一下我删除 @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

请注意,当我省略 @Transactional(propagation = Propagation.NEVER) 时,会为 userDao 的每次方法调用创建单独的 session 。

所以我的问题也可以表述为

Shouldn’t be @Transactional(propagation = Propagation.NEVER) implemented in Spring as a guardian that prevents us from accidental use of transaction, without any side effect (session creation)?

最佳答案

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

我引用 Hibernate's official documentation :

The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes. Instances may exist in one of three states:

  • transient: never persistent, not associated with any Session
  • persistent: associated with a unique Session detached: previously
  • persistent, not associated with any Session

Transient instances may be made persistent by calling save(), persist() or saveOrUpdate(). Persistent instances may be made transient by calling delete(). Any instance returned by a get() or load() method is persistent.

取自 Java Persistence With Hibernate, Second Edition作者:Christian Bauer、Gavin King 和 Gary Gregory:

The persistence context acts as a first-level cache; it remembers all entity instances you’ve handled in a particular unit of work. For example, if you ask Hibernate to load an entity instance using a primary key value (a lookup by identifier), Hibernate can first check the current unit of work in the persistence context. If Hibernate finds the entity instance in the persistence context, no database hit occurs—this is a repeatable read for an application. Consecutive em.find(Item.class, ITEM_ID) calls with the same persistence context will yield the same result.

也来自 Java Persistence With Hibernate, Second Edition :

The persistence context cache is always on—it can’t be turned off. It ensures the following:

  • The persistence layer isn’t vulnerable to stack overflows in the case of circular references in an object graph.
  • There can never be conflicting representations of the same database row at the end of a unit of work. The provider can safely write all changes made to an entity instance to the database.
  • Likewise, changes made in a particular persistence context are always immediately visible to all other code executed inside that unit of work and its persistence context. JPA guarantees repeatable entity-instance reads.

关于交易,这里摘自official Hibernate's documentation :

Defines the contract for abstracting applications from the configured underlying means of transaction management. Allows the application to define units of work, while maintaining abstraction from the underlying transaction implementation (eg. JTA, JDBC).

所以,综上所述,withTransactionwithoutTransaction 不会共享 UnitOfWork,因此不会共享一级缓存,这就是为什么第二次加载返回原值。

至于这两种方法不共享工作单元的原因,可以引用Shailendra的回答。

编辑:

你好像误会了什么。必须始终创建 session - 这就是 Hibernate 的工作方式,期间。您对不创建 session 的期望等于期望在没有 JDBC 连接的情况下执行 JDBC 查询:)

您的两个示例之间的区别在于,使用 @Transactional(propagation = Propagation.NEVER) 您的方法被 Spring 拦截和代理,并且只为 中的查询创建一个 session 没有交易。当您删除注释时,您会从 Spring 的事务拦截器中排除您的方法,因此将为每个与 DB 相关的操作创建一个新 session 。我再重复一遍,我怎么强调都不过分 - 您必须有一个开放的 session 来执行任何查询。

就保护而言 - 尝试通过使 withTransaction 使用 Propagation.NEVER 和 withoutTransaction 使用默认的 @Transactional 注释,看看会发生什么(剧透:你会得到一个 IllegalTransactionStateException)。

EDIT2:

至于为什么 session 在外部 bean 中的两个负载之间共享 - 这正是 JpaTransactionManager 应该做的事情,并且通过使用 @Transactional 注释您的方法,您'已经通知 Spring 它应该使用配置的事务管理器来包装你的方法。这是the official documentation谈到 JpaTransactionManager 的预期行为:

PlatformTransactionManager implementation for a single JPA EntityManagerFactory. Binds a JPA EntityManager from the specified factory to the thread, potentially allowing for one thread-bound EntityManager per factory. SharedEntityManagerCreator and @PersistenceContext are aware of thread-bound entity managers and participate in such transactions automatically. Using either is required for JPA access code supporting this transaction management mechanism.

另外,要了解 Spring 如何处理声明式事务管理(即方法上的 @Transactional 注释),请参阅 official documentation .为了便于导航,我将引用一段话:

The most important concepts to grasp with regard to the Spring Framework’s declarative transaction support are that this support is enabled via AOP proxies, and that the transactional advice is driven by metadata (currently XML- or annotation-based). The combination of AOP with transactional metadata yields an AOP proxy that uses a TransactionInterceptor in conjunction with an appropriate PlatformTransactionManager implementation to drive transactions around method invocations.

关于java - Spring @Transactional(Propagation.NEVER) 应该创建 Hibernate session 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38351157/

相关文章:

java - @UniqueConstraint 和 @Column(unique = true) 在 hibernate 注释中

java - 为什么这段代码返回 false 而不是 true?

java - 使用 Oracle STANDARD_HASH,在 JAVA 中重现哈希

java - JasperReports 是在 Web 应用程序中显示报告的适当解决方案吗?

spring - 在我的 Spring 批处理中添加 maven 依赖项会出现特定错误

mysql - 使用 JPA/Spring/Hibernate 自动截断字符串列的方法?

java - Spring Task Scheduler 与 Java 的 ScheduledExecutorService

java - Hibernate - 从数据库获取对象版本

java - 删除实体会导致 hibernate 中的 ObjectDeletedException

java - 反向字符串和附加后缀的性能测量