SpringBoot Transactional Propagation REQUIRES_NEW doesn't create new Transaction - spring-boot

I have the following scenario:
#Service
public class ServiceA {
#Autowired private ServiceB serviceB;
public void runA(){
serviceB.runB()
}
}
#Service
public class ServiceB {
#Autowired private ServiceC serviceC;
#Transactional
public void runB(){
serviceC.runC()
...rest of the logic
}
}
#Service
public class ServiceC {
#Autowired private TestRepository testRepository;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean runC(){
Optional<TestEntity> testEntityOptional = testRepository.findByKeyAndType("Key", "Type");
if(testEntityOptional.isPresent()) {
testEntityOptional.get().setActive(true);
return true;
}
return false;
}
}
#Transactional
public interface TestRepository
extends JpaRepository<TestEntity, Integer>, JpaSpecificationExecutor<TestEntity> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#QueryHints({#QueryHint(name = "javax.persistence.lock.timeout", value = "5000")})
Optional<TestEntity> findByKeyAndType(#NotNull String key, #NotNull String type);
}
I expect next flow:
ServiceA.runA() invokes ServiceB.runB()
ServiceB.runB() opens TRANSACTION_1 (or use transaction if it's opened before) as default propagation is REQUIRED
ServiceB.runB() invokes ServiceC.runC()
ServiceC.runC() opens TRANSACTION_2 because propagation is REQUIRED_NEW
ServiceC.runC() invokes TestRepository.findByKeyAndType() to fetch testEntity by some criteria
TestRepository.findByKeyAndType() return record from DB which match the criteria and lock it for read/update
ServiceB.runC() process the record
ServiceB.runC() returns value and TRANSACTION_2 is committed and so lock released
ServiceB.runB() returns and TRANSACTION_1 is committed
But from my testing this is not the case. It seams that ServiceC.runC() do not create new transaction (TRANSACTION_2) even propagation REQUIRED_NEW is set (or it do not commit it at the return), and lock is released only when ServiceB.runB() returns (when TRANSACTION_1 is committed)
Do anyone see what I am doing wrong here? Is this normal behavior of SpringBoot?
Also the lock timeout doesn't work:
I am using Postgress v10 for DB where lock_timeout is set to "0".
So it looks like #QueryHints({#QueryHint(name = "javax.persistence.lock.timeout", value = "5000")}) doesn't work as once record is locked, the other transaction which try to read the record hangs for a while until the record is unlocked

Once I enabled logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG, this is what I see. It includes suspension and resumption.
Is your transaction manager also JpaTransactionManager?
2020-06-30 01:11:53.239 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.example.accessingdatajpa.BlogService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-06-30 01:11:57.532 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1968179698<open>)] for JPA transaction
2020-06-30 01:11:57.538 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#7f2542f]
2020-06-30 01:12:02.432 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1968179698<open>)] for JPA transaction
2020-06-30 01:12:04.032 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Suspending current transaction, creating new transaction with name [com.example.accessingdatajpa.Service2.save]
2020-06-30 01:12:06.915 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(2143659352<open>)] for JPA transaction
2020-06-30 01:12:06.916 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#42fd8f2f]
2020-06-30 01:12:10.196 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2143659352<open>)] for JPA transaction
2020-06-30 01:12:11.489 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2020-06-30 01:12:12.227 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2020-06-30 01:12:13.082 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(2143659352<open>)]
2020-06-30 01:12:13.109 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(2143659352<open>)] after transaction
2020-06-30 01:12:13.110 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Resuming suspended transaction after completion of inner transaction
2020-06-30 01:12:16.409 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2020-06-30 01:12:17.541 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1968179698<open>)]
2020-06-30 01:12:17.542 DEBUG 47133 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1968179698<open>)] after transaction

Related

Why Spring Boot Test initialize DataSource many times?

I used this configuration for database initiation in code.
But when I start the unit test, the database will be initialized many times.
I am not sure if this is correct. Because sometimes I will get some error response from Database.
This configuration PrimaryDataSourceConfiguration contains all the database-related beans.
#Configuration
#EnableJpaRepositories(
basePackages = {"com.test.core.infrastructure.datasource.primary.repo"},
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
#EnableTransactionManagement
public class PrimaryDataSourceConfiguration {
#Bean("primaryDataSource")
#Primary
#ConfigurationProperties("test.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean("primaryJdbcTemplate")
#Primary
public JdbcTemplate primaryJdbcTemplate(#Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean("primaryEntityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
#Qualifier("primaryDataSource") DataSource primaryDataSource,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.packages("com.test.core.infrastructure.datasource.primary.entity")
.persistenceUnit("myDS")
.build();
}
#Bean("primaryTransactionManager")
#Primary
public PlatformTransactionManager primaryTransactionManager(
#Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory) {
return new JpaTransactionManager(primaryEntityManagerFactory.getObject());
}
}
2022-06-10 18:48:29.070 INFO 21772 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'myDS'
2022-06-10 18:48:29.073 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-06-10 18:48:29.079 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2022-06-10 18:48:29.084 INFO 21772 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'myDS'
2022-06-10 18:48:29.086 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated...
2022-06-10 18:48:29.157 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed.
2022-06-10 18:48:29.159 INFO 21772 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'myDS'
2022-06-10 18:48:29.160 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Shutdown initiated...
2022-06-10 18:48:29.213 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Shutdown completed.
2022-06-10 18:48:29.214 INFO 21772 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'myDS'
2022-06-10 18:48:29.215 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-4 - Shutdown initiated...
2022-06-10 18:48:29.263 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-4 - Shutdown completed.
2022-06-10 18:48:29.266 INFO 21772 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'myDS'
2022-06-10 18:48:29.268 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-5 - Shutdown initiated...
2022-06-10 18:48:29.358 INFO 21772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-5 - Shutdown completed.

Hibernate does not save entity after transaction commit

I have transactional service that I use to create a Person and persist it in db.
The problem is that entity is not saved into db after transaction is commited.
At first I thought it was the fault of the JpaRepository, which opens a new transaction every time a method is called, but from what I've learned, these are just logical transactions inside a single physical one that gets opened in my service.
I use Spring Boot and Hibernate to connect with my firebird databases.
For this purpose i configured two separate datasources and transaction managers.
In each #Transactional annotation, I specify the transaction manager to be used.
Fetching data works perfect. The problem concerns only saving.
I'm actually performing more operations here, but for the sake of the example I'll limit myself to just saving the entities.
Simple example below.
#Transactional("secondaryTransactionManager")
#RequiredArgsConstructor
public class PersonServiceImpl implements PersonService {
private final PersonJpaRepository personJpaRepository;
#Override
public String savePerson() {
var personEntity = new PersonEntity();
personEntity.setName("Slawek");
personEntity.setSurname("Filip");
personEntity.setPersonalNumber("12341234123");
var save = personJpaRepository.save(personEntity);
return save.getName();
}
}
Person entity is not saved in db though. There are logs
2021-10-16 17:41:13.345 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : Getting transaction for [com.backend.declarations.service.impl.PersonServiceImpl.savePerson]
2021-10-16 17:41:13.772 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-10-16 17:41:13.967 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-10-16 17:41:14.716 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2021-10-16 17:41:14.728 DEBUG 884 --- [nio-8080-exec-3] o.s.orm.jpa.EntityManagerFactoryUtils : Opening JPA EntityManager
2021-10-16 17:41:14.728 TRACE 884 --- [nio-8080-exec-3] .i.SessionFactoryImpl$SessionBuilderImpl : Opening Hibernate Session. tenant=null
2021-10-16 17:41:14.729 TRACE 884 --- [nio-8080-exec-3] org.hibernate.internal.SessionImpl : Opened Session [16b4f365-b4f9-4267-bed6-a5057aad25a9] at timestamp: 1634398874728
2021-10-16 17:41:14.735 TRACE 884 --- [nio-8080-exec-3] o.hibernate.event.internal.EntityState : Transient instance of: com.backend.declarations.repository.entity.PersonEntity
2021-10-16 17:41:14.736 TRACE 884 --- [nio-8080-exec-3] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2021-10-16 17:41:14.738 DEBUG 884 --- [nio-8080-exec-3] org.hibernate.SQL : select gen_id( GEN_OSOBY_ID, 1 ) from RDB$DATABASE
2021-10-16 17:41:14.750 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Registering statement [org.firebirdsql.jdbc.FBPreparedStatement#1b6]
2021-10-16 17:41:14.752 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Registering result set [org.firebirdsql.jdbc.FBResultSet#7e70efdc]
2021-10-16 17:41:14.760 DEBUG 884 --- [nio-8080-exec-3] o.h.id.enhanced.SequenceStructure : Sequence value obtained: 3422
2021-10-16 17:41:14.760 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Releasing result set [org.firebirdsql.jdbc.FBResultSet#7e70efdc]
2021-10-16 17:41:14.760 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Closing result set [org.firebirdsql.jdbc.FBResultSet#7e70efdc]
2021-10-16 17:41:14.764 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Releasing statement [org.firebirdsql.jdbc.FBPreparedStatement#1b6]
2021-10-16 17:41:14.764 DEBUG 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : HHH000387: ResultSet's statement was not registered
2021-10-16 17:41:14.764 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Closing prepared statement [org.firebirdsql.jdbc.FBPreparedStatement#1b6]
2021-10-16 17:41:14.764 TRACE 884 --- [nio-8080-exec-3] o.h.e.jdbc.internal.JdbcCoordinatorImpl : Starting after statement execution processing [ON_CLOSE]
2021-10-16 17:41:14.765 DEBUG 884 --- [nio-8080-exec-3] o.h.e.i.AbstractSaveEventListener : Generated identifier: 3422, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
2021-10-16 17:41:14.765 TRACE 884 --- [nio-8080-exec-3] o.h.e.i.AbstractSaveEventListener : Saving [com.backend.declarations.repository.entity.PersonEntity#3422]
2021-10-16 17:41:14.769 TRACE 884 --- [nio-8080-exec-3] org.hibernate.engine.spi.ActionQueue : Adding an EntityInsertAction for [com.backend.declarations.repository.entity.PersonEntity] object
2021-10-16 17:41:14.772 TRACE 884 --- [nio-8080-exec-3] org.hibernate.engine.spi.ActionQueue : Adding insert with no non-nullable, transient entities: [EntityInsertAction[com.backend.declarations.repository.entity.PersonEntity#3422]]
2021-10-16 17:41:14.772 TRACE 884 --- [nio-8080-exec-3] org.hibernate.engine.spi.ActionQueue : Adding resolved non-early insert action.
2021-10-16 17:41:14.778 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2021-10-16 17:41:14.871 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-10-16 17:41:14.951 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-10-16 17:41:18.392 TRACE 884 --- [nio-8080-exec-3] o.s.t.i.TransactionInterceptor : Completing transaction for [com.backend.declarations.service.impl.PersonServiceImpl.savePerson]
2021-10-16 17:41:18.393 TRACE 884 --- [nio-8080-exec-3] org.hibernate.internal.SessionImpl : Closing session [16b4f365-b4f9-4267-bed6-a5057aad25a9]
2021-10-16 17:41:18.393 TRACE 884 --- [nio-8080-exec-3] o.h.e.jdbc.internal.JdbcCoordinatorImpl : Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl#695993a3]
2021-10-16 17:41:18.393 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Releasing JDBC resources
2021-10-16 17:41:18.393 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.LogicalConnectionManagedImpl : Closing logical connection
2021-10-16 17:41:18.394 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.ResourceRegistryStandardImpl : Releasing JDBC resources
2021-10-16 17:41:18.395 TRACE 884 --- [nio-8080-exec-3] o.h.r.j.i.LogicalConnectionManagedImpl : Logical connection closed
Primary Ts configuration:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.backend.auth.repository",
"com.backend.domainoptions.repository",
"com.backend.actionlog.repository",
"com.backend.surveys.repository"
},
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "primaryTransactionManager")
#EnableTransactionManagement
class PrimaryTsConfiguration {
#Autowired
Environment env;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource());
em.setPackagesToScan(
"com.backend.auth.repository",
"com.backend.domainoptions.repository",
"com.backend.actionlog.repository",
"com.backend.surveys.repository"
);
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto",
env.getProperty("spring.jpa.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect",
env.getProperty("spring.jpa.hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean(name = "defaultDs")
#Primary
public DataSource primaryDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Bean(name="primaryTransactionManager")
#Autowired
#Primary
DataSourceTransactionManager primaryTransactionManager(#Qualifier("defaultDs") DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
}
Secondary Ts configuration:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.backend.schools.repository",
"com.backend.declarations.repository",
"com.backend.verifications.repository"
},
entityManagerFactoryRef = "secondaryEntityManager",
transactionManagerRef = "secondaryTransactionManager")
#EnableConfigurationProperties(RepositoryProperties.class)
#EnableTransactionManagement
public class SecondaryTmConfiguration {
#Autowired
Environment env;
#Configuration
#PropertySource(factory = YamlPropertySourceFactory.class, value = "datasourceconfig.yml")
static class PropertiesConfiguration {
}
#Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManager(RepositoryProperties repositoryProperties) {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(secondaryDataSource(repositoryProperties));
em.setPackagesToScan(
"com.backend.schools.repository.entity",
"com.backend.declarations.repository.entity",
"com.backend.verifications.repository.entity"
);
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto",
env.getProperty("spring.jpa.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect",
env.getProperty("spring.jpa.hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean(name = "secondDs")
public DataSource secondaryDataSource(RepositoryProperties repositoryProperties) {
System.out.println(repositoryProperties);
DataSourceRouting dataSourceRouting = new DataSourceRouting();
dataSourceRouting.initDatasource(createDataSources(repositoryProperties));
return dataSourceRouting;
}
#Bean(name="secondaryTransactionManager")
#Autowired
DataSourceTransactionManager secondaryTransactionManager(#Qualifier("secondDs") DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
private List<Pair<String, DataSource>> createDataSources(RepositoryProperties repositoryProperties) {
return repositoryProperties.getDbNumbers().stream()
.map(dbNumber -> createDataSource(dbNumber, repositoryProperties))
.collect(Collectors.toList());
}
private Pair<String, DataSource> createDataSource(String dbNumber, RepositoryProperties repositoryProperties) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(createDataSourceUrl(dbNumber));
dataSource.setUsername(repositoryProperties.getUsername());
dataSource.setPassword(repositoryProperties.getPassword());
dataSource.setDriverClassName(repositoryProperties.getDriverClassName());
return Pair.of(dbNumber, dataSource);
}
private String createDataSourceUrl(String dbNumber) {
return env.getProperty("spring.custom.datasource.url.prefix") + dbNumber + env.getProperty("spring.custom.datasource.url.suffix");
}
}
Why it's not working?
What have I missed?
Ok, the problem was that JPA entities does not work with DataSourceTransactionManager.
I used JpaTransactionManager instead and it works fine

Not fetch a LAZY relation if I call from a method which calls after another Transactional method

In my Spring Boot 2.2.5.RELEASE application I have a problem when I try to fetch a LAZY field.
The problem occurs in a service method, createWayBillFromInvoice, which marked as #Transactional.
Case A: I call this method from a controller directly it executes with no problem.
Case B: I call the same method from another service method which is not itself Transactional
my LAZY fields not fetch.
So I assume it is about Transaction management but I could not find the reason.
The method where my LAZY fields not fetched (and fetched in a direct-call):
#Transactional
public createWayBillFromInvoice(){
Invoice invoice = invoiceRepository.getInvoice(invoiceId);//invoice is selected
Order order = invoice.getOrder();
}
Here my order relation is not null but not initialized and not proxied, so If I call order.getOtherRelations it returns empty.
In case B I call something like:
public void create(){ //This method IS NOT Transactional
invoiceService.createInvoice();//This method is Transactional
waybillService.createWaybilFromInvoice();//The method declared above
}
I tried:
Make the create method Transactional
Set propagation to REQUIRES_NEW for createWaybillFromInvoice
Here are some logs:
2020-06-04 17:17:16.337 DEBUG 1208 --- [nio-9002-exec-4] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [io.salesart.billingservice.service.impl.WaybillServiceImpl.createWayBillFromInvoice]:
2020-06-04 17:17:16.376 DEBUG 1208 --- [nio-9002-exec-4] o.s.jdbc.datasource.DataSourceUtils : Changing isolation level of JDBC Connection [HikariProxyConnection#2044564149 wrapping org.postgresql.jdbc.PgConnection#27db45f] to 2
2020-06-04 17:17:16.450 DEBUG 1208 --- [nio-9002-exec-4] o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
2020-06-04 17:17:16.451 DEBUG 1208 --- [nio-9002-exec-4] o.h.e.t.internal.TransactionImpl : begin
2020-06-04 17:17:16.451 DEBUG 1208 --- [nio-9002-exec-4] org.postgresql.jdbc.PgConnection : setAutoCommit = false
2020-06-04 17:17:16.457 DEBUG 1208 --- [nio-9002-exec-4] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#caf1ee0]
2020-06-04 17:17:19.532 DEBUG 1208 --- [nio-9002-exec-4] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1021250378<open>)] for JPA transaction

Spring Data REST - Why hibernate fetches children entity with FetchType.LAZY?

I have a simple use case where I created 2 entities Account & AccountDetail.
These entities have a one-to-one relationship as shown below:
table relationship
The Account entity:
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
#EqualsAndHashCode
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private AccountDetail accountDetail;
}
The AccountDetail entity:
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
#EqualsAndHashCode
public class AccountDetail {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) #MapsId
private Account account;
private String firstName;
private String lastName;
}
I am using Spring Boot (v2.3.0) with following dependencies:
- Spring Data JPA: to persist entities using Hibernate
- Rest Repositories: to expose repositories over REST endpoints
The issue is that Hibernate is fetching the AccountDetail entity even though I specified fetch = FetchType.LAZY.
You can see the 2 sql statements in the log:
hibernate log
What I already tried:
Using Projection:
#Projection(types = {Account.class})
public interface AccountProjection {
String getUsername();
}
With the following GET request http://localhost:8080/accounts/1?projection=accountProjection, I get:
{
"username": "warrior24",
"_links": {
"self": {
"href": "http://localhost:8080/accounts/1"
},
"account": {
"href": "http://localhost:8080/accounts/1{?projection}",
"templated": true
},
"accountDetail": {
"href": "http://localhost:8080/accounts/1/accountDetail"
}
}
}
Unfortunately, with this solution, it still makes 2 sql requests.
Using Hibernate Bytecode Enhancement with #LazyToOne(LazyToOneOption.NO_PROXY)
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#LazyToOne(LazyToOneOption.NO_PROXY)
private AccountDetail accountDetail;
Even this solution does not solve the issue.
Conclusion
I am not sure to understand why hibernate is fetching the child. At the moment it is not a big issue, but if the number of database records gets big it could impact on the performance.
Does anybody have a similar issue and maybe know how to solve it?
Update: Including the debug log for a GET request
2020-05-27 15:23:58.414 INFO 2119 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat-3].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-05-27 15:23:58.414 INFO 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-05-27 15:23:58.414 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
2020-05-27 15:23:58.415 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2020-05-27 15:23:58.415 INFO 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2020-05-27 15:23:58.415 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/accounts/1", parameters={}
2020-05-27 15:23:58.416 DEBUG 2119 --- [nio-8080-exec-1] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped to org.springframework.data.rest.webmvc.RepositoryEntityController#getItemResource(RootResourceInformation, Serializable, PersistentEntityResourceAssembler, HttpHeaders)
2020-05-27 15:23:58.416 DEBUG 2119 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] stomAnnotationTransactionAttributeSource : Adding transactional method 'findById' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1137877934<open>)] for JPA transaction
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.jdbc.datasource.DataSourceUtils : Setting JDBC Connection [HikariProxyConnection#646683728 wrapping conn193: url=jdbc:h2:mem:f0df200a-97ea-4e5b-9f80-050ff9a550f7 user=SA] read-only
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#3a65c273]
2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] org.hibernate.SQL : select account0_.id as id1_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from account account0_ where account0_.id=?
2020-05-27 15:23:58.417 TRACE 2119 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] org.hibernate.SQL : select accountdet0_.account_id as account_3_1_0_, accountdet0_.first_name as first_na1_1_0_, accountdet0_.last_name as last_nam2_1_0_ from account_detail accountdet0_ where accountdet0_.account_id=?
2020-05-27 15:23:58.418 TRACE 2119 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1137877934<open>)]
2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.jdbc.datasource.DataSourceUtils : Resetting read-only flag of JDBC Connection [HikariProxyConnection#646683728 wrapping conn193: url=jdbc:h2:mem:f0df200a-97ea-4e5b-9f80-050ff9a550f7 user=SA]
2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction
2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/hal+json', given [*/*] and supported [application/hal+json]
2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Resource { content: Account(id=1, username=warrior24, password=1234), links: [<http://localhost:8080 (truncated)...]
2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] s.d.r.w.j.PersistentEntityJackson2Module : Serializing PersistentEntity org.springframework.data.jpa.mapping.JpaPersistentEntityImpl#45b7be72.
2020-05-27 15:23:58.423 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages] - neither plain properties nor XML
2020-05-27 15:23:58.424 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages_en] - neither plain properties nor XML
2020-05-27 15:23:58.424 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages_en_GB] - neither plain properties nor XML
2020-05-27 15:23:58.426 DEBUG 2119 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2020-05-27 15:23:58.426 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK
Try to use #JsonIgnore annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality.
#JsonIgnore
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private AccountDetail accountDetail;
Try to put #EqualsAndHashCode.Exclude annotation to exclude accountDetail from equals and hashCode methods
#EqualsAndHashCode.Exclude
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private AccountDetail accountDetail;
SOLUTION (kind of)
Okay I think I found the solution but unfortunately it is not compatible with Spring Data Rest.
By adding optional = false, you can tell hibernate that you will ensure that the child reference will never be null (in my case AccountDetail).
Therefore, it will not need to fetch the child in order to check if it has to create a proxy or assign NULL.
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
#LazyToOne(LazyToOneOption.NO_PROXY)
private AccountDetail accountDetail;
The issue with this solution is that you cannot create a new Account resource using the POST endpoint provided with the #RepositoryRestResource annotation.
Since, it now expects the AccountDetail to be not null, you need to provide the link of this resource in the JSON (which is not existing yet). And you cannot create the AccountDetail resource because it uses as primary key the foreign key from the Account table.

#Transactional does not rollback in springboot

In springboot(1.5.7) using mybatis and mysql(InnoDB), when inserting into database in one method ,some operations succeed and one of them occured data truncation exception.However, the succeeded ones didn't rollback, Why?
Is there anything wrong? I can't find anything in-depth and fully about spring transaction,Do anyone know or have?Thanks!
Here is the main App
#SpringBootApplication
#MapperScan("com.demo.dao")
#EnableTransactionManagement
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Here is the service
#Service
public class DemoServiceImpl implements DemoService {
#Autowired
private DemoMapper demoMapper;
#Transactional(propagation=Propagation.SUPPORTS,rollbackFor=Exception.class)
public void addInfo(List<EntityInfo> infos) {
demoMapper.insert(infos);
}
}
Here is DemoMapper interface
public interface DemoMapper {
void insert(#Param("infos")List<EntityInfo> infos);
}
Here is the mapper.xml content
<insert id="addInfos" parameterType="java.util.List">
<foreach collection="infos" item="info" open="" close="" separator=";">
INSERT INTO ....
</foreach>
</insert>
Here is the log
2017-10-14 19:18:48.501 DEBUG 35894 --- [ main] t.a.AnnotationTransactionAttributeSource : Adding transactional method 'com.weiyan.risk.apply.service.impl.CreditServiceImpl.addCreditInfo' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',-java.lang.Exception
2017-10-14 19:18:48.496 DEBUG 35894 --- [ main] t.a.AnnotationTransactionAttributeSource : Adding transactional method 'com.weiyan.risk.apply.service.impl.ApplyServiceImpl.modifyApplyResult' with attribute: PROPAGATION_SUPPORTS,ISOLATION_DEFAULT; '',-java.lang.Exception
2017-10-14 19:19:40.620 TRACE 35894 --- [io-30200-exec-1] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization
2017-10-14 19:19:40.620 TRACE 35894 --- [io-30200-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.weiyan.risk.apply.service.impl.CreditServiceImpl.addCreditInfo]
2017-10-14 19:19:40.621 INFO 35894 --- [io-30200-exec-1] c.w.r.apply.aop.InterfaceLogInterceptor : 请求开始,方法:addInfos
2017-10-14 19:19:40.666 TRACE 35894 --- [io-30200-exec-1] .s.t.s.TransactionSynchronizationManager : Bound value [org.mybatis.spring.SqlSessionHolder#5bee83c] for key [org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#6a075dde] to thread [http-nio-30200-exec-1]
...some long trace with attributes...
2017-10-14 19:19:40.761 INFO 35894 --- [io-30200-exec-1]
c.w.r.apply.aop.InterfaceLogInterceptor : exception:
org.springframework.dao.DataIntegrityViolationException:
### Error updating database. Cause:
com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'bank_cardno' at row 1
### The error may involve com.weiyan.risk.apply.dao.CreditMapper.addBanks-Inline
....stacktrace....
2017-10-14 19:19:40.763 TRACE 35894 --- [io-30200-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.weiyan.risk.apply.service.impl.CreditServiceImpl.addCreditInfo]
2017-10-14 19:19:40.763 TRACE 35894 --- [io-30200-exec-1] .s.t.s.TransactionSynchronizationManager : Removed value [org.mybatis.spring.SqlSessionHolder#5bee83c] for key [org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#6a075dde] from thread [http-nio-30200-exec-1]
....
2017-10-14 19:19:40.766 TRACE 35894 --- [io-30200-exec-1] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization

Resources