JPA:使用服务和多个存储库的正确方法

2022-09-01 22:38:13

我制作了一个使用JPA和Spring的系统。例如,如果我需要处理帐户,我会使用存储库:

@Repository
public interface AccountRepository extends JpaRepository<Account, Long>

然后,我创建一个使用存储库的服务:

class AccountServiceImpl implements AccountService {

  @Autowired
  private AccountRepository repository;

  @Transactional
  public Account save(Account account){
    return repository.save(account);
  }

...

现在,我已经创建了一个控制器来处理帐户的 POST 方法:

@Controller
public class AccountController {

@Autowired    
private final accountService service;

@RequestMapping(value = "/account", method = RequestMethod.POST)
        public ModelAndView account(@Valid Account account, BindingResult bindingResult) {
    ...
service.save(account);

客户,产品,联系人等也是如此。

现在,假设我还有一个名为“注册”的类,其中包含足够的数据来创建客户,包括他的帐户,联系人数据和产品(大量数据)。“确认”注册的操作是专门用于执行此操作的操作:

@RequestMapping(value = "/confirm", method = RequestMethod.POST)
    public ModelAndView confirmRegistration(@Valid Registration registration, BindingResult bindingResult) {

现在我的问题:为每个存储库调用 save 方法的正确方法是什么?

1)我应该在控制器中创建类,然后为每个创建的类调用save方法:

@RequestMapping(value = "/confirm", method = RequestMethod.POST)
        public ModelAndView confirmRegistration(@Valid Registration registration, BindingResult bindingResult) {
...
customerService.save(customer);
accountService.save(account);
contactDataService.save(contactData);
productService.save(contactData);
...

2) 调用注册服务中每个服务的保存:

class RegistrationServiceImpl implements RegistrationService {

  @Autowired
  private AccountService accountService;
  @Autowired
  private CustomerService customerService;
  ....

  @Transactional
  public void confirm(Registration registration){
  ... here I create the object
  customerService.save(customer);
  accountService.save(account);
  }

3) 调用注册服务中每个存储库的保存:

class RegistrationServiceImpl implements RegistrationService {

  @Autowired
  private AccountRepository accountRepository;
  @Autowired
  private CustomerRepository customerRepository;
  ....

  @Transactional
  public void confirm(Registration registration){
  ... here I create the object
  customerRepository.save(customer);
  accountRepository.save(account);
  }

如果我必须使用(1),我明白了。但对选项(2)和(3)感到困惑。

问题再次出现:

我应该/是否可以在服务中使用其他服务?或者我必须在服务中仅使用存储库?

谷歌对此的解释的正确方法是什么?对不起,英语不是我的母语,我找不到正确的方法来询问这种设计。


答案 1

服务并不总是事务性的,但是当您使用JPA执行数据库工作时,事务非常重要,因为事务可以确保您的更改以可预测的方式提交,而不会受到同时进行的其他工作的干扰。Spring使你的服务变得容易交易,确保你理解交易,这样你就可以充分利用它们。

您可以在服务中使用服务,您可以设置事务传播,以便它们都使用相同的事务,或者它们可以使用单独的事务,其中任何一个都有有效的情况。但我建议不要做你在这里做的事情。

服务是放置业务逻辑的地方,尤其是需要事务性(全有或全无)的业务逻辑。根据功能将逻辑组织到服务中是有意义的,这样服务方法就是用户在扮演某些特定角色时执行的操作。

但是为每种类型的实体提供服务并不是很有用,我建议不要这样做。一个服务可以有任意数量的存储库,您不必将每个存储库包装在自己的服务中。(教程显示了特定于实体的服务,或者它们可能会完全跳过服务层,这是因为它们希望向您展示框架功能,并最小化业务逻辑。但实际的应用程序往往有很多业务逻辑。

你对特定于实体的服务执行的操作存在一个问题:在控制器中一个接一个地调用它们意味着每个服务都使用自己的事务。创建事务很慢,并且具有单独的事务会打开可能的数据不一致的情况。将业务逻辑包含在一个事务中,会将一致性问题的风险限制为仅与事务隔离级别相关的问题。

我也不同意服务应该在dtos和实体之间转换的想法。偶尔你可能会发现需要dto,但它不应该是例行公事。对于使用 JSP 或 thymeleaf 的 Web 应用程序,您可以愉快地将实体添加为请求属性,并让模板直接使用它们。如果您的控制器需要返回 JSON,则可以挂接消息转换器以直接从实体生成 JSON,或者最好使用 dto。尽量将重点放在实现业务功能上,并避免将数据从一种持有者移动到另一种类型的持有者上,因为这种代码很脆弱且容易出错。

对于控制器和服务之间的差异,我在这里和这里都有答案。


答案 2

控制器理想情况下,控制器仅用于接受 HTTP 请求并提供响应。如果数据保存在数据库或文件中,或者通过另一个HTTP调用发送到另一个服务,控制器不应该打扰。始终保持控制器不受任何业务影响。

服务

服务层是将数据传输对象转换为实体的位置,反之亦然。控制器接收的数据对象可能/可能不会直接映射到数据库实体。此转换由服务层完成。在您的情况下,您的控制器接收Registration

因此,最好的方法是让你的控制器调用RegisterService,并将其传递给接收到的数据对象来处理它。

注册服务的工作是将数据对象转换为数据库实体并将其保存在事务中。


我应该/是否可以在服务中使用其他服务?或者我必须在服务中仅使用存储库?

我更喜欢将问题分开。

例如,只有账户服务才能知道账户存储库的进出。帐户服务应该像处理帐户的中间人一样。你想保存它吗?把它给我。你想找到它吗?我会为你找到。

简而言之,我更喜欢注册服务调用其他服务,而不是直接调用存储库。


推荐