假设我们已经在 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)
为导致第二次调用 的方法
从 Hibernate 一级缓存中读取而不是从数据库中读取?withoutTransaction()
创建 Hibernate session 是否正确? user.getName()
编辑
为了解释更多问题,我附上了创建 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()
orsaveOrUpdate()
. Persistent instances may be made transient by callingdelete()
. Any instance returned by aget()
orload()
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).
所以,综上所述,withTransaction
和 withoutTransaction
不会共享 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 appropriatePlatformTransactionManager
implementation to drive transactions around method invocations.
关于java - Spring @Transactional(Propagation.NEVER) 应该创建 Hibernate session 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38351157/