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' 中,那么您只需几行代码就可以向 运行 业务功能(如创建新用户)添加其他方式。
我已经阅读了很多 @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' 中,那么您只需几行代码就可以向 运行 业务功能(如创建新用户)添加其他方式。