I am getting the error while using hibernate in spring boot application No qualifying bean of type TransactionManager' available
I am using the following config class:
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#org.springframework.context.annotation.Configuration
#EnableTransactionManagement
public class Config {
#Bean
public SessionFactory sessionFactory() {
Configuration configuration = new Configuration();
configuration.configure();
configuration.addAnnotatedClass(Ct.class);
configuration.addAnnotatedClass(St.class);
SessionFactory sessionFactory = configuration.buildSessionFactory();
return sessionFactory;
}
}
#RestController
public class RestAPIController {
#Autowired
private SessionFactory sessionFactory;
#PutMapping("/addS")
#Transactional
public void addSt(#RequestParam("cc") String cc,#RequestParam("st") String st) {
CC cc1= new CC();
CC.setCode(cc);
State state = new State(cc,st);
sessionFactory.getCurrentSession().save(state);
}
}
}
The main reason I added the #Transactional in the addSt method is due to error: The transaction was still an active when an exception occurred during Database.
So I turned to use spring boot for managing transactions. I am not sure what to do here.
--------------------UPDATED CODE--------------------
#Repository
public interface StateRepository extends CrudRepository<State, String> {}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;
#Service
#Transactional
public class StateService {
#Autowired
private StateRepository stateRepository;
public void save(State state) {
stateRepository.save(state);
}
public List<State> findAll() {
List<State> states = new ArrayList<>();
stateRepository.findAll().forEach(states::add);
return states;
}
}
For starters use proper layers and write a service and use JPA instead of plain Hibernate. If you want a Session you can always use EntityManager.unwrap to obtain the underlying Session.
#Service
#Transactional
public StateService {
#PersistenceContext
private EntityManager em;
public void save(State state) {
em.persist(state);
}
Use this service in your controller instead of the SessionFactory.
#RestController
public class RestAPIController {
private final StateService stateService;
RestAPIController(StateService stateService) {
this.stateService=stateService;
}
#PutMapping("/addS")
public void addSt(#RequestParam("cc") String cc, #RequestParam("st") String st) {
CC cc1= new CC();
CC.setCode(cc);
State state = new State(cc,st);
stateService.save(state);
}
}
Now ditch your Config class and restart the application.
NOTE
When using Spring Data JPA it is even easier, define a repository extending CrudRepository and inject that into the service instead of an EntityManager. (I'm assuming that Long is the type of primary key you defined).
public interface StateRepository extends CrudRepository<State, Long> {}
#Service
#Transactional
public StateService {
private final StateRepository states;
public StateService(StateRepository states) {
this.states=states;
}
public void save(State state) {
states.save(state);
}
}
In my code the service method savePerson is annotated with #Transactional. Inside this method a Person entity is persisted and inmediately a Runtime exception is intentionally throwed. I suposse the transaction should not be commited but the Person entity is persisted in database....rollback is not working and I dont know why.
This is my Hibernate Configuration:
package newp;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableTransactionManagement
public class HibernateConf {
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[]{"newp"});
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/poas");
dataSource.setUsername("admin");
dataSource.setPassword("admin");
return dataSource;
}
#Bean
public PlatformTransactionManager hibernateTransactionManager() {
HibernateTransactionManager transactionManager= new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
private final Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");
return hibernateProperties;
}
}
This is my service:
package newp.services;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import newp.dao.PersonDao;
import newp.model.Person;
#Service
public class PersonService{
#Autowired
PersonDao dao;
#Transactional
public void savePerson(Person p) {
dao.savePerson(p);
throw new RuntimeException();
}
}
The DAO
package newp.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import newp.entities.PersonEntity;
import newp.model.Person;
#Repository
public class PersonDao {
#Autowired
SessionFactory sessionFactory;
public void savePerson(Person person) {
Session s = sessionFactory.getCurrentSession();
PersonEntity p=new PersonEntity();
p.setAge(person.getAge());
p.setName(person.getName());
p.setSurname(person.getSurname());
s.saveOrUpdate(p);
}
}
You are probably using tables with the MyISAM storage engine which does not support transactions: https://stackoverflow.com/a/8036049/412446
Through #Autowired i am not able to access the #Component/#Service/#Respository/#Controller class objects in other java files which has #Component annotation (Step 1: Approach) with the Step 1 approach getting Null pointer Exception, but same i could achieve using (Step 2: Approach).
Can anyone please tell me why i am not able to achieve using Step 1 approach:
FYI- I've searched in my entire project i have not used/called/initialized the #Component classes using new method for the autowired class still i getting the issue as "Null Pointer Exception"
Step 1: Using #Autowired Annotation
#Component
public class Processor {
#Autowired
PropertyConfigurator propconfigrator; --> Getting here as null pointer Exception
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
Step 2: Using ApplicationContext Interface with/without #AutoWired annotation . I am able to get the property value from PropertyConfigurator java file
#Component
public class Processor {
#Autowired
PropertyConfigurator propconfigrator = ApplicationContextHolder.getContext().getBean(PropertyConfigurator.class);
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
ApplicationContextHolder.java
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
PropertyConfigurator.java file
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
#Service
#Configurable
public class PropertyConfigurator {
private final Properties properties;
public Properties getProperties () {
return properties;
}
public PropertyConfigurator(){
properties = new Properties();
try {
properties.load(getClass().getClassLoader().getResourceAsStream("dbconfig.properties"));
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
}
}
}
Why did you use #Configurable annotation? In the code you postet, it doesn't make sense. #Configurable is only needed in cases when instances of this class are not createt by spring.
I have changed into Constructor Injection Autowiring as below with the step 1 approach of above (Not using Step 2. It resolved my issue finally.
Not sure why Spring is not able to inject the bean without using the Constructor Autowiring.
Step 1: Using #Autowired Annotation with Constructor
#Component
public class Processor {
#Autowired
public Processor (PropertyConfigurator propconfigrator) {
this.propconfigrator = propconfigrator;
}
public void getDetails(){
System.out.println ("Application URL +propconfigrator.getProperties().getProperty("appURL"));
}
}
I found in Web Filter, the autowired bean cannot be initialized, like code below
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
public class ClientIpFilter implements Filter {
#Autowired
private Environment environment;
}
And register Bean in Configuration
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class CustomWebSecurityConfigurerAdapter {
#Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
ClientIpFilter securityFilter = new ClientIpFilter();
registrationBean.setFilter(securityFilter);
registrationBean.setOrder(1);
return registrationBean;
}
}
Is this because Filter is inited before other bean? If yes, how can I use spring beans in web filter?
You missed #Component annotation for ClientIpFilter class.
The Spring MVC application uses SpringDataJPA and hibernate. The production mode leverages MySQL but for the unit tests I have set up H2DB to run, both with separate java configuration. The application also utilizes Spring Security but its configuration I omitted below.
The appliaction starts up with the unit tests but at the first test (testOIC() see in the below code) I got the "TransactionRequiredException: no transaction is in progress".
testException = org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress, mergedContextConfiguration = [WebMergedContextConfiguration#1c581a0 testClass = OpenPositionsServiceTest, locations = '{}', classes = '{class our.dcollect.configuration.AppConfiguration, class our.dcollect.configuration.HibernateConfigurationTest}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.test.context.web.WebDelegatingSmartContextLoader', parent = [null]]].
The unit test I run with Propagation.REQUIRES_NEW, so it should have a transaction context. I am afraid I messed up something in the appliaction context. The posts I found in connection with this topic did not help a lot.
The ApplicationContext contains the following beans:
Spring Bean Definition Names in ApplicationContext (applicationContext.getBeanDefinitionNames()):
===================================================
Bean: appConfiguration
Bean: authenticationManagerBuilder
Bean: autowiredWebSecurityConfigurersIgnoreParents
Bean: beanNameHandlerMapping
Bean: dataSource
Bean: dataSourceTest
Bean: defaultServletHandlerMapping
Bean: delegatingApplicationListener
Bean: dtoConverter
Bean: emBeanDefinitionRegistrarPostProcessor
Bean: enableGlobalAuthenticationAutowiredConfigurer
Bean: entityManagerFactory
Bean: entityManagerFactoryTest
Bean: fileUploadController
Bean: fileUploadService
Bean: getInternalResourceViewResolverJsp
Bean: handlerExceptionResolver
Bean: hibernateConfiguration
Bean: hibernateConfigurationTest
Bean: hibernateProperties
Bean: httpRequestHandlerAdapter
Bean: initializeAuthenticationProviderBeanManagerConfigurer
Bean: initializeUserDetailsBeanManagerConfigurer
Bean: jpaContext
Bean: jpaMappingContext
Bean: jpaVendorAdapter
Bean: jpaVendorAdapterTest
Bean: multipartResolver
Bean: mvcContentNegotiationManager
Bean: mvcConversionService
Bean: mvcPathMatcher
Bean: mvcResourceUrlProvider
Bean: mvcUriComponentsContributor
Bean: mvcUrlPathHelper
Bean: mvcValidator
Bean: mvcViewResolver
Bean: objectPostProcessor
Bean: openIn....Repository
Bean: openPositionsService
Bean: org.springframework.aop.config.internalAutoProxyCreator
Bean: org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
Bean: org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
Bean: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
Bean: org.springframework.context.annotation.internalCommonAnnotationProcessor
Bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
Bean: org.springframework.context.annotation.internalPersistenceAnnotationProcessor
Bean: org.springframework.context.annotation.internalRequiredAnnotationProcessor
Bean: org.springframework.context.event.internalEventListenerFactory
Bean: org.springframework.context.event.internalEventListenerProcessor
Bean: org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension#0
Bean: org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport_Predictor
Bean: org.springframework.orm.jpa.SharedEntityManagerCreator#0
Bean: org.springframework.orm.jpa.SharedEntityManagerCreator#1
Bean: org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
Bean: org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration
Bean: org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration
Bean: org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
Bean: org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
Bean: org.springframework.transaction.config.internalTransactionAdvisor
Bean: org.springframework.transaction.config.internalTransactionalEventListenerFactory
Bean: org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
Bean: privilegeEvaluator
Bean: push....Repository
Bean: requestDataValueProcessor
Bean: requestMappingHandlerAdapter
Bean: requestMappingHandlerMapping
Bean: resourceHandlerMapping
Bean: securityConfiguration
Bean: sessionFactory
Bean: sessionFactoryTest
Bean: simpleControllerHandlerAdapter
Bean: springSecurityFilterChain
Bean: transactionAttributeSource
Bean: transactionInterceptor
Bean: transactionManager
Bean: transactionManagerTest
Bean: viewControllerHandlerMapping
Bean: webSecurityExpressionHandler
===================================================
Could you help? The relevant code parts I posted below.
MVC configuration:
package our.dcollect.configuration;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "our.dcollect")
public class AppConfiguration {
private static final Logger log = Logger.getLogger(AppConfiguration.class);
#Bean
public InternalResourceViewResolver getInternalResourceViewResolverJsp(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
viewResolver.setOrder(0);
log.info("#### Internal view resolver 0 called...");
return viewResolver;
}
#Bean
public StandardServletMultipartResolver multipartResolver(){
log.info("#### Multipart resolver called...");
return new StandardServletMultipartResolver();
}
}
Hibernate Configuration:
package our.dcollect.configuration;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#ComponentScan(basePackages = "our.dcollect")
#EnableJpaRepositories(basePackages = {"our.dcollect.repository"},
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager")
#PropertySource(value = { "classpath:application.properties" })
#EnableTransactionManagement
//#PropertySource({ "/resources/hibernate.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean(name="sessionFactory")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "our.dcollect" });
sessionFactory.setHibernateProperties(this.hibernateProperties());
return sessionFactory;
}
#Bean(name="dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
#Bean(name="hibernateProperties")
public Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
properties.put("hibernate.hbm2ddl.auto", "create-drop");
return properties;
}
#Bean(name="transactionManager")
#Autowired
public HibernateTransactionManager transactionManager(#Qualifier("sessionFactory") SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
#Bean(name="jpaVendorAdapter")
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(this.dataSource());
lef.setJpaProperties(this.hibernateProperties());
lef.setJpaVendorAdapter(this.jpaVendorAdapter());
lef.setPackagesToScan(new String[] { "our.dcollect.model"});
return lef;
}
}
Hibernate Configuration for the Unit Tests:
package our.dcollect.configuration;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/***************************************************************************************
* The same as the real HibernateConfiguration but this works with H2 DB instead of MySQL.
* In addition, the properties are not read from a property file.
****************************************************************************************/
#Configuration
#ComponentScan(basePackages = "our.dcollect")
#EnableJpaRepositories(basePackages = {"our.dcollect.repository"},
entityManagerFactoryRef = "entityManagerFactoryTest",
transactionManagerRef = "transactionManagerTest")
#PropertySource(value = { "classpath:application.properties" })
#EnableTransactionManagement
public class HibernateConfigurationTest {
#Bean(name = "sessionFactoryTest")
public LocalSessionFactoryBean sessionFactoryTest() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSourceTest());
sessionFactory.setPackagesToScan(new String[] { "our.dcollect" });
sessionFactory.setHibernateProperties(hibernatePropertiesTest());
return sessionFactory;
}
#Bean(name = "dataSourceTest")
public DataSource dataSourceTest() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSource.setUsername("...");
dataSource.setPassword("...");
return dataSource;
}
private Properties hibernatePropertiesTest() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
return properties;
}
#Bean(name = "transactionManagerTest")
#Autowired
public HibernateTransactionManager transactionManagerTest(#Qualifier("sessionFactoryTest") SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
#Bean(name = "jpaVendorAdapterTest")
public JpaVendorAdapter jpaVendorAdapterTest() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
#Bean(name = "entityManagerFactoryTest")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryTest() {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(this.dataSourceTest());
lef.setJpaProperties(this.hibernatePropertiesTest());
lef.setJpaVendorAdapter(this.jpaVendorAdapterTest());
lef.setPackagesToScan(new String[] { "our.dcollect.model"});
return lef;
}
}
The Unit Test class abbriviated for legibility:
package our.dcollect.service;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import our.dcollect.configuration.AppConfiguration;
import our.dcollect.configuration.HibernateConfigurationTest;
import our.dcollect.model.OpenIC;
import our.dcollect.repository.OpenICRepository;
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfiguration.class, HibernateConfigurationTest.class})
public class OpenPositionsServiceTest {
#Autowired
private OpenICRepository OpenICDAO;
#PersistenceContext(unitName = "entityManagerFactoryTest")
private EntityManager entityManager;
#Test
#Transactional(value="transactionManagerTest", propagation = Propagation.REQUIRES_NEW)
public void testOIC() {
System.out.println("getOpenPositionsNotRegisteredInPushBanch");
OpenIC oic = new OpenIC();
oic.setBaId("111");
oic.setBezeichnung("abc");
// clear the persistence context so we don't return the previously cached location object
// this is a test only thing and normally doesn't need to be done in prod code
entityManager.clear();
OpenICDAO.saveAndFlush(oic);
List<OpenIC> list = OpenICDAO.findAll();
assertEquals(list.size(), 1);
OpenIC oicReadBack = list.get(0);
OpenICDAO.delete(oicReadBack.getOpenICId());
}
[...]
}
Steps Towards the Solution:
The posts from Funtik helped a lot. The following setps I carried out:
#DependsOn("transactionManagerTest") to tell Spring that the
EntityManagerFactory needs to be loaded after the TransactionManager
but it did not solve the problem.
Introducing #Profile("test") for HibernateConfigurationTest and #Profile("!test") for HibernateConfiguration. On the test
class I put #ActiveProfiles("test"). This separated the beans;
only one flavour was loaded during the test: the ones in
HibernateConfigurationTest. This however did not solve the
problem either.
Then I completely removed one profile in a separate branch to see whether the two configuration for the DB cause the issue. I
experienced the same problem with one DB configuration as before.
I added #TestExecutionListeners({TransactionalTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class}) to the unit test
class and #Rollback(false) to the test method to see whether
anything is saved in the MySQL database. I also changed
"hibernate.hbm2ddl.auto" to "create", to preserve the DB after the
test. It changed the situation as the message appeared in the log:
"[...] Began transaction (1) for test context
[DefaultTestContext#a15b73 [...]". So one transaction context has
definitely been created and has had 1 transaction. The problem was
however that the entity was not persisted in the database if I call
OpenICDAO.save(oic) in the test. In addition, calling
OpenICDAO.saveAndFlush(oic) causes the nearly the exception below:
"[...] 4671 [main] WARN org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener#45c9b3] to process 'after' execution for test: method [public void our.dcollect.service.OpenPositionsServiceTest.testGetOpenPositionsNotRegisteredInPushBanch()], instance [our.dcollect.service.OpenPositionsServiceTest#dc7b7d], exception [org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress]
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only [...]"
Tests in error:
testOIC(our.dcollect.service.OpenPositionsServiceTest): no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
testOIC(our.dcollect.service.OpenPositionsServiceTest): Transaction rolled back because it has been marked as rollback-only
Tests run: 3, Failures: 0, Errors: 2, Skipped: 0
However, I only have 2 methods not 3 from which testOIC is reported twice.
Solution:
The transactionManager must be a PlatformTransactionManager hosting a JpaTransactionManager. This solved the problem. Thank you for all the comments. I found the #Profile annotation very useful and the idea of listing the beans in the application context. After having the correct transactionManager, the annotation DependsOn() caused a circular reference, so I removed it; #TestExecutionListeners annotation is also not necessary in this case. I took a simple demo application that I changed to my configuration and tried to reproduce the original fault. After being able to reproduce it, I checked the differences between the good and bad state, it helped to find the difference. (The same also needs to be done for transactionManagerTest that expects a SessionFactoryTest object.)
// #Bean(name="transactionManager")
// #Autowired
// public HibernateTransactionManager transactionManager(#Qualifier("sessionFactory") SessionFactory s) {
// HibernateTransactionManager txManager = new HibernateTransactionManager();
// txManager.setSessionFactory(s);
// return txManager;
// }
#Bean(name="transactionManager")
#Autowired
public PlatformTransactionManager transactionManager(#Qualifier("sessionFactory") SessionFactory s) {
return new JpaTransactionManager( entityManagerFactory().getObject() );
}
I had the same problem. From what I experienced it seems that the entityManager bean is loaded before transactionManager bean, so the spring does not sense the presence of any transactions at all.
#DependsOn annotation fixed this issue for me. Try setting the explicit dependency on TransactionManager like this:
#DependsOn("transactionManagerTest")
#Bean(name = "entityManagerFactoryTest")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryTest() {
I added the Solution to the original post at the bottom.
I have run into this issue while trying to persist an entity during test.
And followed your solution in trying to resolve:
"The transactionManager must be a PlatformTransactionManager hosting a JpaTransactionManager"
Changed HibernateTransactionManager to JpaTransactionManager
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory s) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(s);
return txManager;
}
Was very amazed for your solution to work. If anyone could point me on documentation where it's described it would be great.
Thank you.