本文介绍了Spring @Transactional TransactionRequiredException 或 RollbackException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经阅读了很多@Transactional 注释,我看到了 stackoverflow 的答案,但它对我没有帮助.所以我正在创建我的问题.

I have read a lot of @Transactional annotation, I saw stackoverflow answers but it does not help me. So I am creating my question.

我的情况是用唯一的电子邮件保存用户.在数据库中,我有用户的电子邮件地址为 [email protected],并且我使用相同的电子邮件地址保存用户.为了保存我必须使用 entityManager.merge() 因为这篇文章 thymeleaf binding collections 这不重要.

My case is to save user with unique email. In DB I have user with email [email protected], and I am saving user with the same email address. For saving I have to use entityManager.merge() because of this post thymeleaf binding collections it is not important.

第一个例子:

@Controller
public class EmployeeController extends AbstractCrudController {

    // rest of code (...)

    @RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
    public String processNewEmployee(Model model, @ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
        prepareUserForm(model);
        if (!result.hasErrors()) {
            try {
                saveEmployee(employee);
                model.addAttribute("success", true);
            } catch (Exception e) {
                model.addAttribute("error", true);
            }
        }

        return "crud/employee/create";
    }

    @Transactional
    public void saveEmployee(User employee) {
        entityManager.merge(employee);
    }

    private void prepareUserForm(Model model) {
        HashSet<Position> positions = new HashSet<Position>(positionRepository.findByEnabledTrueOrderByNameAsc());
        HashSet<Role> roles = new HashSet<Role>(roleRepository.findAll());
        User employee = new User();

        model.addAttribute("employee", employee);
        model.addAttribute("allPositions", positions);
        model.addAttribute("allRoles", roles);
    }
}

这段代码抛出TransactionRequiredException,不知道为什么?看起来@Transactional 注释不起作用,所以我将注释移动到 processNewEmployee()

This code is throwing TransactionRequiredException, I do not know why? It looks like @Transactional annotation did not work, so I moved annotation to processNewEmployee()

第二个例子:

@Controller
public class EmployeeController extends AbstractCrudController {

    // rest of code (...)

    @Transactional
    @RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
    public String processNewEmployee(Model model, @ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
        prepareUserForm(model);
        if (!result.hasErrors()) {

            try {
                entityManager.merge(employee);
                model.addAttribute("success", true);
            } catch (Exception e) {
                model.addAttribute("error", true);
            }
        }

        return "crud/employee/create";
    }

    private void prepareUserForm(Model model) { /*(.....)*/ }
}

这段代码抛出了 PersistenceException(因为 ConstraintViolationException),当然我得到了事务标记为 rollbackOnly"异常.

And this code is throwing PersistenceException (because of ConstraintViolationException) and of course I got "Transaction marked as rollbackOnly" exeption.

当我尝试保存不存在的电子邮件时,此代码工作正常,所以我认为 @Transactional 注释配置良好.

When I try to save email which not exists this code works fine, so I thing that @Transactional annotation is configured well.

如果这很重要,我将放置 TransationManagersConfig:

If this is important I am putting my TransationManagersConfig:

@Configuration
@EnableTransactionManagement
public class TransactionManagersConfig implements TransactionManagementConfigurer {

    @Autowired
    private EntityManagerFactory emf;

    @Autowired
    private DataSource dataSource;

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager tm =
                new JpaTransactionManager();
        tm.setEntityManagerFactory(emf);
        tm.setDataSource(dataSource);
        return tm;
    }

    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return transactionManager();
    }
}

你能解释一下我做错了什么并建议这个问题的可能解决方案吗?

Could you explain my what I am doing wrong and suggest possible solution of this problem?

解决方案:

感谢 R4J 我已经创建了 UserService 并且在我的 EmployeeController 我现在使用它而不是 entityManager.merge()它工作正常

Thanks to R4J I have created UserService and in my EmployeeController I am using it instead of entityManager.merge() now it works fine

@Service
public class UserService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void merge(User user) {
        entityManager.merge(user);
    }
}

和员工控制器:

@Controller
public class EmployeeController extends AbstractCrudController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
    public String processNewEmployee(Model model, @ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
         // (.....)
         userService.merge(employee);
         // (.....)
    }

}

推荐答案

您的事务不起作用,因为您从public String processNewEmployee"方法中直接调用了this.saveEmployee(...)".

Your transactions don't work because you call directly 'this.saveEmployee(...)' from your 'public String processNewEmployee' method.

怎么会?

当您添加@Transactional 时,Spring 会为您的组件创建一个代理并代理所有公共方法.因此,当 Spring 本身将您的方法作为 HTTP Rest Request 调用时,它被认为是通过代理正确执行的外部调用,并根据需要启动新事务并且代码正常工作.

When you add @Transactional, Spring creates a Proxy for your Component and proxies all public methods. So when Spring itself calls your method as a HTTP Rest Request it is considered an external call that goes properly through a Proxy and new Transaction is started as required and code works.

但是,当您有一个代理组件并在类代码中调用this.saveEmployee"(具有 @Transactional 注释)时,您实际上是在绕过 Spring 已创建的代理并且未启动新事务.

But when you have a Proxied Component and you call 'this.saveEmployee' (that has @Transactional annotation) inside your class code you are actually bypassing the Proxy Spring has created and new Transaction is not started.

解决方案:将整个数据库逻辑提取到某种服务或 DAO,然后将其自动连接到您的 Rest 控制器.那么一切都应该像一个魅力.

Solution:Extract entire database logic to some sort of a Service or DAO and just Autowire it to your Rest Controller. Then everything should work like a charm.

无论如何,您应该避免从控制器直接访问数据库,因为这不是一个很好的做法.控制器应该尽可能精简并且不包含业务逻辑,因为它只是一种访问"系统的方式.如果您的整个逻辑都在域"中,那么您只需几行代码即可添加其他方式来运行业务功能(例如创建新用户).

You should avoid direct database access from Controllers anyway as it is not a very good practice. Controller should be as thin as possible and contain no business logic because it is just a 'way to access' your system. If your entire logic is in the 'domain' then you can add other ways to run business functionalities (like new user creation) in a matter of just few lines of code.

这篇关于Spring @Transactional TransactionRequiredException 或 RollbackException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-22 20:40