【问题标题】:persisting MVC data to ORM将 MVC 数据持久化到 ORM
【发布时间】:2013-07-06 02:15:40
【问题描述】:

Java 或 dotNet 世界有丰富的开源框架和库。我们几乎在任何地方都喜欢使用 Spring 和 Hibernate。 每个人都同意hibernate是一个非常方便的工具。 Hibernate 可以做什么?好吧,基本上 - Hibernate 可以跟踪我们的域对象更改并仅将修改后的数据保存到数据库中,就是这样。 基本上,这就是我们想要的一切。我想从数据库中加载一些记录,对它们进行一些修改,然后调用 transaction.commit(),所有修改都会立即持久化。 太好了,对吧!

但是网络世界呢?在 Web 应用程序中,数据库会话必须关闭。 我无法加载一些域对象并等待用户通过 HTTP 进行修改,并在修改后保留这些对象。

我们必须使用分离的对象或 DTO。这个怎么运作 ? 用户在 HTML 浏览器中进行修改,spring Mvc 使用 MVC 模型绑定自动将这些 HTML 修改传递给我们自定义的 DTO 对象, 然后我们做一些编程工作来将修改从 DTO 对象转移到休眠域对象,然后我们才持久化它们。 例如 - 我们有一个更新客户地址的 Web 表单,以及另一个更新客户详细信息的表单。 我们必须有两个不同的业务层方法——UpdateAddress() 和 UpdateDetails(),这两个方法都必须接受某种 DTO, 一个代表地址信息,另一个代表详细信息。 我们还有自定义逻辑,将数据从这 2 个 DTO 传输到域类“客户”。 是的,当然,我们可以重用我们的域类,而不是 DTO 对象。但这并没有让它变得更简单。 在这两种情况下,我们仍然必须实现将修改传输到持久对象的自定义逻辑, 我不能立即持久化分离的对象,因为通常域类有很多很多属性,代表着许多关系,例如。客户有 - Orders 属性。当我更新客户地址时,我不想更新其订单。

是否有一种美观通用的方法可以将修改从 mvc 模型映射到域对象,而无需编写大量自定义代码并且没有覆盖太多字段的风险?

【问题讨论】:

  • 使用 AutoMapper 意味着我必须为每个 Service 方法创建自定义地图,这实际上与使用自定义代码进行传输相同。
  • 这是一行代码而不是手动映射所有项目属性。如果存在无法推断的实体,则为该实体明确定义映射,但大多数情况下可以节省大量时间。
  • 它永远不是一行代码...我为每个服务方法创建多个自定义 DTO 类并使用 1 行代码映射映射它们。或者我使用域对象并为每个服务方法创建映射,指定要包含的属性和要排除的属性,以及嵌套对象的行为。我只是告诉你,如果映射行为可以根据传入的 JSON 或 HTTP POST 参数确定,那么这些映射不是必需的。请看这里-sites.google.com/site/upida4j

标签: asp.net-mvc hibernate model-view-controller spring-mvc orm


【解决方案1】:

拥有一个数据访问层是一种很好的做法,这意味着每个域对象/实体都有一个存储库。此外,所有存储库都共享通用代码,因此您自然拥有一个抽象存储库:

public abstract class AbstractRepository<E extends BaseModel> implements Repository<E> {

    @PersistenceContext
    private EntityManager entityManager;

    private Class<E> entityClass;

    public AbstractRepository(Class<E> entityClass) {
        this.entityClass = entityClass;
    }

    protected EntityManager getEM() {
        return entityManager;
    }

    protected TypedQuery<E> createQuery(String jpql) {
        return createQuery(jpql, entityClass);
    }

    protected <T> TypedQuery<T> createQuery(String jpql, Class<T> typeClass) {
        return getEM().createQuery(jpql, typeClass);
    }

    @Override
    public E merge(E entity) {
        return getEM().merge(entity);
    }

    @Override
    public void remove(E entity) {
        getEM().remove(entity);
    }

    @Override
    public E findById(long id) {
        return getEM().find(entityClass, id);
    }
} 

拥有一个服务层也是一种很好的做法,您可以在其中创建、更新和删除实体的实例(如果您愿意,可以通过 DTO 传递到创建和更新方法)。

...

@Inject
private CustomerRepository customerRepository;

public Customer createCustomer(CustomerDto customerDto) {
    Customer customer = new Customer();
    customer.setEmail(customerDto.getEmail());
    ...
    return customerRepository.merge(customer); 
}

public Customer updateCustomerAddress(Customer customer, String address) {
    customer.setAddress(address);
    return customerRepository.merge(customer);  
}
...

因此,您需要多少更新方法取决于您。我通常会将它们分组为常见操作,例如更新客户地址,您可以将客户 ID 和更新后的地址从前端(可能通过 ajax)传递到在特定端点上侦听的控制器。例如,您可以在此端点使用存储库通过 Id 查找实体,然后将其传递给您的服务以进行地址更新。

最后,您需要确保数据实际被持久化,因此在 Spring 中,您可以将 @Transactional 注释添加到 Spring MVC 控制器或执行持久化的服务。我不知道这方面的任何最佳实践,但我更喜欢将其添加到我的控制器中,这样无论您使用什么服务,您都可以始终保证进行交易。

【讨论】:

  • 嗨,Markus,这看起来是正确的,但同样 - 这是很多自定义编码。数据访问层是可以的,但有可能避免编写与更新每个客户字段相关的自定义代码。
  • 嗨,Markus,这看起来是正确的,但同样 - 这是很多自定义编码。数据访问层没问题,但有可能避免编写与更新每个客户字段相关的自定义代码。想象一下——我的 MVC 层接受客户的 JSON 副本。 JSON 中仅存在 Customer 的某些字段,其他字段缺失。我将 JSON 解析为分离的 Customer 对象,该对象跟踪所有分配的字段。然后我将这个分离的客户对象传递给数据层,在数据层中,它仅将分配的字段映射(或复制)到现有的持久客户对象。我打电话给保存。
  • 而不是 JSON,它可以是 HTTP POST 参数列表。
  • 好的,我知道你在做什么。不幸的是,我不知道任何这样的框架/库/设计模式会给你这个结果。话虽如此,TDD 被广泛接受为开发软件的方式,而分层架构是正确编写单元测试的结果。因此,如果没有服务层,我认为您不会逃脱,因为数据访问层纯粹用于访问(当然也是持久化),您的控制器只是将其委托给适当的服务。
  • 最后,您要查找的代码将是一个相当复杂的自定义服务,难以测试或维护。这最终不是一个好主意。
猜你喜欢
  • 2014-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-16
  • 2019-12-07
  • 2020-12-09
  • 2015-08-19
  • 2021-09-19
相关资源
最近更新 更多