@Transactional not working in Spring Boot.
Application.java :
@EnableTransactionManagement(proxyTargetClass=true)
@SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
public class Application {
@Autowired
private EntityManagerFactory entityManagerFactory;
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
@Bean
public SessionFactory getSessionFactory() {
if (entityManagerFactory.unwrap(SessionFactory.class) == null) {
throw new NullPointerException("factory is not a hibernate factory");
}
return entityManagerFactory.unwrap(SessionFactory.class);
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.buhryn.interviewer.models" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/interviewer");
dataSource.setUsername("postgres");
dataSource.setPassword("postgres");
return dataSource;
}
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.show_sql", "false");
properties.setProperty("hibernate.format_sql", "false");
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.current_session_context_class", "org.hibernate.context.internal.ThreadLocalSessionContext");
return properties;
}
}
CandidateDao.java
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class CandidateDao implements ICandidateDao{
@Autowired
SessionFactory sessionFactory;
protected Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
@Override
@Transactional
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
getCurrentSession().save(candidateModel);
return candidateModel;
}
@Override
public CandidateModel show(Long id) {
return new CandidateModel(
"new",
"new",
"new",
"new");
}
@Override
public CandidateModel update(Long id, CandidateDto candidate) {
return new CandidateModel(
"updated",
candidate.getLastName(),
candidate.getEmail(),
candidate.getPhone());
}
@Override
public void delete(Long id) {
}
}
Service Class
@Service
public class CandidateService implements ICandidateService{
@Autowired
ICandidateDao candidateDao;
@Override
public CandidateModel create(CandidateDto candidate) {
return candidateDao.create(candidate);
}
@Override
public CandidateModel show(Long id) {
return candidateDao.show(id);
}
@Override
public CandidateModel update(Long id, CandidateDto candidate) {
return candidateDao.update(id, candidate);
}
@Override
public void delete(Long id) {
candidateDao.delete(id);
}
}
Controller.class
@RestController
@RequestMapping(value = "/api/candidates")
public class CandidateController {
@Autowired
ICandidateService candidateService;
@RequestMapping(value="/{id}", method = RequestMethod.GET)
public CandidateModel show(@PathVariable("id") Long id) {
return candidateService.show(id);
}
@RequestMapping(method = RequestMethod.POST)
public CandidateModel create(@Valid @RequestBody CandidateDto candidate, BindingResult result) {
RequestValidator.validate(result);
return candidateService.create(candidate);
}
@RequestMapping(value="/{id}", method = RequestMethod.PUT)
public CandidateModel update(@PathVariable("id") Long id, @Valid @RequestBody CandidateDto candidate, BindingResult result) {
RequestValidator.validate(result);
return candidateService.update(id, candidate);
}
@RequestMapping(value="/{id}", method = RequestMethod.DELETE)
public void delete(@PathVariable("id") Long id) {
candidateService.delete(id);
}
}
When I call create method in DAO system throw exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: save is not valid without active transaction; nested exception is org.hibernate.HibernateException: save is not valid without active transaction
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:291)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:102)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
My Gradle file :
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'interviewer'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.codehaus.jackson:jackson-mapper-asl:1.9.13")
compile("com.google.code.gson:gson:2.3.1")
compile("org.springframework.data:spring-data-jpa:1.8.0.RELEASE")
compile("org.hibernate:hibernate-entitymanager:4.3.10.Final")
compile("postgresql:postgresql:9.1-901-1.jdbc4")
compile("org.aspectj:aspectjweaver:1.8.6")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
And link to git repository : https://github.com/Yurii-Buhryn/interviewer
However, if we're using a Spring Boot project and have a spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.
You can check if transaction is active using TransactionSynchronizationManager. isActualTransactionActive() . But you should call it before a service method executing. TransactionStatus status = TransactionAspectSupport.
The transactional annotation itself defines the scope of a single database transaction. The database transaction happens inside the scope of apersistence context. The persistence context is in JPA the EntityManager , implemented internally using an Hibernate Session (when using Hibernate as the persistence provider).
However, if we put the annotation on a private or protected method, Spring will ignore it without an error. Usually it's not recommended to set @Transactional on the interface; however, it is acceptable for cases like @Repository with Spring Data.
Implementation To start using @Transactional annotation in a Spring based application, we need to first enable annotations in our Spring application by adding the needed configuration into spring context file – Next is to define the transaction manager bean, with the same name as specified in the above transaction-manager attribute value.
The annotation @EnableTransactionManagement tells Spring that classes with the @Transactional annotation should be wrapped with the Transactional Aspect. With this the @Transactional is now ready to be used. The Spring declarative transaction management mechanism is very powerful, but it can be misused or wrongly configured easily.
If Spring detects the @Transactional annotation on a bean, it creates a dynamic proxy of that bean. 2. The proxy has access to a transaction manager (which is the PlatformTransactionManager in Spring Boot) and will ask it to open and close transactions/connections.
Why the @Transactional annotation is being ignored? @Transactional will only work over public methods, so this annotation will be always ignored if it is above a private, protected or package-protected method. “When using proxies, you should apply the @Transactional annotation only to methods with public visibility.
First you are using Spring Boot then use Spring Boot and let that auto configure things for you. It will configure a datasource, entitymanagerfactory, transaction manager etc.
Next you are using the wrong transaction manager, you are using JPA so you should use the JpaTransactionManager
instead of the HibernateTransactionManager
as that is already configured for you you can simply remove the bean definition for that.
Second your hibernate.current_session_context_class
is messing up proper tx integration remove it.
When you take all this into account you can basically reduce your Application
class to the following.
@SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
@EntityScan("com.buhryn.interviewer.models")
public class Application {
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
@Bean
public SessionFactory sessionFactory(EntityManagerFactory emf) {
if (emf.unwrap(SessionFactory.class) == null) {
throw new NullPointerException("factory is not a hibernate factory");
}
return emf.unwrap(SessionFactory.class);
}
}
Next add an application.properties
in src/main/resources
containing the following.
# DataSource configuration
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/interviewer
# General JPA properties
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=false
# Hibernate Specific properties
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.hibernate.ddl-auto=create
This will configure the datasource and JPA correctly.
Another tip instead of using the plain hibernate API simply use JPA that way you could remove the bean for the SessionFactory
as well. Simply change your dao to use an EntityManager
instead of a SessionFactory
.
@Repository
public class CandidateDao implements ICandidateDao{
@PersistenceContext
private EntityManager em;
@Override
@Transactional
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
return em.persist(candidateModel);
}
@Override
public CandidateModel show(Long id) {
return new CandidateModel(
"new",
"new",
"new",
"new");
}
@Override
public CandidateModel update(Long id, CandidateDto candidate) {
return new CandidateModel(
"updated",
candidate.getLastName(),
candidate.getEmail(),
candidate.getPhone());
}
@Override
public void delete(Long id) {
}
}
And if you really want to benefit add Spring Data JPA into the mix and remove your DAO completely and leave only an interface. What you have now would be moved to a service class (where it belongs IMHO).
The whole repository
public interface ICandidateDao extends JpaRepository<CandidateModel, Long> {}
The modified service (which is now also transactional as it should and all business logic is in the service).
@Service
@Transactional
public class CandidateService implements ICandidateService{
@Autowired
ICandidateDao candidateDao;
@Override
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
return candidateDao.save(candidate);
}
@Override
public CandidateModel show(Long id) {
return candidateDao.findOne(id);
}
@Override
public CandidateModel update(Long id, CandidateDto candidate) {
CandidateModel cm = candidateDao.findOne(id);
// Update values.
return candidateDao.save(cm);
}
@Override
public void delete(Long id) {
candidateDao.delete(id);
}
}
Now you can also remove the bean definition for the SessionFactory
reducing your Application
to just a main
method.
@SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
@EntityScan("com.buhryn.interviewer.models")
public class Application {
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
So I would strongly suggest to work with the framework instead of trying to work around the framework. As that will really simplify your developer live.
As a final note I would suggest removing the spring-data-jpa
dependency from your dependencies and use the starter instead. The same goes for AspectJ use the AOP starter for that. Also jackson 1 isn't supported anymore so adding that dependency doesn't add anything
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-aop")
compile("com.google.code.gson:gson:2.3.1")
compile("org.hibernate:hibernate-entitymanager:4.3.10.Final")
compile("postgresql:postgresql:9.1-901-1.jdbc4")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With