事务注释在 Spring Boot 中不起作用

Posted

技术标签:

【中文标题】事务注释在 Spring Boot 中不起作用【英文标题】:Transactional annotation not working in Spring Boot 【发布时间】:2015-09-01 10:09:12 【问题描述】:

@Transactional 在 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
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);
    

当我在 DAO 系统中调用 create 方法时抛出 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)

我的 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'

并链接到 git 存储库:https://github.com/Yurii-Buhryn/interviewer

【问题讨论】:

您在哪里/如何调用@Transactional方法?您可以将那段代码添加到问题中吗? @mhlz 添加以及 git 存储库的路径:github.com/Yurii-Buhryn/interviewer 你已经在你的 gradle 文件中定义了 spring-data-jpa,但是你手动实现了你的 dao 层,你为什么要这样做?您不需要定义 sessionFactory、entityFactory 和所有这些,只需在您的 ICandidateDao 中扩展 JpaRepository<CandidateModel, Long>,用 @Repository 注释它,删除您的 CandidateDao 实现并完成它 您正在使用 JPA,因此应该使用 JpaTransactionManager 而不是 HibernateTransactionManager。如果你可以简单地用 JPA 来做,为什么还要使用普通的休眠?你只是让事情变得更复杂。此外,您正在使用 Spring Boot,然后使用 Spring Boot 并让它为您自动配置东西,而不是您解决这个问题并手动配置所有内容。 【参考方案1】:

首先您使用 Spring Boot,然后使用 Spring Boot 并让它为您自动配置。它将配置数据源、实体管理器、事务管理器等。

接下来您使用了错误的事务管理器,您使用的是 JPA,因此您应该使用 JpaTransactionManager 而不是 HibernateTransactionManager,因为它已经为您配置好了,您可以简单地删除它的 bean 定义。

第二次你的hibernate.current_session_context_class 搞砸了正确的 tx 集成,删除它。

使用自动配置

当您考虑到所有这些时,您基本上可以将您的 Application 类减少到以下内容。

@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);
    

接下来在src/main/resources 中添加一个application.properties,其中包含以下内容。

# 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

这将正确配置数据源和 JPA。

使用 JPA 代替普通的 Hibernate

另一个技巧,而不是使用普通的休眠 API,只需使用 JPA,这样您也可以删除 SessionFactory 的 bean。只需将您的 dao 更改为使用 EntityManager 而不是 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) 

    

添加 Spring Data JPA

如果您真的想从中受益,请将 Spring Data JPA 添加到组合中并完全删除您的 DAO 并只留下一个接口。您现在拥有的内容将被移至服务类(恕我直言)。

整个仓库

public interface ICandidateDao extends JpaRepository<CandidateModel, Long> 

修改后的服务(现在也是事务性的,所有业务逻辑都在服务中)。

@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);
    

现在您还可以删除 SessionFactory 的 bean 定义,将您的 Application 减少到只是一个 main 方法。

@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);
    

所以我强烈建议使用框架而不是试图绕过框架。因为这将真正简化您的开发人员的生活。

依赖关系

作为最后一点,我建议从您的依赖项中删除 spring-data-jpa 依赖项并改用 starter。 AspectJ 也是如此,为此使用 AOP 启动器。此外,不再支持 jackson 1,因此添加该依赖项不会添加任何内容

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")

【讨论】:

一般来说,您不希望在 DAO/Repository 层上使用 @Transactional,而是让它从 Service 层进行管理。 我遵循了你的所有指示,但对我来说还不起作用):我认为 @Transactional 被忽略了,因为我使用了 throw new Exception();模拟错误。 Exception 不仅仅回滚RuntimeExceptions(这在参考指南和所述类的javadocs中都有解释)。 我也尝试过使用,抛出新的 RuntimeException();明确地说,我使用了 rollbackFor = RuntimeException.class, Exception.class (先一个然后两个)并且不起作用。 如前所述,您还没有像问题中那样做。您自己正在捕获异常,因此正在破坏事务管理。它看不到错误,因此将提交而不是回滚。

以上是关于事务注释在 Spring Boot 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

注释 CrossOrigin 在 Spring Boot 中不起作用

@Transactional 注释 rollbackFor 值在 Spring Boot 中不起作用

Spring ComponentScan excludeFilters 注释在 Spring Boot Test 上下文中不起作用

JUnit 在带有 @Autowired 注释的 Spring Boot 中不起作用

带有 nativeQuery 的 Spring Boot Query 注释在 Postgresql 中不起作用

@WithMockUser 在集成测试中不起作用 - Spring boot