Spring @Transactional TransactionRequiredException 或 RollbackException

Spring @Transactional TransactionRequiredException or RollbackException

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

我的情况是用唯一的电子邮件保存用户。在数据库中,我有电子邮件 xxx@xxx.com 的用户,并且我正在使用相同的电子邮件地址保存用户。为了节省我必须使用 entityManager.merge() 因为这个 post 这并不重要。

第一个例子:

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

第二个例子:

@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),当然我得到了 "Transaction marked as rollbackOnly" 异常。

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

如果这很重要,我将我的 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();
    }
}

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

解法:

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

@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(...)'。

怎么会?

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

但是当你有一个代理组件并且你在你的 class 代码中调用 'this.saveEmployee'(有 @Transactional 注释)时你实际上绕过了代理 Spring 已经创建和新交易未开始。

解法: 将整个数据库逻辑提取到某种服务或 DAO,然后将其自动连接到您的 Rest 控制器。然后一切都应该像魅力一样工作。

你应该避免从控制器直接访问数据库,因为这不是一个很好的做法。控制器应该尽可能薄并且不包含业务逻辑,因为它只是一个 'way to access' 您的系统。如果您的整个逻辑都在 'domain' 中,那么您只需几行代码就可以向 运行 业务功能(如创建新用户)添加其他方式。