@Transactional ne fonctionne pas dans 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) {
}
}
Classe de service
@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);
}
}
Lorsque j'appelle la méthode create dans le système DAO 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)
Mon fichier Gradle :
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'
}
Et un lien vers le référentiel git: https://github.com/Yurii-Buhryn/interviewer
Vous utilisez d'abord Spring Boot, puis utilisez Spring Boot et laissez-le configurer automatiquement les choses pour vous. Il configurera une source de données, une entité entitymanagerfactory, un gestionnaire de transactions, etc.
Ensuite, vous utilisez le mauvais gestionnaire de transactions, vous utilisez JPA, vous devez donc utiliser le JpaTransactionManager
au lieu du HibernateTransactionManager
car il est déjà configuré pour vous, vous pouvez simplement supprimer la définition du bean pour cela.
Second votre hibernate.current_session_context_class
gâche une bonne intégration tx, supprimez-le.
Lorsque vous prenez tout cela en compte, vous pouvez essentiellement réduire votre classe Application
comme suit.
@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);
}
}
Ajoutez ensuite un application.properties
dans src/main/resources
contenant les éléments suivants.
# 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
Cela configurera correctement la source de données et JPA.
Une autre astuce au lieu d'utiliser l'API hibernate simple consiste simplement à utiliser JPA de cette façon, vous pouvez également supprimer le bean pour le SessionFactory
. Changez simplement votre dao pour utiliser un EntityManager
au lieu d'un 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) {
}
}
Et si vous voulez vraiment en bénéficier, ajoutez Spring Data JPA dans le mix et supprimez complètement votre DAO et ne laissez qu'une interface. Ce que vous avez maintenant serait déplacé vers une classe de service (à laquelle il appartient à mon humble avis).
L'ensemble du référentiel
public interface ICandidateDao extends JpaRepository<CandidateModel, Long> {}
Le service modifié (qui est maintenant aussi transactionnel comme il se doit et toute la logique métier est dans le 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);
}
}
Maintenant, vous pouvez également supprimer la définition de bean pour le SessionFactory
en réduisant votre Application
à une méthode main
uniquement.
@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);
}
}
Je suggère donc fortement de travailler avec le cadre au lieu d'essayer de contourner le cadre. Comme cela simplifiera vraiment votre développeur en direct.
Pour terminer, je suggère de supprimer le spring-data-jpa
dépendance de vos dépendances et utilisez plutôt le démarreur. La même chose vaut pour AspectJ utiliser le démarreur AOP pour cela. De plus, jackson 1 n'est plus pris en charge, donc l'ajout de cette dépendance n'ajoute rien
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")
}