问题描述
我已经阅读了很多@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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!