EntityManager closed when executing queries on different threads - spring-boot

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?

Related

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

#Transactional does not rollback in springboot

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

Entities not persisting. Are RepositoryItemWriter & SimpleJpaWriter thread-safe?

I have encountered an odd issue with a RepositoryItemWriter, where it does not appear to be persisting entities correctly through my configured Spring Data JPA repository to the data source.
Step configuration
#Bean
public Step orderStep(StepBuilderFactory stepBuilderFactory, ItemReader<OrderEncounter> orderEncounterReader, ItemWriter<List<Order>> orderWriter,
ItemProcessor<OrderEncounter, List<Order>> orderProcessor, TaskExecutor taskExecutor) {
return stepBuilderFactory.get("orderStep")
.<OrderEncounter, List<Order>> chunk(10)
.reader(orderEncounterReader)
.processor(orderProcessor)
.writer(orderWriter)
.taskExecutor(taskExecutor)
.build();
}
Task Executor Bean (configured for 1 thread for testing purposes)
#Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(1);
taskExecutor.afterPropertiesSet();
return taskExecutor;
}
OrderRepository
#Repository
public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
}
orderWriter bean
#Bean
public ItemWriter<List<Order>> orderWriter(OrderRepository orderRepository) {
RepositoryListItemWriter<List<Order>> writer = new RepositoryListItemWriter<>();
writer.setRepository(orderRepository);
writer.setMethodName("save");
try {
writer.afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
return writer;
}
RepositoryListItemWriter (modified RepositoryItemWriter to support multiple items that are returned from a stored procedure result set)
public class RepositoryListItemWriter<T> implements ItemWriter<T>, InitializingBean {
protected static final Log logger = LogFactory.getLog(RepositoryListItemWriter.class);
private CrudRepository<?, ?> repository;
private String methodName;
public RepositoryListItemWriter() {
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public void setRepository(CrudRepository<?, ?> repository) {
this.repository = repository;
}
public void write(List<? extends T> items) throws Exception {
if(!CollectionUtils.isEmpty(items)) {
this.doWrite(items);
}
}
protected void doWrite(List<? extends T> items) throws Exception {
if(logger.isDebugEnabled()) {
logger.debug("Writing to the repository with " + items.size() + " items.");
}
MethodInvoker invoker = this.createMethodInvoker(this.repository, this.methodName);
Iterator i$ = items.iterator();
while(i$.hasNext()) {
Object object = i$.next();
invoker.setArguments(new Object[]{object});
this.doInvoke(invoker);
}
}
public void afterPropertiesSet() throws Exception {
Assert.state(this.repository != null, "A CrudRepository implementation is required");
}
private Object doInvoke(MethodInvoker invoker) throws Exception {
try {
invoker.prepare();
} catch (ClassNotFoundException var3) {
throw new DynamicMethodInvocationException(var3);
} catch (NoSuchMethodException var4) {
throw new DynamicMethodInvocationException(var4);
}
try {
return invoker.invoke();
} catch (InvocationTargetException var5) {
if(var5.getCause() instanceof Exception) {
throw (Exception)var5.getCause();
} else {
throw new InvocationTargetThrowableWrapper(var5.getCause());
}
} catch (IllegalAccessException var6) {
throw new DynamicMethodInvocationException(var6);
}
}
private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
MethodInvoker invoker = new MethodInvoker();
invoker.setTargetObject(targetObject);
invoker.setTargetMethod(targetMethod);
return invoker;
}
}
When orderStep is configured without a taskExecutor, then entities persist fine through the RepositoryItemWriter to the database; however, when it is configured with even a single-threaded taskExecutor, nothing is persisted to the database throughout the entire job execution - including after completion.
I've performed a considerable amount of research and RepositoryItemWriter appears to be thread-safe -- along with PagingAndSortingRepository & SimpleJpaWriter. Any suggestions?
Trace-level log output
No TaskExecutor in job:
2015-11-23 20:51:07.589 TRACE 31126 --- [nio-8080-exec-1] o.s.beans.CachedIntrospectionResults : Found bean property 'verified
ChangedByUsername' of type [java.lang.String]2015-11-23 20:51:07.589 TRACE 31126 --- [nio-8080-exec-1] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springfr
amework.orm.jpa.EntityManagerHolder#56ca0bbc] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#5ffffca4] bound to thread [http-nio-8080-exec-1]2015-11-23 20:51:07.590 TRACE 31126 --- [nio-8080-exec-1] o.h.e.i.AbstractSaveEventListener : Transient instance of: com.ii
massociates.distiller.domain.Order2015-11-23 20:51:07.590 TRACE 31126 --- [nio-8080-exec-1] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2015-11-23 20:51:07.590 TRACE 31126 --- [nio-8080-exec-1] o.h.e.i.AbstractSaveEventListener : Saving [com.iimassociates.distiller.domain.Order#<null>]
2015-11-23 20:51:07.591 TRACE 31126 --- [nio-8080-exec-1] org.hibernate.engine.spi.ActionQueue : Adding an EntityIdentityInsertAction for [com.iimassociates.distiller.domain.Order] object
2015-11-23 20:51:07.591 TRACE 31126 --- [nio-8080-exec-1] org.hibernate.engine.spi.ActionQueue : Executing inserts before finding non-nullable transient entities for early insert: [EntityIdentityInsertAction[com.iimassociates.distiller.domain.Order#<null>]
]
2015-11-23 20:51:07.591 TRACE 31126 --- [nio-8080-exec-1] org.hibernate.engine.spi.ActionQueue : Adding insert with no non-nullable, transient entities: [EntityIdentityInsertAction[com.iimassociates.distiller.domain.Order#<null>]]
2015-11-23 20:51:07.591 TRACE 31126 --- [nio-8080-exec-1] org.hibernate.engine.spi.ActionQueue : Executing insertions before resolved early-insert
2015-11-23 20:51:07.591 DEBUG 31126 --- [nio-8080-exec-1] org.hibernate.engine.spi.ActionQueue : Executing identity-insert immediately
2015-11-23 20:51:07.591 TRACE 31126 --- [nio-8080-exec-1] o.h.p.entity.AbstractEntityPersister : Inserting entity: com.iimassociates.distiller.domain.Order (native id)
2015-11-23 20:51:07.592 DEBUG 31126 --- [nio-8080-exec-1] org.hibernate.SQL : insert into orders (blah blah SQL)
TaskExecutor in job
2015-11-23 21:02:34.628 TRACE 31257 --- [ taskExecutor-1] o.s.beans.CachedIntrospectionResults : Found bean property 'verifiedChangedByUsername' of type [java.lang.String]
2015-11-23 21:02:34.628 TRACE 31257 --- [ taskExecutor-1] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#2b151d8a] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#2706e09c] bound to thread [taskExecutor-1]
2015-11-23 21:02:34.629 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.AbstractSaveEventListener : Transient instance of: com.iimassociates.distiller.domain.Order
2015-11-23 21:02:34.629 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2015-11-23 21:02:34.629 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.AbstractSaveEventListener : Saving [com.iimassociates.distiller.domain.Order#<null>]
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding an EntityIdentityInsertAction for [com.iimassociates.distiller.domain.Order] object
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding insert with no non-nullable, transient entities: [EntityIdentityInsertAction[com.iimassociates.distiller.domain.Order#<delayed:1>]]
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding resolved non-early insert action.
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] o.h.a.i.UnresolvedEntityInsertActions : No unresolved entity inserts that depended on [[com.iimassociates.distiller.domain.Order#<delayed:1>]]
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] o.h.a.i.UnresolvedEntityInsertActions : No entity insert actions have non-nullable, transient entity dependencies.
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#2b151d8a] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#2706e09c] bound to thread [taskExecutor-1]
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.AbstractSaveEventListener : Transient instance of: com.iimassociates.distiller.domain.Order
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2015-11-23 21:02:34.630 TRACE 31257 --- [ taskExecutor-1] o.h.e.i.AbstractSaveEventListener : Saving [com.iimassociates.distiller.domain.Order#<null>]
2015-11-23 21:02:34.631 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding an EntityIdentityInsertAction for [com.iimassociates.distiller.domain.Order] object
2015-11-23 21:02:34.631 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding insert with no non-nullable, transient entities: [EntityIdentityInsertAction[com.iimassociates.distiller.domain.Order#<delayed:2>]]
2015-11-23 21:02:34.631 TRACE 31257 --- [ taskExecutor-1] org.hibernate.engine.spi.ActionQueue : Adding resolved non-early insert action.
Looks like I fixed it by adding a PlatformTransactionManager. See changes below. Hope this helps someone, as this is one that I've been fighting for a couple of weeks now. What I don't understand is why Spring Boot is able to provide a JtaTransactionManager w/o Bitronix or Atomikos in my pom.xml.
#SuppressWarnings("SpringJavaAutowiringInspection")
#Bean
public Step orderStep(StepBuilderFactory stepBuilderFactory, ItemReader<OrderEncounter> orderEncounterReader, ItemWriter<List<Order>> orderWriter,
ItemProcessor<OrderEncounter, List<Order>> orderProcessor, TaskExecutor taskExecutor, PlatformTransactionManager platformTransactionManager) {
return stepBuilderFactory.get("orderStep")
.<OrderEncounter, List<Order>> chunk(10)
.reader(orderEncounterReader)
.processor(orderProcessor)
.writer(orderWriter)
.taskExecutor(taskExecutor)
.transactionManager(platformTransactionManager)
.build();
}
And the configuration class:
#Configuration
public class JTOpenDataSourceConfiguration {
#Bean(name="distillerDataSource")
#Primary
#ConfigurationProperties(prefix="spring.datasource.distiller")
public DataSource distillerDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="spring.datasource.target")
public DataSource targetDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public PlatformTransactionManager platformTransactionManager(#Qualifier("targetDataSource") DataSource targetDataSource) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(targetDataSource);
return transactionManager;
}

Neo4jConfiguration has circular references

I'm using spring-data-neo4j in a spring-boot application. I did the configuration as recommended in spring.io guides and many other places by inheriting Neo4jConfiguration class. This works when the database location is hardcoded in the provided examples. However when I want to use a placeholder for retrieving the database location from a property file it's not retrieved and I get null. Here's the code
#Configuration
#EnableNeo4jRepositories(basePackageClasses = {MyRepository.class})
public class Neo4jConfig extends Neo4jConfiguration {
#Value("${neo4j.location}")
private String neo4jDatabaseLocation;
#Bean
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory()
.newEmbeddedDatabase(neo4jDatabaseLocation);
}
...
This normally works in any other config class but not in this one because of the Neo4jConfiguration class has some several methods marked with #Autowired. This causes circular reference and it's not initialized properly. This can be seen in the logs:
2014-09-06 20:59:45.168 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [c.m.f.Neo4jConfig$$EnhancerBySpringCGLIB$$7165d752]: AutowiredFieldElement for private javax.validation.Validator org.springframework.data.neo4j.config.Neo4jConfiguration.validator
2014-09-06 20:59:45.169 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [c.m.f.Neo4jConfig$$EnhancerBySpringCGLIB$$7165d752]: AutowiredMethodElement for public void org.springframework.data.neo4j.config.Neo4jConfiguration.setConversionService(org.springframework.core.convert.ConversionService)
2014-09-06 20:59:45.169 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [c.m.f.Neo4jConfig$$EnhancerBySpringCGLIB$$7165d752]: AutowiredMethodElement for public void org.springframework.data.neo4j.config.Neo4jConfiguration.setGraphDatabaseService(org.neo4j.graphdb.GraphDatabaseService)
2014-09-06 20:59:45.169 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [c.m.f.Neo4jConfig$$EnhancerBySpringCGLIB$$7165d752]: AutowiredFieldElement for private java.lang.String c.m.f.Neo4jConfig.neo4jDatabaseLocation
2014-09-06 20:59:45.169 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'c.m.f.Neo4jConfig' to allow for resolving potential circular references
2014-09-06 20:59:45.171 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Processing injected method of bean 'c.m.f.Neo4jConfig': AutowiredFieldElement for private javax.validation.Validator org.springframework.data.neo4j.config.Neo4jConfiguration.validator
2014-09-06 20:59:45.182 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Processing injected method of bean 'c.m.f.Neo4jConfig': AutowiredMethodElement for public void org.springframework.data.neo4j.config.Neo4jConfiguration.setConversionService(org.springframework.core.convert.ConversionService)
2014-09-06 20:59:45.183 DEBUG 4665 --- [ main] o.s.b.f.annotation.InjectionMetadata : Processing injected method of bean 'c.m.f.Neo4jConfig': AutowiredMethodElement for public void org.springframework.data.neo4j.config.Neo4jConfiguration.setGraphDatabaseService(org.neo4j.graphdb.GraphDatabaseService)
2014-09-06 20:59:45.184 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'graphDatabaseService'
2014-09-06 20:59:45.184 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'graphDatabaseService'
2014-09-06 20:59:45.185 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
2014-09-06 20:59:45.185 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
2014-09-06 20:59:45.188 DEBUG 4665 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning eagerly cached instance of singleton bean 'c.m.f.Neo4jConfig' that is not fully initialized yet - a consequence of a circular reference
As you can see what I'm trying to achieve here is not to hardcode the database location. Is there any workaround for this circular reference problem? Or maybe any other way of configuring it? As this is a spring-boot application, I don't have any Xml configuration and if it's possible I want to keep it that way.
Have you tried passing in the neo4jDatabaseLocation as a parameter:
#Configuration
#EnableNeo4jRepositories(basePackageClasses = {MyRepository.class})
public class Neo4jConfig extends Neo4jConfiguration {
#Bean
public GraphDatabaseService graphDatabaseService(#Value("${neo4j.location}") String neo4jDatabaseLocation) {
return new GraphDatabaseFactory()
.newEmbeddedDatabase(neo4jDatabaseLocation);
}
...
}
Proposed suggestions did not work for the spring framework version I am using. As a workaround, I defined the GraphDatabaseService bean in another configuration file which does not extend Neo4jConfiguration. Note that this Neo4jConfig class is needed regardless.

Resources