2015-09-01 6 views
0

Я прочитал много комментариев @Transactional, я увидел ответы stackoverflow, но это мне не помогает. Поэтому я задаю свой вопрос.Spring @Transactional TransactionRequiredException или RollbackException

Моим случаем является сохранение пользователя с уникальным электронным письмом. В БД у меня есть пользователь с адресом электронной почты [email protected], и я сохраняю пользователя с тем же адресом электронной почты. Для экономии я должен использовать 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) и, конечно, я получил «Сделку отмеченный как rollbackOnly "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

Ваши транзакции не работают, потому что вы вызываете непосредственно 'this.saveEmploy ee (...) 'из вашего метода public String processNewEmployee.

Почему?

Когда вы добавляете @Transactional, Spring создает прокси для вашего Компонента и проксирует все общедоступные методы. Поэтому, когда Spring сама называет ваш метод HTTP Request Rest, он считается внешним вызовом, который идет правильно через прокси, и новая транзакция запускается по мере необходимости, а код работает.

Но если у вас есть Прокси-компонент и вы вызываете «this.saveEmployee» (который имеет аннотацию @Transactional) внутри вашего кода класса, вы фактически обходите прокси-пружину, и новая транзакция не запущена.

Решение: Extract вся логика базы данных в какой-то службы или DAO и просто Autowire его к контроллеру Rest. Тогда все должно работать как шарм.

Вы должны избегать прямого доступа к базе данных от контроллеров, так как это не очень хорошая практика. Контроллер должен быть как можно более тонким и не содержать бизнес-логики, потому что это всего лишь «способ доступа» к вашей системе. Если вся ваша логика находится в «домене», вы можете добавить другие способы запуска бизнес-функций (например, создания новых пользователей) только в нескольких строках кода.

+0

Thx для вашего ответа и совета по DDD. Я понимаю, что вы написали, и я попытаюсь изменить коэффициент кода. Теперь я обновляю свой вопрос, пожалуйста, посмотрите на мое решение и дайте мне несколько комментариев, если он в порядке или нет, поэтому я мог бы отметить ваш ответ как правильный. – Purzynski

+0

Теперь он выглядит намного лучше. Прокси весной может быть сложным время от времени;) –

Смежные вопросы