Spring @Transactional TransactionRequiredException或RollbackException

时间:2015-09-01 18:03:23

标签: java spring transactions annotations

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

我的情况是使用唯一的电子邮件保存用户。在DB中我有用户电子邮件xxx@xxx.com,我使用相同的电子邮件地址保存用户。为了保存,我必须使用entityManager.merge(),因为这篇文章thymeleaf binding collections并不重要。

第一个例子:

@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),当然我得到了#34; Transaction标记为rollbackOnly&#34; exeption。

当我尝试保存不存在的电子邮件时,这段代码运行正常,所以我认为@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);
    }
}

和EmployeeController:

@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);
         // (.....)
    }

}

1 个答案:

答案 0 :(得分:1)

您的交易无效,因为您直接致电&#39; this.saveEmployee(...)&#39;来自你的公共字符串processNewEmployee&#39;方法

怎么来?

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

但是当你有一个Proxied Component并且你打电话给这个.saveEmployee&#39; (在您的类代码中有@Transactional注释)您实际上绕过了代理Spring创建的并且新的Transaction未启动。

<强>解决方案: 将整个数据库逻辑提取到某种服务或DAO,然后将其自动装配到您的Rest Controller。然后一切都应该像魅力一样。

您应该避免从控制器直接访问数据库,因为这不是一个很好的做法。控制器应该尽可能薄,不包含业务逻辑,因为它只是一种访问&#39;的方式。你的系统。如果您的整个逻辑位于&#39;域&#39;然后,您可以添加其他方式来运行业务功能(如新用户创建),只需几行代码。