簡體   English   中英

Spring @Transactional TransactionRequiredException或RollbackException

[英]Spring @Transactional TransactionRequiredException or RollbackException

我已經閱讀了很多@Transactional批注,我看到了stackoverflow的答案,但對我沒有幫助。 所以我要提出我的問題。

我的情況是用唯一的電子郵件保存用戶。 在數據庫中,我有一個使用電子郵件xxx@xxx.com的用戶,並且正在使用相同的電子郵件地址保存該用戶。 為了保存,我必須使用entityManager.merge()因為此后胸腺葉綁定集合不重要。

第一個例子:

@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),當然我得到了“交易標記為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);
    }
}

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

}

您的交易無效,因為您直接從“公共字符串processNewEmployee”方法中調用“ this.saveEmployee(...)”。

怎么會?

當添加@Transactional時,Spring為您的組件創建一個代理,並代理所有公共方法。 因此,當Spring本身將您的方法作為HTTP Rest Request調用時,它被認為是通過代理正確進行的外部調用,並且根據需要啟動了新的Transaction,並且代碼可以正常工作。

但是,當您具有代理組件並在類代碼中調用“ this.saveEmployee”(具有@Transactional批注)時,實際上實際上是在繞過已創建的代理Spring,並且不會啟動新的Transaction。

解決方案:將整個數據庫邏輯提取到某種服務或DAO中,然后將其自動連接到Rest Controller。 然后,一切都應該像魅力一樣起作用。

無論如何,您都應避免從Controllers直接訪問數據庫,因為這不是很好的做法。 控制器應該盡可能的薄,並且不包含任何業務邏輯,因為它只是訪問系統的一種方式。 如果您的整個邏輯都在“域”中,那么您只需幾行代碼就可以添加其他方式來運行業務功能(例如創建新用戶)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM