Let's suppose I have the following repository:
public interface ApplicationRepository extends JpaRepository<Application, Integer> {
public boolean existsByCode(String code);
public Optional<Application> findByCode(String code);
}
And the following service:
@Service
@RequiredArgsConstructor
public class ApplicationService {
private final ApplicationRepository appRepo;
public Application findById(Integer id) throws RecordNotFoundException {
return appRepo.findById(id)
.orElseThrow(() -> new RecordNotFoundException("Application with id: " id " could not be found"));
}
public boolean existsByCode(String code) {
return appRepo.existsByCode(code);
}
public Application findByCode(String code) throws RecordNotFoundException {
return appRepo.findByCode(code).orElseThrow(
() -> new RecordNotFoundException("Application with code: " code " could not be found"));
}
}
Since default repository methods have @Transactional(readOnly = true), should I add the annotation on my custom methods? If so it's better to add the annotation on service methods or repository's?
If I have a third method, which call 2 other methods marked with @Transactional(readOnly = true), is it better to mark also this method with the annotation?
CodePudding user response:
The Spring reference manual writes:
The
@Transactional
annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction"). The default @Transactional settings are as follows:
- The propagation setting is PROPAGATION_REQUIRED.
- The isolation level is ISOLATION_DEFAULT.
- The transaction is read-write.
- The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
- Any RuntimeException or Error triggers rollback, and any checked Exception does not.
and the JavaDoc of PROPAGATION_REQUIRED
says:
Support a current transaction; create a new one if none exists.
That is, if @Transactional
service calls one or more @Transactional
repositories, the service will manage the transaction, i.e. the transaction will begin upon entering the service method, and commit or rollback upon leaving it. The @Transactional
on the repositories will detect the existing transaction, and do nothing, causing the repositories to participate in the existing transaction.
Starting a transaction in the service level is generally desirable. For one, having a single transaction means that all data is read in a single transaction, giving you stronger consistency guarantees than data loaded in separate transactions (between which another transaction might have modified the data). For another, starting a transaction has a cost, particularly with JPA, where each transaction has its own persistence context. That's why the hibernate team speaks out against the "Session-per-operation anti-pattern".
CodePudding user response:
I wouldn't pay much attention to readOnly = true
. From Spring/Hibernate perspective specifying that causes following behaviour:
JpaTransactionManager#doBegin
calls HibernateJpaDialect#beginTransaction
, which in turn triggers following code:
if (definition.isReadOnly()) {
session.setDefaultReadOnly(true);
}
and Session#setDefaultReadOnly
has following javadoc:
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.
So, @Transactional(readOnly = true)
definition may save you some memory and CPU cycles (it does not prevent HBN from modifying data in DB), however in order to get some benefits from that the most outer transaction definition must be declared as readonly.