Spring #Transactional across JPA and JDBC - java

Setup:
spring-boot-starter-parent:2.5.3
spring-boot-starter-data-jpa (version ommitted, defined by parent)
PostgreSQL (latest version)
One datasource configured via spring.datasource properties
Description:
I have a method that uses JdbcOperations (JdbcTemplate) to insert a value into a table.
This table has a foreign key constraint to another table which is managed by a JPA CrudRepository.
The method in question looks like this:
private static final String SAVE_SQL = "INSERT INTO table (entity_id, value) VALUES (?, ?) ON CONFLICT (entity_id, value) DO UPDATE SET value = EXCLUDED.value";
#Transactional
public void save(long id, String value) {
this.otherService.getOrCreateEntity(id);
this.jdbcOperations.update(SAVE_SQL, (ps) -> {
ps.setLong(id);// this is a foreign key to the JPA managed table
ps.setString(value);
});
}
The method of otherService looks like this:
#Transactional
public Entity getOrCreateEntity(long id) {
final Optional<Entity> optionalEntity = this.crudRepository.findById(id);
Entity entity;
if (optionalEntity.isEmpty()) {
entity = new Entity();
entity.setId(id);
entity = this.crudRepository.save(entity);
} else {
entity = optionalEntity.get();
}
return entity;
}
I added the following logging settings to see which transactions are being used:
logging:
level:
org.springframework.orm.jpa: DEBUG
org.springframework.transaction: DEBUG
When the save(long, String) method is called, I can see that a new transaction is created. The otherService.getOrCreateEntity(long)-Method inserts a new Entity and returns it to the method in question.
When the method in question tries to insert its own value using JdbcOperations, I get a DataIntegrityViolationException : violates foreign key constraint.
This is the transaction log:
2021-09-09 21:42:29.704 DEBUG 16077 --- [nio-9000-exec-7] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [TheJdbcOperationsUsingService.save]: PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED
2021-09-09 21:42:29.704 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(2113410426<open>)] for JPA transaction
2021-09-09 21:42:29.704 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#1e809ae2]
-- Call to otherService.getOrCreateEntity(long) start
2021-09-09 21:43:17.710 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2113410426<open>)] for JPA transaction
2021-09-09 21:43:17.711 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2021-09-09 21:43:17.712 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2113410426<open>)] for JPA transaction
2021-09-09 21:43:17.713 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2021-09-09 21:43:17.719 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2113410426<open>)] for JPA transaction
2021-09-09 21:43:17.720 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
-- Call to otherService.getOrCreateEntity(long) finished
2021-09-09 21:43:51.374 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Initiating transaction rollback
2021-09-09 21:43:51.374 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Rolling back JPA transaction on EntityManager [SessionImpl(2113410426<open>)]
2021-09-09 21:43:51.376 DEBUG 16327 --- [nio-9000-exec-6] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(2113410426<open>)] after transaction
I tried with different Propagation and Isolation Settings on both sides, but with no luck.

The problem you're running into is that the EntityManager is keeping a cache of unsaved changes in memory until the transaction closes, and so when you try to insert your data via JDBC directly the database still hasn't seen it.
To fix this, you can use the more specific interface JpaRespository, which has a saveAndFlush method that instructs the EntityManager to immediately write out the insert and not to batch the writes later.

Related

Spring opens a new transaction for each JpaRepository method that is called within an #Transactional annotated method

I have a method that is annotated with #Transactional. That should mean that any database queries that are fired within this method should all use the same transaction. But in reality that doesn't happen. What does happen is that a transaction is opened for the method itself but then when the first JpaRepository method is called a new transaction is opened for that particular method call.
To make matters more complex, For custom repository methods this new transaction is only opened when the JpaRepository or the JpaRepository custom method is annotated with #Transactional as well.
If not i get the following trace log statement about it:
No need to create transaction for
[org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]:
This method is not transactional.
So it doesn't create a new transaction but it also doesn't seem to use the transaction created by the calling method either.
Heres the repository class:
#Repository
public interface LanguageDao extends JpaRepository<Language, Long> {
#Transactional
public Language findByLanguageCode(String languageCode);
public Language findByIdNotNull();
}
Heres the method that uses different repository methods.
#Transactional
public void afterSingletonsInstantiated() {
languageDao.findByLanguageCode(); //This custom method opens a new transaction, but only because i've annotated this method with #Transactional as well.
languageDao.findAll(); //This one as well because its a standard JpaRepository method.
languageDao.findByIdNotNull();//This custom method doesn't because it lacks its own #Transactional annotation.
}
Heres the #Configuration file, with transaction management and jpa repositories enabled
#EnableJpaRepositories(basePackages={"DAOs"}, transactionManagerRef = "customTransactionManager", enableDefaultTransactions = true)
#EnableTransactionManagement
#Configuration
public class RootConfig implements InitializingBean {
#Bean(name = "customTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
if (shouldCreateInitialLuceneIndex) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
createInitialLuceneIndex(entityManager);
entityManager.close();
}
return transactionManager;
}
}
Relevant application.properties settings
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.open-in-view = false
A bit of actual logs. The first line shows that a transaction for the method afterSingletonsInstantiated is created.
[TRACE] 2021-11-08 15:32:40.811 [main] TransactionInterceptor - Getting transaction for [config.StartupChecks$$EnhancerBySpringCGLIB$$134b7631.afterSingletonsInstantiated]
[INFO ] 2021-11-08 15:32:40.815 [main] StartupChecks - Calling sequence table reset procedure
[DEBUG] 2021-11-08 15:32:40.833 [main] SQL - {call RESET_SEQUENCE_TABLE_VALUES_TO_LATEST_ID_VALUES()}
[INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - Sequence tables reset call finished!
[INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - doing stuff
[INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - testing!
[TRACE] 2021-11-08 15:32:41.087 [main] TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
[DEBUG] 2021-11-08 15:32:41.088 [main] SQL - select language0_.id as id1_77_, language0_.dateCreated as datecrea2_77_, language0_.englishLanguageName as englishl3_77_, language0_.languageCode as language4_77_, language0_.rightToLeft as righttol5_77_, language0_.translatedLanguageName as translat6_77_ from languages language0_
[TRACE] 2021-11-08 15:32:41.091 [main] TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
[INFO ] 2021-11-08 15:32:41.091 [main] StartupChecks - end test!
[TRACE] 2021-11-08 15:32:41.091 [main] TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode]
[DEBUG] 2021-11-08 15:32:41.112 [main] SQL - select language0_.id as id1_77_, language0_.dateCreated as datecrea2_77_, language0_.englishLanguageName as englishl3_77_, language0_.languageCode as language4_77_, language0_.rightToLeft as righttol5_77_, language0_.translatedLanguageName as translat6_77_ from languages language0_ where language0_.languageCode=?
[TRACE] 2021-11-08 15:32:41.113 [main] TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode]
[TRACE] 2021-11-08 15:32:41.113 [main] TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]: This method is not transactional.
[DEBUG] 2021-11-08 15:32:41.115 [main] SQL - select authority0_.ID as id1_7_, authority0_.dateCreated as datecrea2_7_, authority0_.NAME as name3_7_ from AUTHORITY authority0_ where authority0_.ID is not null limit ?
[TRACE] 2021-11-08 15:32:41.120 [main] TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]: This method is not transactional.
Here is a list of the things that i've already tried.
Annotate languageDao with #Transactional(propagation = Propagation.SUPPORTS) or
#Transactional(propagation = Propagation.NESTED). NESTED isn't supported by hibernate and thus this causes an error, This error remains even when i set nestedTransactionAllowed to true on the transactionmanager. The setting SUPPORTS is ignored. The repository still starts a new transaction for each method that is called. (Update: Propagation.MANDATORY has no effect either)
I've named my transactionmanager customTransactionManager and added this as a parameter to #EnableJpaRepositories like so: #EnableJpaRepositories(basePackages={"DAOs"}, transactionManagerRef = "customTransactionManager")
I've set enableDefaultTransactions of #EnableJpaRepositories to false. This causes default methods like findAll() and save() to no longer be executed in a transaction by default. However it doesn't force them to use the transaction of the calling method that was annotated with #Transactional.
So my question is: How do i make the (custom) jpa repositories use the transaction that was started by the calling method?
EDIT: Here JPA - Spanning a transaction over multiple JpaRepository method calls a similar problem is described. According to the user spring only uses the existing transaction when the repository implements Repository instead of CrudRepository or JpaRepository. But this is a workaround.
EDIT 2: My #Transactional annotations keep working when i remove #EnableTransactionManagement. According to this post that can occur when i use spring-boot-starter-jdbc or spring-boot-starter-data-jpa as a dependency, which i do. Could these dependencies somehow interfere with the normal working of the transaction manager?
Here is my attempt at understanding your problem. I would recommend enabling extra debug
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
My test Service class - note that this is marked as transactional - for now that's the only place it is put as that's what we intend - to create a transactional boundary.
#Service
public class LanguageService {
#Autowired
private LanguageRepository languageRepository;
#Transactional
public void runAllMethods() {
languageRepository.findByLanguageCode("en");
languageRepository.findAll();
languageRepository.findByIdNotNull();
}
}
Next is the repository - there are no transactional annotations.
public interface LanguageRepository extends JpaRepository<Language, Long> {
public Language findByLanguageCode(String languageCode);
public Language findByIdNotNull();
}
Now on hitting the service via a controller - I get below logs. Notice the line where it says "Creating new transaction with name [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT" - meaning that the transaction was created at the beginning of method invocation.
Also note the statement "Participating in existing transaction" which indicates that method is participating in transaction.
2021-11-09 11:43:06.061 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2084817241<open>)] for JPA transaction
2021-11-09 11:43:06.061 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2021-11-09 11:43:06.069 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#3107a702]
2021-11-09 11:43:06.069 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]
2021-11-09 11:43:06.099 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode]: This method is not transactional.
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_ where language0_.language_code=?
2021-11-09 11:43:06.333 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2084817241<open>)] for JPA transaction
2021-11-09 11:43:06.333 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2021-11-09 11:43:06.333 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByIdNotNull]: This method is not transactional.
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_ where language0_.id is not null
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]
2021-11-09 11:43:06.348 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2021-11-09 11:43:06.348 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(2084817241<open>)]
For readonly methods - like findAll - you would see "No need to create transaction" - that's because although the default Repository implementation "SimpleJpaRepository" is marked as transactional - the readonly methods are not marked transactional.
#Repository
#Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
After having tried different things, including using a TransactionTemplate i've settled for the following solution:
First i've turned off the default transaction policy for the jparepository methods by annotating a configuration class with the following:
#EnableJpaRepositories(enableDefaultTransactions = false)
enableDefaultTransactions = false causes any inherited method of JpaRepository to stop creating a transaction whenever they're called. Only jpa methods that are explicitly annotated with #Transactional will continue to create a new transaction when called.
All the other ones will now use any transaction that is started by the calling method, for instance a service method that is annotated with #Transactional.
This isn't obvious though because the This method is not transactional log trace message will still be generated for any jpa method that isn't explicitly annotated with #Transactional. This can be a bit confusing.
However i've proven that these methods really do use the transaction of the calling method by testing it with the following custom update method.
#Modifying
#Query("UPDATE User u SET u.userStatus = 1 WHERE u.userStatus = 0")
public void resetActiveUserAccountsToStatusOffline();
Such a method needs to have a transaction or else the exception javax.persistence.TransactionRequiredException: Executing an update/delete query is thrown. But as you can see this jpa method wasn't annotated with #Transactional so it really did use the transaction that was started by the calling service method.
There is one small disadvantage to setting enableDefaultTransactions = false and that is that the transaction type of inherited methods like findAll will not always use a transaction that is read only. This really depends on whether the service level transaction is readonly or not. However you could still override the findAll method and explictly annotate it with Transactional(readOnly = false). Another thing to beware of is that any calling method must always be annotated with #Transactional or the jpa method will run outside a transaction.
I think the advantage far outweighs these small disadvantages though. Because it is very costly performance wise when a new transaction is created for every jpa method call. So this is the solution i'll settle for right now.
To test your own transactions you'll need to add this to your application.properties
logging.level.org.springframework.transaction.interceptor=TRACE
If the setting doesn't work please add Log4j2 to your project.
EDIT:
These additional transactions that are opened by the JpaMethods are only logical transactions when a physical transaction has already been created by the calling method. More about this here: https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction
These jpa methods still use the transaction created by the calling method.
The last answer in this SO thread also explains the difference between logical and physical transactions well: Difference between physical and logical transactions in spring
Experienced same issue while having multiple datasources, hence multiple transaction managers. Apparently the problem was that service methods marked #Transactional used the primary transaction manager, while the repositories were configured to use custom transaction manager:
#EnableJpaRepositories(
basePackageClasses = {
MyRepository.class
},
entityManagerFactoryRef = "customEntityManager",
transactionManagerRef = "customTransactionManager"
)
Solved the issue using spring's annotation on service methods with transactionManager param specified #Transactional(transactionManager = "customTransactionManager")

#Transactional rollback session.save() on exception

I've been testing rollbacks with the #Transactional annotation. If an unchecked exception is thrown, session operations should be rolled back.
I am using org.hibernate.dialect.Oracle12cDialect.
I have #EnableTransactionManagement enabled.
In this first example, entity already exists in the database. I am updating it's name from "Test" to "Test123". After session.saveOrUpdate() is called, a RuntimeException is thrown. Checking the database after the fact, the update did not go through.
#RequestMapping("/test")
#RestController
#Transactional
public class TestController {
#Autowired
protected SessionFactory sessionFactory;
#RequestMapping(value = "")
public String test() {
Session session = sessionFactory.getCurrentSession();
MyEntity entity = new MyEntity();
entity.setId(123); // Update existing
entity.setName("Test123");
entity.setCreateUser("user1");
entity.setUpdateUser("user1");
session.saveOrUpdate(entity);
throw new RuntimeException("Test");
}
}
In the second example, entity does not yet exist in the database. After calling session.saveOrUpdate() a RuntimeException() is thrown again. When checking the database, the new record was created.
#RequestMapping("/test")
#RestController
#Transactional
public class TestController {
#Autowired
protected SessionFactory sessionFactory;
#RequestMapping(value = "")
public String test() {
Session session = sessionFactory.getCurrentSession();
MyEntity entity = new MyEntity();
entity.setId(null); // Create new
entity.setName("TestNew");
entity.setCreateUser("user1");
entity.setUpdateUser("user1");
session.saveOrUpdate(entity);
throw new RuntimeException("Test");
}
}
One interesting thing I found is, in the first example, hibernate does not print out any SQL in the console. In the second example though, the insert statement is printed in the console before the exception is thrown. Does this make a difference?
In this final example, I wrap my statements in a manual transaction. Similar to the second example, entity does not exist in the database. We are creating a new instance. Within the transaction I throw a RuntimeException()
this exception gets caught and the transaction is rolled back successfully. The new record is not in the database.
#RequestMapping("/test")
#RestController
#Transactional
public class TestController {
#Autowired
protected SessionFactory sessionFactory;
#RequestMapping(value = "")
public String test() {
Session session = sessionFactory.getCurrentSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
MyEntity entity = new MyEntity();
entity.setId(null); // Create new
entity.setName("TestNew2");
entity.setCreateUser("user1");
entity.setUpdateUser("user1");
session.saveOrUpdate(entity);
throw new RuntimeException("Test");
tx.commit();
} catch(Exception e) {
if (tx != null) {
tx.rollback();
}
}
}
}
Why does example two not rollback? But example three does successfully rollback?
Edit: I am also using Hikari CP. Which has auto-commit set to true. I'm not sure if that effects anything. But when I set that to false, I at least then don't see the change in the database. But I believe that is because I would need to do manual transactions at that point.
Also here are the logs incase there is anything helpful in there:
2021-02-21 10:44:42.604 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-02-21 10:44:42.650 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.orm.jpa.EntityManagerHolder#68d89998] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#367628c8] to thread [http-nio-8081-exec-1]
2021-02-21 10:44:42.659 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#68d89998] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#367628c8] bound to thread [http-nio-8081-exec-1]
2021-02-21 10:44:42.659 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Found thread-bound EntityManager [org.hibernate.jpa.internal.EntityManagerImpl#393e2dcf] for JPA transaction
2021-02-21 10:44:42.659 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Creating new transaction with name [com.package.myentity.web.PingService.createOrUpdate]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2021-02-21 10:44:42.709 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#77af7c9c]
2021-02-21 10:44:42.709 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.jdbc.datasource.ConnectionHolder#77b95111] for key [HikariDataSource (My WS)] to thread [http-nio-8081-exec-1]
2021-02-21 10:44:42.709 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization
2021-02-21 10:44:42.710 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.package.myentity.web.PingService.createOrUpdate]
2021-02-21 10:44:42.713 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.orm.hibernate5.SessionHolder#4a056888] for key [org.hibernate.internal.SessionFactoryImpl#4639d92c] to thread [http-nio-8081-exec-1]
2021-02-21 10:36:25.781 DEBUG VT0718LA899 --- [nio-8081-exec-2] o.h.SQL :
insert
into
dev_schema.my_entity
(id, name, create_user, update_user)
values
(?, ?, ?, ?)
44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.package.myentity.web.PingService.createOrUpdate] after exception: java.lang.RuntimeException
2021-02-21 10:44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.t.i.RuleBasedTransactionAttribute : Applying rules to determine whether transaction should rollback on java.lang.RuntimeException
2021-02-21 10:44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.t.i.RuleBasedTransactionAttribute : Winning rollback rule is: null
2021-02-21 10:44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.t.i.RuleBasedTransactionAttribute : No relevant rollback rule found: applying default rules
2021-02-21 10:44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Triggering beforeCompletion synchronization
2021-02-21 10:44:42.935 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.orm.hibernate5.SessionHolder#4a056888] for key [org.hibernate.internal.SessionFactoryImpl#4639d92c] from thread [http-nio-8081-exec-1]
2021-02-21 10:44:42.935 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Initiating transaction rollback
2021-02-21 10:44:42.935 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Rolling back JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl#393e2dcf]
2021-02-21 10:44:42.986 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization
2021-02-21 10:44:42.986 TRACE VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Triggering afterCompletion synchronization
2021-02-21 10:44:42.988 TRACE VT0718LA899 --- [nio-8081-exec-1] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.jdbc.datasource.ConnectionHolder#77b95111] for key [HikariDataSource (My WS)] from thread [http-nio-8081-exec-1]
2021-02-21 10:44:42.988 DEBUG VT0718LA899 --- [nio-8081-exec-1] o.s.o.j.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction
java.lang.RuntimeException
...
Well, auto-commit means that after every statement, the transaction is committed and a new one is started. The insert statement does not fail, so this is committed then. The update statement fails though, which is why the transaction is rolled back and you are not seeing the change. Always disable auto-commit.

Setup h2 in spring boot application with r2dbc and flyway

I'm playing around with Spring Boot and the reactive jdbc driver called r2dbc. In my main application I'm using Postgres as a database and now I want to the use h2 for the tests. And the Flyway migration is working with the setup but when the Spring application is able to insert records.
Here is my setup and code
#SpringBootTest
class CustomerRepositoryTest {
#Autowired
CustomerRepository repository;
#Test
void insertToDatabase() {
repository.saveAll(List.of(new Customer("Jack", "Bauer"),
new Customer("Chloe", "O'Brian"),
new Customer("Kim", "Bauer"),
new Customer("David", "Palmer"),
new Customer("Michelle", "Dessler")))
.blockLast(Duration.ofSeconds(10));
}
}
Here is the error that I'm getting
:: Spring Boot :: (v2.3.4.RELEASE)
2020-10-14 15:59:18.538 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : Starting CustomerRepositoryTest on imalik8088.fritz.box with PID 25279 (started by imalik in /Users/imalik/code/private/explore-java/spring-example)
2020-10-14 15:59:18.540 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : No active profile set, falling back to default profiles: default
2020-10-14 15:59:19.108 INFO 25279 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2020-10-14 15:59:19.273 INFO 25279 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 160ms. Found 1 R2DBC repository interfaces.
2020-10-14 15:59:19.894 INFO 25279 --- [ main] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 6.5.0 by Redgate
2020-10-14 15:59:20.052 INFO 25279 --- [ main] o.f.c.internal.database.DatabaseFactory : Database: jdbc:h2:mem:///DBNAME (H2 1.4)
2020-10-14 15:59:20.118 INFO 25279 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.022s)
2020-10-14 15:59:20.131 INFO 25279 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2020-10-14 15:59:20.175 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >>
2020-10-14 15:59:20.178 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version 1.0.0 - schma
2020-10-14 15:59:20.204 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.036s)
2020-10-14 15:59:20.689 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : Started CustomerRepositoryTest in 2.466 seconds (JVM running for 3.326)
2020-10-14 15:59:21.115 DEBUG 25279 --- [ main] o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO customer (first_name, last_name) VALUES ($1, $2)]
org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO customer (first_name, last_name) VALUES ($1, $2)]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Tabelle "CUSTOMER" nicht gefunden
Table "CUSTOMER" not found; SQL statement:
INSERT INTO customer (first_name, last_name) VALUES ($1, $2) [42102-200]
My src/test/resources/application.yaml is looking like this:
spring:
r2dbc:
url: r2dbc:h2:mem:///DBNAME?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
flyway:
url: jdbc:h2:mem:///DBNAME
baseline-on-migrate: true
user: sa
password:
Any ideas whats missing missing or whats wrong with the setup? If further information is needed please let me know.
Addition/Solution:
The url pattern is different between jdbc and r2dbc. The working solution for me is as follows:
url: r2dbc:h2:file:///./tmp/test-database
url: jdbc:h2:file:./tmp/test-database
And In order to setup Flyway you have to Configure Flyway:
// Flyway is not compatible with r2dbc yet, therefore this config class is created
#Configuration
public class FlywayConfig {
private final Environment env;
public FlywayConfig(final Environment env) {
this.env = env;
}
#Bean(initMethod = "migrate")
public Flyway flyway() {
return new Flyway(Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
env.getRequiredProperty("spring.flyway.url"),
env.getRequiredProperty("spring.flyway.user"),
env.getRequiredProperty("spring.flyway.password"))
);
}
}
I've faced the same issue to setup and access to h2 database in memory for tests:
Liquibase for database migration using JDBC driver
Tests Reactive Crud Repository using R2DBC driver
Error encoutred:
org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO MY_TABLE... Table "MY_TABLE" not found ...
Inspired by Chris's solution, i configured my src/testresources/application.properties file as follow:
spring.r2dbc.url=r2dbc:h2:mem:///~/db/testdb
spring.r2dbc.username=sa
spring.r2dbc.password=
spring.liquibase.url=jdbc:h2:mem:~/db/testdb;DB_CLOSE_DELAY=-1
spring.liquibase.user=sa
spring.liquibase.password=
spring.liquibase.enabled=true
I am currently having the same problem using r2dbc with liquibase. I am suspecting that the JDBC url points to a different database due to a slightly different syntax between R2DB and JDBC. I can manage to get h2 running from the file system though...
url: r2dbc:h2:file:///~/db/testdb
...
url: jdbc:h2:file:~/db/testdb
EDIT:
In non-reactive Spring Data I'd usually populate the Schema into the H2 memory database using a schema.sql/data.sql pair. This is also possible with R2DBC, but you have to configure the populator yourself.
It's also in the Getting Started R2DBC Tutorial. Basically you have to register a ConnectionFactoryInitializer bean.
#Bean
public ConnectionFactoryInitializer initializer(#Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
var initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
var populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
I was able to get it working.
First of all I created following test configuration class (because I want to execute tests only agains H2, on production mode I am using PostgreSQL):
#TestConfiguration
public class TestConfig {
#Bean
#Profile("test")
public ConnectionFactory connectionFactory() {
System.out.println(">>>>>>>>>> Using H2 in mem R2DBC connection factory");
return H2ConnectionFactory.inMemory("testdb");
}
#Bean(initMethod = "migrate")
#Profile("test")
public Flyway flyway() {
System.out.println("####### Using H2 in mem Flyway connection");
return new Flyway(Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
"jdbc:h2:mem:testdb",
"sa",
"")
);
}
}
As you can see in the code above, both beans are scoped to the "test" profile only. As you can imagine I have pretty much the same beans in a regular ApplicationConfiguration class but annotated as a #Profile("default") and configured to use a PostgreSQL.
Second thing is that I created annotation which combines several other annotations to not repeat myself and to easily pickup beans declared in the TestConfig class:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#SpringBootTest
#ActiveProfiles("test")
#Import(TestConfig.class)
public #interface IntegrationTest {
}
Now the test itself:
#IntegrationTest
class CartsIntegrationTest {
// test methods here ....
}
I believe the main hint is to use H2ConnectionFactory.inMemory("testdb");
Flyway currently only supports the blocking JDBC APIs, and it is not compatible with the reactive r2dbc if possbile do not mix them in the same application.
Try to register a ConnectionFactoryInitializer to initiate the database schema and data as #Chris posted, my working example can be found here.
Try nkonev/r2dbc-migrate which is trying to migrate the flyway to the R2dbc world.
There were 2 issues I was experiencing in my project.
I needed to include the dependency:
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
I needed to change the value for spring.r2dbc.url to r2dbc:h2:mem:///test_db
With these changes, rd2bc worked with an in memory h2 database for testing. See also:
https://github.com/r2dbc/r2dbc-h2

JPA save with multiple entities not rolling back when inside Spring #Transactional and rollback for Exception.class enabled

I have been at this for a good few hours and I can not find a way to get around what seems to be JPARepository automatically starting/committing a transaction when I save entities inside an encapsulating transaction.
I create 3 entities in total.
The first 2, ClientAdmin and CreditPot are to be made so when the third Institution entity is made, the entities can form relationships before being persisted.
When an Exception is thrown, I wanted it so that the created entities are NOT COMMITED (i.e. the entire process rolled back).
At the moment, I can see in the logs that the JPA Save method may start its own transaction (I don't fully understand the logs) which causes the entities that have called repo.save(entity) to be committed in their own transaction?
Any help would be greatly appreciated.
#Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void createInstitution(InstitutionSignUpForm form) throws Exception {
//Create Admin account.
ClientAdmin clientAdmin = new ClientAdmin();
clientAdmin.setRole(roleRepo.findOneByRole("SUPER_ADMIN").orElseThrow(() -> new Exception("Can not resolve role SUPER_ADMIN")));
clientAdmin.setPassword(passwordEncoder.encode(form.getPassword()));
clientAdmin.setUsername(form.getUsername());
clientAdmin = clientAdminRepo.save(clientAdmin);
//Create Credit Pot for institution.
CreditPot creditPot = new CreditPot();
creditPot.setIsPrimaryPot(true);
creditPot.setName("Primary Credit Pot");
creditPot.setCredits(300L);
creditPot = creditPotRepo.save(creditPot);
System.out.println("Throwing Exception.");
if(1==1) throw new Exception("!!!", new Throwable("!!!"));
//Create institution and assign relationships.
Institution institution = new Institution();
institution.setDiceCode(dicewareGenerator.generateRandomPassword(6));
institution.setName(form.getInstitutionName());
institution.setPrimaryCreditPot(creditPot);
clientAdmin.setInstitution(institution);
institutionRepo.saveAndFlush(institution);
}
The logs:
2019-09-12 10:46:41.148 DEBUG 24621 --- [nio-5000-exec-6] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2019-09-12 10:46:41.148 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.149 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.149 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(652375272<open>)] for JPA transaction
2019-09-12 10:46:41.149 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.j3den.edu.webserver.services.sigup.InstitutionSignUpService.createInstitution]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
2019-09-12 10:46:41.150 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#74972cba]
2019-09-12 10:46:41.150 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.jdbc.datasource.ConnectionHolder#19e50439] for key [HikariDataSource (HikariPool-1)] to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.150 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization
2019-09-12 10:46:41.150 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Getting transaction for [com.j3den.edu.webserver.services.sigup.InstitutionSignUpService.createInstitution]
2019-09-12 10:46:41.150 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#304f1d77] for key [public abstract java.util.Optional com.j3den.edu.models.repos.RoleRepo.findOneByRole(java.lang.String)] to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.151 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findOneByRole]: This method is not transactional.
2019-09-12 10:46:41.151 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.151 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.153 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#304f1d77] for key [public abstract java.util.Optional com.j3den.edu.models.repos.RoleRepo.findOneByRole(java.lang.String)] from thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.242 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#2d5257a2] for key [public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object)] to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.242 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.242 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(652375272<open>)] for JPA transaction
2019-09-12 10:46:41.242 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder#19e50439] for key [HikariDataSource (HikariPool-1)] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.243 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2019-09-12 10:46:41.243 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2019-09-12 10:46:41.243 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.244 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#2d5257a2] for key [public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object)] from thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#2d5257a2] for key [public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object)] to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.245 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(652375272<open>)] for JPA transaction
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder#19e50439] for key [HikariDataSource (HikariPool-1)] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.245 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2019-09-12 10:46:41.245 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] bound to thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.246 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2019-09-12 10:46:41.247 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata#2d5257a2] for key [public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object)] from thread [http-nio-5000-exec-6]
Throwing Exception.
2019-09-12 10:46:41.247 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.TransactionInterceptor : Completing transaction for [com.j3den.edu.webserver.services.sigup.InstitutionSignUpService.createInstitution] after exception: java.lang.Exception: !!!
2019-09-12 10:46:41.247 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.RuleBasedTransactionAttribute : Applying rules to determine whether transaction should rollback on java.lang.Exception: !!!
2019-09-12 10:46:41.247 TRACE 24621 --- [nio-5000-exec-6] o.s.t.i.RuleBasedTransactionAttribute : Winning rollback rule is: RollbackRuleAttribute with pattern [java.lang.Exception]
2019-09-12 10:46:41.247 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Initiating transaction rollback
2019-09-12 10:46:41.247 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Rolling back JPA transaction on EntityManager [SessionImpl(652375272<open>)]
2019-09-12 10:46:41.248 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization
2019-09-12 10:46:41.248 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.jdbc.datasource.ConnectionHolder#19e50439] for key [HikariDataSource (HikariPool-1)] from thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.248 DEBUG 24621 --- [nio-5000-exec-6] o.s.orm.jpa.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction
2019-09-12 10:46:41.248 TRACE 24621 --- [nio-5000-exec-6] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.orm.jpa.EntityManagerHolder#5ed67928] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#70286b92] from thread [http-nio-5000-exec-6]
2019-09-12 10:46:41.248 DEBUG 24621 --- [nio-5000-exec-6] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
MySQL historically by default created tables using the MyISAM engine, which didn't support transactions. Later InnoDB was added which did support transactions, however MyISAM remained the default for quite some time.
Hibernate with its dialects followed the same defaults and hence the default for the MySQL dialect is MyISAM (for backwards compatibility).
You have 2 ways to fix this, either select a specific dialect or toggle from MyISAM to InnoDB with a specific property.
To select a dialect use the spring.jpa.database-platform to specify the required dependency.
spring.jpa.database-platform=org.hibernate.dialect.MySQL57InnoDBDialect`
or to set the property use
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
or a combination of both (as the MySQL57InnoDBDialect is deprecated now that there is the hibernate.dialect.storage_engine property).
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
See also: https://in.relation.to/2017/02/20/mysql-dialect-refactoring/

Hibernate Hangs on saveOrUpdate

I am doing a standart getHibernateTemplate().saveOrUpdate() in Spring Hibernate application, but function never returns and does not print any errors.
Debug log is like below.
19:06:07.014 [qtp8540084-26] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
19:06:07.014 [qtp8540084-26] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'transactionManager'
19:06:07.014 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Found thread-bound Session [org.hibernate.impl.SessionImpl#13ca565] for Hibernate transaction
19:06:07.014 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Creating new transaction with name [com.mydao.InventoryDAOImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
19:06:07.014 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl#13ca565]
19:06:07.014 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - begin
19:06:07.014 [qtp8540084-26] DEBUG org.hibernate.jdbc.ConnectionManager - opening JDBC connection
19:06:07.014 [qtp8540084-26] DEBUG o.s.j.d.DriverManagerDataSource - Creating new JDBCDriverManager Connection to [jdbc:hsqldb:file:hsqldb/MyDB]
19:06:07.014 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - current autocommit status: true
19:06:07.014 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - disabling autocommit
19:06:07.014 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Exposing Hibernate transaction as JDBC transaction [org.hsqldb.jdbc.jdbcConnection#65335b]
19:06:07.030 [qtp8540084-26] DEBUG o.s.orm.hibernate3.HibernateTemplate - Found thread-bound Session for HibernateTemplate
19:06:07.030 [qtp8540084-26] DEBUG o.s.orm.hibernate3.HibernateTemplate - Not closing pre-bound Hibernate Session after HibernateTemplate
19:06:07.030 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction rollback
19:06:07.030 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl#13ca565]
19:06:07.030 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - rollback
19:06:07.030 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - re-enabling autocommit
19:06:07.030 [qtp8540084-26] DEBUG o.h.transaction.JDBCTransaction - rolled back JDBC Connection
19:06:07.030 [qtp8540084-26] DEBUG org.hibernate.jdbc.ConnectionManager - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
19:06:07.030 [qtp8540084-26] DEBUG o.s.o.h.HibernateTransactionManager - Not closing pre-bound Hibernate Session [org.hibernate.impl.SessionImpl#13ca565] aftertransaction
19:06:07.030 [qtp8540084-26] DEBUG org.hibernate.impl.SessionImpl - disconnecting session
19:06:07.045 [qtp8540084-26] DEBUG org.hibernate.jdbc.ConnectionManager - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
19:06:07.045 [qtp8540084-26] DEBUG org.hibernate.jdbc.ConnectionManager - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
19:06:07.045 [qtp8540084-26] DEBUG o.s.a.f.a.ThrowsAdviceInterceptor - Found handler for exception of type [java.lang.Throwable]: public void org.springframework.flex.core.ExceptionTranslationAdvice.afterThrowing(java.lang.Throwable)throws java.lang.Throwable
19:06:07.077 [qtp8540084-26] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'Spring MVC Dispatcher Servlet': assuming HandlerAdapter completed request handling
19:06:07.077 [qtp8540084-26] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
19:06:07.092 [qtp8540084-26] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Chainprocessed normally
19:06:07.092 [qtp8540084-26] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter -SecurityContextHolder now cleared, as request processing completed
19:06:07.092 [qtp8540084-26] DEBUG o.s.o.h.s.OpenSessionInViewFilter - Closing single Hibernate Session in OpenSessionInViewFilter
19:06:07.092 [qtp8540084-26] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Closing Hibernate Session
Regards.
Edit1: I can use this same method of DAO for other entities successfully. Also, I can update a persistent object of this entity, but i can not "save" or "create" a new one with saveOrUpdate.
This hang can be caused by a deadlock between transactions.
Such deadlocks are often caused by incorrect use of several transactions in a single thread (if transactions A and B are created by a single thread, transaction A waits for transaction B to release a lock, but control flow never reaches the point where B is to be committed).
So, make sure that Spring transaction management is configured properly and that you don't mix Spring-managed transactions with manually managed ones.
Another possible reason is a long-running transaction created by external system. If you have some external systems connected to the database in question (for example, administration tools), make sure that transactions created by these systems are committed.
It can also happen if any of the child entities are lazy fetched.changing to eager fetch will resolve the issue

Categories

Resources