2.当我们使用 JPA 时,是否应该总是将@Transactional(readOnly = true)添加到服务层的只读方法?有什么取舍吗?
/** * A boolean flag that can be set to {@code true} if the transaction is * effectively read-only, allowing for corresponding optimizations at runtime. * <p>Defaults to {@code false}. * <p>This just serves as a hint for the actual transaction subsystem; * it will <i>not necessarily</i> cause failure of write access attempts. * A transaction manager which cannot interpret the read-only hint will * <i>not</i> throw an exception when asked for a read-only transaction * but rather silently ignore the hint. * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly() * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() */ boolean readOnly() default false;我们可以看到 readOnly = true 选项允许优化。事务管理器将使用只读选项作为提示。让我们看看用于事务管理器的JpaTransactionManager。
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { JpaTransactionObject txObject = (JpaTransactionObject) transaction; // . // Delegate to JpaDialect for actual transaction begin. int timeoutToUse = determineTimeout(definition); Object transactionData = getJpaDialect().beginTransaction(em, new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder())); //... }在JpaTransactionManager中,doBegin方法委托JpaDialect来开始实际的事务,并在JpaDialect中调用beginTransaction。让我们来看看HibernateJpaDialect类。
@Override public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition) throws PersistenceException, SQLException, TransactionException { // ... // Adapt flush mode and store previous isolation level, if any. FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly()); if (definition instanceof ResourceTransactionDefinition && ((ResourceTransactionDefinition) definition).isLocalResource()) { // As of 5.1, we explicitly optimize for a transaction-local EntityManager, // aligned with native HibernateTransactionManager behavior. previousFlushMode = null; if (definition.isReadOnly()) { session.setDefaultReadOnly(true); } } //堆代码 duidaima.com } protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException { FlushMode flushMode = session.getHibernateFlushMode(); if (readOnly) { // We should suppress flushing for a read-only transaction. if (!flushMode.equals(FlushMode.MANUAL)) { session.setHibernateFlushMode(Flusode.MANUAL); return flushMode; } } else { // We need AUTO or COMMIT for a non-read-only transaction. if (flushMode.lessThan(FlushMode.COMMIT)) { session.setHibernateFlushMode(FlushMode.AUTO); return flushMode; } } // No FlushMode change needed... return null; }在JpaDialect中,我们可以看到JpaDialect使用只读选项准备刷新模式。当 readOnly = true 时, JpaDialect 禁止刷新。此外,您还可以看到,在准备刷新模式后,session.setDefaultReadOnly(true)将session的readOnly属性设置为true。
/** * Change the default for entities and proxies loaded into this session * from modifiable to read-only mode, or from modifiable to read-only mode. * * Read-only entities are not dirty-checked and snapshots of persistent * state are not maintained. Read-only entities can be modified, but * changes are not persisted. * * When a proxy is initialized, the loaded entity will have the same * read-only/modifiable setting as the uninitialized * proxy has, regardless of the session's current setting. * * To change the read-only/modifiable setting for a particular entity * or proxy that is already in this session: * @see Session#setReadOnly(Object,boolean) * * To override this session's read-only/modifiable setting for entities * and proxies loaded by a Query: * @see Query#setReadOnly(boolean) * * @param readOnly true, the default for loaded entities/proxies is read-only; * false, the default for loaded entities/proxies is modifiable */ void setDefaultReadOnly(boolean readOnly);在Session接口中,通过将readOnly属性设置为true,将不会对只读实体进行脏检查,也不会维护持久状态的快照。此外,只读实体的更改也不会持久化。
@Transactional(readOnly = true) public List<UserDto> transactionalReadOnlyOnService(){ List<UserDto> userDtos = userRepository.findAll().stream() .map(userMapper::toDto) .toList(); timeSleepAndPrintConnection(); return userDtos; } public List<UserDto> transactionalReadOnlyOnRepository(){ List<UserDto> userDtos = userRepository.findAll().stream() .map(userMapper::toDto) .toList(); timeSleepAndPrintConnection(); return userDtos; }
我在服务层测试了两个方法,一个是@Transactional(readOnly = true),另一个是存储库层中的@Transactional (readOnly = true)(在 SimpleJpaRepository 中,它是 Jpa Respitory 的默认实现,在类的顶部有@Transformational(ready Only),因此 findAll()方法在默认情况下有@transactional(read only = True))。
activeConnections:0, IdleConnections:10, TotalConnections:10 start transactionalReadOnlyOnService!! Hibernate: select u1_0.id, u1_0.email, u1_0.name, u1_0.profile_file_name from users u1_0 activeConnections:1, IdleConnections:9, TotalConnections:10 activeConnections:1, IdleConnections:9, TotalConnections:10 activeConnections:1, IdleConnections:9, TotalConnections:10 activeConnections:1, IdleConnections:9, TotalConnections:10 activeConnections:1, IdleConnections:9, TotalConnections:10 end transactionalReadOnlyOnService!! activeConnections:0, IdleConnections:10, TotalConnections:10对于存储库层方法中的@Transactional(readOnly = true),
activeConnections:0, IdleConnections:10, TotalConnections:10 start transactionalReadOnlyOnRepository!! Hibernate: select u1_0.id, u1_0.email, u1_0.name, u1_0.profile_file_name from users u1_0 activeConnections:0, IdleConnections:10, TotalConnections:10 activeConnections:0, IdleConnections:10, TotalConnections:10 activeConnections:0, IdleConnections:10, TotalConnections:10 activeConnections:0, IdleConnections:10, TotalConnections:10 activeConnections:0, IdleConnections:10, TotalConnections:10 end transactionalReadOnlyOnRepository!! activeConnections:0, IdleConnections:10, TotalConnections:10正如您所看到的,@Transactional(readOnly = true)一旦查询结果到达,存储库层就会释放连接。