Transaction configure failed in Spring Boot - spring-boot

update:
I found that I inject class A into class C which extends an external class,
and that class didn't managed by spring, like this:
public class C extends ExternalClass {
#AutoWired
private A a;
//doSomething...
}
That should be the main cause of transaction failure.
Another question: is there any way to make spring manage transaction of class A which has injected into anothor class that isn't handled by spring?
I'm building a project with Spring Boot and Mybatis.
I have a problem that one of service class cannot a create transactional connection and won't perform a roll back.
I found that if I removed injection of A Class in B Class, like this:
class A{
//#Autowired
//private B b;
// b is not used in this class
#Autowired
private ADao dao;
}
class B{
#Autowired
private BDao dao;
//Transaction of this method failed
//session didn't roll back
public void (){
dao.insert(new Entity ());
//Exception here
}
}
The connection created by class B would be transactional. Both of two class are in the same package, but if I add that injection, the transaction would fail. What made me much confused is that class B can inject into other class, and transaction would work well too.
Here is the log:
2018-01-05 21:30:33.861 DEBUG 10346 --- [http-nio-8099-exec-2] org.mybatis.spring.SqlSessionUtils 97 : Creating a new SqlSession
2018-01-05 21:30:33.866 DEBUG 10346 --- [http-nio-8099-exec-2] org.mybatis.spring.SqlSessionUtils 148 : SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#3aeb5ca8] was not registered for synchronization because synchronization is not active
2018-01-05 21:30:33.888 DEBUG 10346 --- [http-nio-8099-exec-2] o.s.jdbc.datasource.DataSourceUtils 110 : Fetching JDBC Connection from DataSource
2018-01-05 21:30:33.888 DEBUG 10346 --- [http-nio-8099-exec-2] o.s.j.d.DriverManagerDataSource 142 : Creating new JDBC DriverManager Connection to [jdbc:mariadb://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true]
2018-01-05 21:30:33.905 DEBUG 10346 --- [http-nio-8099-exec-2] o.m.s.t.SpringManagedTransaction 87 : JDBC Connection [org.mariadb.jdbc.MySQLConnection#2bad8689] will not be managed by Spring
2018-01-05 21:30:33.908 DEBUG 10346 --- [http-nio-8099-exec-2] p.c.z.admin.dao.UserDao.insertSelective 159 : ==> Preparing: INSERT INTO sys_user ( id,username ) VALUES( ?,? )
2018-01-05 21:30:33.916 DEBUG 10346 --- [http-nio-8099-exec-2] p.c.z.admin.dao.UserDao.insertSelective 159 : ==> Parameters: null, test(String)
2018-01-05 21:30:33.929 DEBUG 10346 --- [http-nio-8099-exec-2] p.c.z.admin.dao.UserDao.insertSelective 159 : <== Updates: 1
2018-01-05 21:30:33.932 DEBUG 10346 --- [http-nio-8099-exec-2] p.c.z.a.d.U.insertSelective!selectKey 159 : ==> Executing: SELECT LAST_INSERT_ID()
2018-01-05 21:30:33.940 DEBUG 10346 --- [http-nio-8099-exec-2] p.c.z.a.d.U.insertSelective!selectKey 159 : <== Total: 1
2018-01-05 21:30:33.942 DEBUG 10346 --- [http-nio-8099-exec-2] org.mybatis.spring.SqlSessionUtils 191 : Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#3aeb5ca8]
I've tried 3 ways to config transaction:
with java config:
#Bean(name = "transactionInterceptor")
public TransactionInterceptor transactionInterceptor(PlatformTransactionManager platformTransactionManager) {
TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
transactionInterceptor.setTransactionManager(platformTransactionManager);
Properties transactionAttributes = new Properties();
transactionAttributes.setProperty("insert*","PROPAGATION_REQUIRED,-Throwable");
transactionAttributes.setProperty("update*","PROPAGATION_REQUIRED,-Throwable");
transactionAttributes.setProperty("delete*","PROPAGATION_REQUIRED,-Throwable");
transactionAttributes.setProperty("select*","PROPAGATION_REQUIRED,-Throwable,readOnly");
transactionInterceptor.setTransactionAttributes(transactionAttributes);
return transactionInterceptor;
}
#Bean
public BeanNameAutoProxyCreator transactionAutoProxy() {
BeanNameAutoProxyCreator transactionAutoProxy = new BeanNameAutoProxyCreator();
transactionAutoProxy.setProxyTargetClass(true);
transactionAutoProxy.setBeanNames("*ServiceImpl");
transactionAutoProxy.setInterceptorNames("transactionInterceptor");
return transactionAutoProxy;
}
and with xml:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="services"
expression="execution(* root.*.service.*.*(..))"/>
<aop:advisor pointcut-ref="services" advice-ref="txAdvice"/>
</aop:config>
and with #Transactional.
However none of them worked.

The problem is that if you inject a class into another one which is not a spring managed bean, then the spring transaction management will failed.
So while having a transaction failure, go check if there is a wrong dependency injection.

Related

EntityManager closed when executing queries on different threads

I am trying to execute a couple of queries on different threads. There are 2 top level queries each executing on different tables at runtime. For executing the first set of queries (executeQuery1()), I spawn 2 different threads and they are processed well. From the output of these queries, I have to extract a list of ids and then fire another set of queries (executeQuery2()) on entirely different threads. As soon as the second set of queries are about to be submitted to the database, I see that EntityManager is closed and the application shutdown.
2022-04-28 22:34:29.529 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'springApplicationAdminRegistrar'
2022-04-28 22:34:29.529 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'mbeanExporter'
2022-04-28 22:34:29.529 DEBUG 48403 --- [main] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
2022-04-28 22:34:29.529 DEBUG 48403 --- [main] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans
2022-04-28 22:34:29.529 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'defaultValidator'
2022-04-28 22:34:29.529 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'org.springframework.data.jpa.util.JpaMetamodelCacheCleanup'
2022-04-28 22:34:29.530 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'threadPoolTaskExecutor'
2022-04-28 22:34:29.530 DEBUG 48403 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'threadPoolTaskExecutor'
2022-04-28 22:34:29.531 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'verticaEntityManagerFactory': [verticaTransactionManager]
2022-04-28 22:34:29.531 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'verticaTransactionManager': [transactionTemplate]
2022-04-28 22:34:29.531 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking destroy() on bean with name 'verticaEntityManagerFactory'
2022-04-28 22:34:29.531 INFO 48403 --- [main] c.b.a.n.h.c.VerticaDataSourceConfig$1 : Closing JPA EntityManagerFactory for persistence unit 'vertica'
2022-04-28 22:34:29.531 DEBUG 48403 --- [main] o.hibernate.internal.SessionFactoryImpl : HHH000031: Closing
2022-04-28 22:34:29.531 TRACE 48403 --- [main] o.h.engine.query.spi.QueryPlanCache : Cleaning QueryPlan Cache
2022-04-28 22:34:29.531 TRACE 48403 --- [main] o.h.type.spi.TypeConfiguration$Scope : Handling #sessionFactoryClosed from [org.hibernate.internal.SessionFactoryImpl#77774571] for TypeConfiguration
2022-04-28 22:34:29.532 DEBUG 48403 --- [main] o.h.type.spi.TypeConfiguration$Scope : Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope#44af588b] from SessionFactory [org.hibernate.internal.SessionFactoryImpl#77774571]
2022-04-28 22:34:29.532 DEBUG 48403 --- [main] o.h.s.i.AbstractServiceRegistryImpl : Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
2022-04-28 22:34:29.532 DEBUG 48403 --- [main] o.h.b.r.i.BootstrapServiceRegistryImpl : Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
2022-04-28 22:34:29.532 TRACE 48403 --- [MyAsyncThread-4] j.i.AbstractLogicalConnectionImplementor : Preparing to begin transaction via JDBC Connection.setAutoCommit(false)
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'verticaDataSource': [dataSourceScriptDatabaseInitializer, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration, jdbcTemplate]
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'dataSourceScriptDatabaseInitializer': [jdbcTemplate, namedParameterJdbcTemplate]
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'jdbcTemplate': [namedParameterJdbcTemplate]
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': [jpaVendorAdapter, entityManagerFactoryBuilder]
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'jpaVendorAdapter': [entityManagerFactoryBuilder]
2022-04-28 22:34:29.532 TRACE 48403 --- [main] o.s.b.f.support.DisposableBeanAdapter : Invoking close() on bean with name 'verticaDataSource'
2022-04-28 22:34:29.533 INFO 48403 --- [main] com.zaxxer.hikari.HikariDataSource : vertica-db-pool - Shutdown initiated...
2022-04-28 22:34:29.533 DEBUG 48403 --- [main] com.zaxxer.hikari.pool.HikariPool : vertica-db-pool - Before shutdown stats (total=20, active=2, idle=18, waiting=0)
I have 2 datasources in my Spring Boot app and therefore have to configure datasources programmatically.
AsyncConfiguration.java
#EnableAsync
#Configuration
public class AsyncConfiguration implements AsyncConfigurer {
#Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("MyAsyncThread-");
executor.initialize();
return executor;
}
#Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
AsyncService.java
#Slf4j
#Service
public class AsyncService {
#Autowired VerticaRepository verticaRepo;
#Async("threadPoolTaskExecutor")
public CompletableFuture<List<Entity1>> execute1(String query) {
List<Entity1> result = verticaRepo.executeQuery1(query);
return CompletableFuture.completedFuture(result);
}
#Async("threadPoolTaskExecutor")
public CompletableFuture<List<Entity2>> execute2(List<BigInteger> ids, String query) {
List<Entity2> result = verticaRepo.executeQuery2(ids, query);
return CompletableFuture.completedFuture(result);
}
}
VerticaDataSourceConfig.java
#Configuration
#ConfigurationProperties("vertica.datasource")
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "verticaEntityManagerFactory",
transactionManagerRef = "verticaTransactionManager",
basePackages = { "mypackage.repository" }
)
public class VerticaDataSourceConfig /*extends HikariConfig*/ {
public final static String PERSISTENCE_UNIT_NAME = "vertica";
public final static String PACKAGES_TO_SCAN = "mypackage.entity";
#Autowired
private Environment env;
#Bean
public HikariDataSource verticaDataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(env.getProperty("vertica.datasource.jdbc-url"));
hikariConfig.setUsername(env.getProperty("vertica.datasource.username"));
hikariConfig.setPassword(env.getProperty("vertica.datasource.password"));
hikariConfig.setDriverClassName(env.getProperty("vertica.datasource.driver-class-name"));
hikariConfig.setConnectionTimeout(Long.parseLong(env.getProperty("vertica.datasource.hikari.connectionTimeout")));
hikariConfig.setIdleTimeout(Long.parseLong(env.getProperty("vertica.datasource.hikari.idleTimeout")));
hikariConfig.setMaxLifetime(Long.parseLong(env.getProperty("vertica.datasource.hikari.maxLifetime")));
hikariConfig.setKeepaliveTime(Long.parseLong(env.getProperty("vertica.datasource.hikari.keepaliveTime")));
hikariConfig.setMaximumPoolSize(Integer.parseInt(env.getProperty("vertica.datasource.hikari.maximumPoolSize")));
hikariConfig.setPoolName(env.getProperty("vertica.datasource.hikari.poolName"));
hikariConfig.setValidationTimeout(Integer.parseInt(env.getProperty("vertica.datasource.hikari.validationTimeout")));
return new HikariDataSource(hikariConfig);
}
#Bean
public LocalContainerEntityManagerFactoryBean verticaEntityManagerFactory(
final HikariDataSource verticaDataSource) {
return new LocalContainerEntityManagerFactoryBean() {{
setDataSource(verticaDataSource);
setPersistenceProviderClass(HibernatePersistenceProvider.class);
setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
setPackagesToScan(PACKAGES_TO_SCAN);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.ddl-auto", env.getProperty("vertica.jpa.hibernate.ddl-auto"));
jpaProperties.put("hibernate.show-sql", env.getProperty("vertica.jpa.hibernate.show-sql"));
jpaProperties.put("hibernate.format_sql", env.getProperty("vertica.jpa.hibernate.format_sql"));
jpaProperties.put("hibernate.dialect", env.getProperty("vertica.jpa.properties.hibernate.dialect"));
setJpaProperties(jpaProperties);
afterPropertiesSet();;
}};
}
#Bean
public PlatformTransactionManager verticaTransactionManager(EntityManagerFactory verticaEntityManagerFactory) {
return new JpaTransactionManager(verticaEntityManagerFactory);
}
}
VerticaRepository.java
#Repository
public class VerticaRepository {
//#Autowired
#PersistenceContext(unitName = "vertica")
private EntityManager em;
#Transactional
public List<Entity1> executeQuery1(String queryStr) {
// query.setParameter() can only replace parameters in WHERE clause of a query;
// it cannot replace table or column names
String replacedQuery = // replace table name and column name
Query query = em.createNativeQuery(replacedQuery);
List<Object[]> result = query.getResultList();
List<Entity1> entities = new ArrayList<>();
// fill entities list with result
return entities;
}
#Transactional
public List<Entity2> executeQuery2(List<BigInteger> ids, String queryStr) {
String replacedQuery = // replace table name and column name; the table and col names are different from the ones in executeQWuery1()
Query query = em.createNativeQuery(replacedQuery);
List<Object[]> result = query.getResultList();
List<Entity2> entities = new ArrayList<>();
// fill entities list with result
return entities;
}
}
BusinessService.java
#Slf4j
#Component("businessService")
public class BusinessService {
#Autowired
private String query1;
#Autowired
private String query2;
#Autowired private AsyncService asyncService;
public Void serve() throws Exception {
List<CompletableFuture<List<Entity1>>> violationFutures = new ArrayList<>();
for (iterate over some list not shown here; this will loop 2 times with different table and col name substitutions in the query) {
violationFutures.add(asyncService.execute1(query1));
}
CompletableFuture<List<List<Entity1>>> vcf = sequence(violationFutures);
List<Entity1> aggregatedViolations = new ArrayList<>();
for (List<Entity1> list: vcf.get()) {
aggregatedViolations.addAll(list);
}
int numProcessors = Runtime.getRuntime().availableProcessors();
List<BigInteger> idList= //somehow get a list of ids from aggregatedViolations
List<List<BigInteger>> partitionedList = ListUtils.partition(idList, numProcessors);
List<CompletableFuture<List<Entity2>>> trendFutures = new ArrayList<>();
for (List<BigInteger> ids: partitionedList) {
for (iterate over some list not shown here; this will loop 2 times with different table and col name substitutions in the query) {
trendFutures.add(asyncService.execute2(getIds(devices), query2));
}
}
CompletableFuture<List<List<Entity2>>> tcf = sequence(trendFutures);
// rest of the business logic is dependent on the above queries execution
return null;
}
private static <T> CompletableFuture<List<List<T>>> sequence(List<CompletableFuture<List<T>>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.toList())
);
}
I believe that this has something to do with the EntityManagers being used in multi-threaded env. However, when I read the documentation, #Transactional will supply a new EM everytime. If the executeQuery1() was able to run 2 queries in parallel on different threads, why is executeQuery2() closing the EM?

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

SpringBoot Transactional Propagation REQUIRES_NEW doesn't create new Transaction

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

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

Hibernate doesn't save edited entities, while saves new ones

I'm facing a weird problem. I'm currently writing a web application based on Spring-MVC 3.2, and Hibernate 4.1.9. I wrote a sample controller with its TestNG unit tests, and everything is fine except for editing. I can see that when saving a new object, it works like a charm, but if I try to edit an existing object, it doesn't get saved without giving out any reason (I'm calling the same method for adding and updating).
The log of adding a new object of type Application
14:26:36.636 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - DispatcherServlet with name '' processing POST request for [/app/add.json]
14:26:36.637 [main] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /app/add.json
14:26:36.650 [main] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Returning handler method [public java.lang.Long com.wstars.kinzhunt.platform.apps.web.AppController.createApp(com.wstars.kinzhunt.platform.model.apps.Application)]
14:26:36.651 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appController'
14:26:36.821 [main] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Reading [class com.wstars.kinzhunt.platform.model.apps.Application] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#5dc6bb75]
14:26:36.890 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Opening Hibernate Session
14:26:36.890 [main] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 13580799968
14:26:36.892 [main] DEBUG c.w.c.dao.hibernate.BaseDaoHibernate - Saving or Updating Object: com.wstars.kinzhunt.platform.model.apps.Application#54fc519b[id=<null>,name=KinzHunt,company=com.wstars.kinzhunt.platform.model.apps.Company#151c2b4[id=1,name=KinzHunt],callbackUrl=http://www.kinzhunt.com/callback/,website=http://www.kinzhunt.com,senderEmail=no-reply#kinzhunt.com,logoUrl=<null>]
14:26:36.892 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Opening Hibernate Session
14:26:36.893 [main] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 13580799968
14:26:36.893 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Closing Hibernate Session
14:26:36.894 [main] DEBUG o.h.e.def.AbstractSaveEventListener - executing identity-insert immediately
14:26:36.898 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
14:26:36.899 [main] DEBUG org.hibernate.jdbc.ConnectionManager - opening JDBC connection
14:26:36.899 [main] DEBUG o.s.j.d.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:mem:platform_test;DB_CLOSE_DELAY=-1]
14:26:36.901 [main] DEBUG org.hibernate.SQL - /* insert com.wstars.kinzhunt.platform.model.apps.Application */ insert into applications (id, callback_url, company_id, logo_url, name, sender_email, website) values (null, ?, ?, ?, ?, ?, ?)
14:26:36.904 [main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 2
14:26:36.904 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
14:26:36.905 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Closing Hibernate Session
14:26:36.905 [main] DEBUG org.hibernate.jdbc.ConnectionManager - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
14:26:36.905 [main] 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!
14:26:36.926 [main] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Written [2] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#5dc6bb75]
14:26:36.927 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
14:26:36.928 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Successfully completed request
While the log for saving an edited object is
14:27:03.398 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - DispatcherServlet with name '' processing POST request for [/app/1/edit.json]
14:27:03.398 [main] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /app/1/edit.json
14:27:03.401 [main] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Returning handler method [public java.lang.Long com.wstars.kinzhunt.platform.apps.web.AppController.editApp(com.wstars.kinzhunt.platform.model.apps.Application,java.lang.Long)]
14:27:03.401 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appController'
14:27:03.404 [main] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Reading [class com.wstars.kinzhunt.platform.model.apps.Application] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#5dc6bb75]
14:27:03.409 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Opening Hibernate Session
14:27:03.410 [main] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 13580800234
14:27:03.411 [main] DEBUG c.w.c.dao.hibernate.BaseDaoHibernate - Saving or Updating Object: com.wstars.kinzhunt.platform.model.apps.Application#1ba4f8a6[id=1,name=KinzHunt,company=com.wstars.kinzhunt.platform.model.apps.Company#6bc06877[id=1,name=KinzHunt],callbackUrl=http://www.kinzhunt.com/callback/,website=http://www.wstars.com/KinzHunt/,senderEmail=no-reply#kinzhunt.com,logoUrl=<null>]
14:27:03.412 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Opening Hibernate Session
14:27:03.412 [main] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 13580800234
14:27:03.413 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Closing Hibernate Session
14:27:03.422 [main] DEBUG o.s.o.hibernate3.SessionFactoryUtils - Closing Hibernate Session
14:27:03.424 [main] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Written [1] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#5dc6bb75]
14:27:03.424 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
14:27:03.425 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Successfully completed request
As you can see, in the second log, no prepared statement is created, and no JDBC connection is opened. My test configuration for the database is like this:
<bean id="targetDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:platform_test;DB_CLOSE_DELAY=-1" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="targetDataSource" />
<property name="packagesToScan">
<list>
<value>com.mypackage.model.*</value>
</list>
</property>
<property name="namingStrategy">
<bean class="com.example.common.config.MyOwnNamingStrategy"/>
</property>
<property name="hibernateProperties">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<entry key="hibernate.max_fetch_depth" value="1" />
<entry key="hibernate.use_sql_comments" value="true" />
<entry key="hibernate.hbm2ddl.auto" value="update" />
</map>
</property>
<!-- <property key="hibernate.current_session_context_class" value="thread"/> -->
<!-- <property key="hibernate.transaction.factory_class" value="org.hibernate.transaction.JDBCTransactionFactory"/> -->
</bean>
<bean id="h2WebServer" class="org.h2.tools.Server"
factory-method="createWebServer" depends-on="targetDataSource"
init-method="start" lazy-init="false">
<constructor-arg value="-web,-webPort,11111" />
</bean>
My controller code is:
#Controller
public class AppController extends BaseAnnotatedController {
#Autowired
private AppManagementService appManagementService;
#RequestMapping(value="/app/add", method=RequestMethod.POST, consumes={"application/json"})
public #ResponseBody Long createApp(#RequestBody Application app) {
saveApp(app);
return app.getId();
}
#RequestMapping(value="/app/{appId}/edit", method=RequestMethod.POST, consumes={"application/json"})
public #ResponseBody Long editApp(#RequestBody Application app, #PathVariable Long appId) {
if (!appId.equals(app.getId())) {
WSError error = new WSError(ValidationErrorType.GENERIC, "id");
throw new ValidationException(error);
} else {
saveApp(app);
return app.getId();
}
}
#RequestMapping(value="/app/list", method=RequestMethod.GET)
public #ResponseBody List<Application> listApps() {
return appManagementService.listAllApps();
}
#RequestMapping(value="/app/{appId}/get", method=RequestMethod.GET)
public #ResponseBody Application getAppDetails(#PathVariable Long appId) {
return appManagementService.getApplication(appId);
}
private void saveApp(Application app) {
if (isValid(app)) {
appManagementService.saveApp(app);
}
}
public #ResponseBody List<Application> listAllApps() {
return appManagementService.listAllApps();
}
}
My test class is:
#Test
public class AppControllerIntegrationTests extends AbstractContextControllerTests {
private MockMvc mockMvc;
#Autowired
AppController appController;
#Autowired
private AppManagementService appManagementService;
#Autowired
private BaseDao baseDao;
#BeforeClass
public void classSetup() {
Company comp = new Company();
comp.setName("Some Company");
baseDao.saveObject(comp);
}
#BeforeMethod
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void testAddInvalidAppWebJson() throws Exception {
String appJson = getInvalidApp().toJsonString();
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/app/add.json")
.contentType(MediaType.APPLICATION_JSON).content(appJson);
ResultActions resultAction = this.mockMvc.perform(requestBuilder);
resultAction.andExpect(status().isForbidden());
MvcResult mvcResult = resultAction.andReturn();
Exception resolvedException = mvcResult.getResolvedException();
assertTrue(resolvedException instanceof ValidationException);
ValidationException validationException = (ValidationException) resolvedException;
assertEquals(validationException.getErrors().size(), 3);
}
#Test
public void testAddAppWebJson() throws Exception {
Application app = getMockApp();
String appJson = app.toJsonString();
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/app/add.json")
.contentType(MediaType.APPLICATION_JSON).content(appJson);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
#Test
public void testEditAppWithWrongIdWebJson() throws Exception {
String appJson = getMockAppWithId().toJsonString();
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/app/2/edit.json")
.contentType(MediaType.APPLICATION_JSON).content(appJson);
this.mockMvc
.perform(requestBuilder)
.andExpect(status().isForbidden())
.andExpect(
content()
.string("{\"errors\":[{\"errorType\":\"-100\",\"field\":\"id\",\"constraint\":null}],\"objects\":null}"));
}
#Test(dependsOnMethods={"testAddApp", "testAddAppWebJson"})
public void testEditAppWebJson() throws Exception {
Application app = getMockAppWithId();
setAppWebsiteToDifferentOne(app);
String appJson = app.toJsonString();
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/app/1/edit.json")
.contentType(MediaType.APPLICATION_JSON).content(appJson);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
#Test
public void testEditInvalidAppWebJson() throws Exception {
Application app = getMockAppWithId();
app.setWebsite("Saba7o 3asal");
String appJson = app.toJsonString();
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/app/1/edit.json")
.contentType(MediaType.APPLICATION_JSON).content(appJson);
this.mockMvc
.perform(requestBuilder)
.andExpect(status().isForbidden())
.andExpect(
content()
.string("{\"errors\":[{\"errorType\":\"-114\",\"field\":\"website\",\"constraint\":null}],\"objects\":null}"));
}
#Test(dependsOnMethods="testEditAppWebJson")
public void testGetAppDetails() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/app/1/get.json");
Application app = getMockAppWithId();
setAppWebsiteToDifferentOne(app);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk())
.andExpect(content().string(app.toJsonString()));
}
}
My service method is:
#Override
#Transactional(readOnly=false)
public void saveApp(Application app) {
baseDao.saveObject(app);
}
All test methods pass, except for the last one, since it's expecting the website of the app to be the one which was edited. Where have I gone wrong?
Need to know which hibernate method is called in saveApp() also make sure your service is annotated with #Transactional.

Resources