【问题标题】:JPA: Correct way to use Service and multiple RepositoriesJPA:使用服务和多个存储库的正确方法
【发布时间】:2019-01-29 07:15:24
【问题描述】:

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

@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);
  }

...

现在,我创建了一个控制器来处理 Accounts 的 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);

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

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

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

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

1) 我是否应该在 controller 中创建类,然后为创建的每个类调用 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) 在RegistrationService中调用每个服务的saves:

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) 在RegistrationService中调用每个repository的saves:

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)感到困惑。

问题又来了:

应该/我可以在服务中使用其他服务吗?或者我只能在服务中使用存储库?

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

【问题讨论】:

  • 考虑事务分界也许是一个起点。服务处理存储库的事务。
  • 谷歌搜索建议:spring 采用了领域驱动设计的术语,您可以谷歌领域驱动设计或 DDD 以及服务或存储库等术语以获得更多概念性解释。

标签: java spring jpa spring-data-jpa


【解决方案1】:

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

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

服务是您放置业务逻辑的地方,尤其是您需要事务性(全有或全无)的业务逻辑。根据功能将您的逻辑组织成服务是有意义的,因此服务方法是用户在扮演某些特定角色时采取的行动。

但是为每种类型的实体提供服务并不是很有用,我建议不要这样做。服务可以有任意数量的存储库,您不必将每个存储库都包装在自己的服务中。 (教程展示的是实体特定的服务,或者他们可能完全跳过服务层,那是因为他们想向您展示框架功能,并最大限度地减少业务逻辑。但实际应用程序往往有很多业务逻辑。)

您对特定于实体的服务所做的一个问题:在控制器中一个接一个地调用它们意味着每个都使用自己的事务。创建事务很慢,并且拥有单独的事务会使您面临可能的数据不一致。将您的业务逻辑放在一个事务中会将您对一致性问题的暴露限制在与您的事务隔离级别相关的那些问题上。

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

对于控制器和服务之间的差异,我有herehere 的答案。

【讨论】:

  • 很好的解释。实际上我有一个服务类正在加载五个存储库(在构造函数中加载)。它会导致任何问题吗?我的怀疑来自“每个方法的签名中不要有太多参数”的规则。我已将所有涉及到的存储库添加到操作中,它需要从不同的实体获取数据。我认为这样交易将是安全的。如果有更好的方法可以做到这一点,我会全力以赴。
  • @Funder:我认为这本身并不构成问题。如果看起来有必要,也许您可​​以使用嵌套服务来传播事务。但我见过更糟糕的情况。 :-)
  • Huges,好吧,我正在阅读有关 BPM 工具(如 jBPM 或 Camunda)的信息,我总是担心无法实现夸张的外部实用程序,但我认为 bpm 中可能有一些有用的东西。我承认我在 2 年后回到 java,所以我觉得我不知道很多东西。让我们进行实验:-)
【解决方案2】:

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

服务

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

因此,最好的方法是让您的控制器调用RegistrationService 并将接收到的数据对象传递给它以进行处理。

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


应该/我可以在服务中使用其他服务吗?或者我只能使用 服务中的存储库?

我更喜欢将关注点分开。

例如,只有 AccountsService 应该知道 Accounts Repository 的进出。 Account Service 应该充当处理帐户的中间人。你想保存它吗?把它给我。你想找到它吗?我会为你找到的。

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

【讨论】:

  • 我同意你的观点,即使用聚合服务包含其他服务来完成复杂的逻辑。但要确保服务之间的依赖关系。
  • 如果这样做,则需要在域服务中重复存储库的功能
  • 我不确定这种方法,因为事务边界。我更愿意进行一次注册交易,而不是多次。
猜你喜欢
  • 2011-06-28
  • 1970-01-01
  • 2022-01-01
  • 2015-10-20
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 2019-07-30
相关资源
最近更新 更多