【问题标题】:Correct way and place for json conversion in SpringBootSpring Boot中json转换的正确方法和位置
【发布时间】:2020-04-02 23:46:31
【问题描述】:

我正在开发一个小型 SpringBoot 项目。在 postgres 数据库中存储订单数据。我有一个为我的前端返回 json 的 API。 我在问自己应该在哪个地方将数据转换为实际的 json。我知道有几种方法可以做到,但我想学习专业的方法。

假设订单包含 id、date、customername、productid。对于此示例,当要求输入特定名称时,我只需要将日期作为 order_date 并将 productid 作为我的前端的 product_id。

可能的方法:

1) 在我的 CrudRepository 中使用原生 postgres 查询:

@Query(value="SELECT json_build_object('order_date', o.date, 'product_id', productid) 
       from order where name=:name", nativeQuery = true)
List<Object> getOrderByName(@Param("name") String name);

然后在控制器方法中简单地调用这个存储库方法。 关于转换为 json 的性能可能是最快的方法。 我现在面临的最大缺点是您无法对 crudrepository 方法的返回值进行任何计算,因为它是这个特殊的 postgres 对象。例如,在测试中模拟这个似乎很复杂。

2) 在 crudrepository 中选择整个对象,然后在控制器中创建 json:

Order findByName(String name);

在控制器中,我将创建一个 HashMap 并返回它,假设我的 方法有一个返回类型 ResponseEntity>。

Order order = orderRepository.findByName("John Doe");
HashMap<String, String> jsonResult = new HashMap<>();
jsonResult.put("order_date", order.getName());
jsonResult.put("product_id", order.getProductId());
return jsonResult;

这样做的好处是我可以对从数据库中获得的订单对象进行计算。我不必使用丑陋的 sql 查询,可以利用我的 ORM 系统的优势。最大的缺点是,我总是必须在每个控制器方法的末尾创建这个自定义 json,这感觉不对。

3) 使用 JPA 投影。我没有尝试过,但我读到了它 (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections)

做这个非常标准的动作的专业方法是什么?它是如何在更大的企业应用程序中完成的?大型企业应用程序是否甚至使用 jpa crudrepository 或如何与 java 中的数据库进行交互?

【问题讨论】:

  • JSON 转换将自动发生(通常由 Jackson “自动”完成),因此您只需要确保在给定端点上返回的类型是 JSON……仅此而已。
  • 但是如何处理我的例子呢?我不想返回整个订单对象。正如我在示例中提到的,我只想返回两列,并且在 json 中它们应该具有特定的名称。性能呢
  • JsonProperty 让你控制键名; JsonView 让您控制返回的字段(在哪个端点上)。您还可以将您的类型映射为“返回类型”,并仅在那些上声明您想要返回的字段/成员。
  • 好的,谢谢!但是,当我想返回包含多个对象和变量的 json 时,情况又如何呢?假设在这个特定的 api 方法中,我想添加一个不属于订单对象的随机字符串。如何将多个对象组合成一个新的 json?该方法的返回类型是什么,或者我现在会使用我在问题中提供的 hashmap 解决方案吗?
  • 这就是我所说的“返回类型”,您可以将要返回的内容放在那些上,并基于一个或多个其他对象构建它们。

标签: java postgresql hibernate spring-boot jpa


【解决方案1】:

您首先需要一个可以映射到您的表的实体以及该实体的 crud/JPA 存储库。您可以查询存储库(在控制器中)并控制要由 API 发回的字段(映射到表的列)。

以下是一个示例(您需要包含 spring web 依赖项以启用自动 Jackson 配置)。这里@JsonIgnore注解,忽略JSON响应的字段。但它将在 java 对象中可用。

@Entity
public class Order {

    @Column(name = "product_id")
    protected Long productId;

    @Column(name = "order_date")
    protected Date orderDate;

    @JsonIgnore
    @Column(name = "order_number")
    protected Date orderNumber;

    //setters and getters

}

@Transactional
public interface OrderRepo extends JpaRepository<Order, Long>{
}

@Controller
public class OrderController
{

  public OrderController(){}

  @Autowired
  OrderRepo orderRepo;

  @GetMapping(/orders/{id})
  public Order findOrder(@PathVariable("id")long id)
  {
   return orderRepo.findById(id);
  }
}

【讨论】:

    【解决方案2】:

    假设您有一个“标准”项目结构:控制器、服务、存储库和域模型 — 为简洁起见,省略了 imports。

    控制器

    @Controller
    @RequestMapping("/entities")
    public final class TheController {
      private static final Logger log = LoggerFactory.getLogger(TheController.class);
    
      private final TheService service;
    
      public TheController(final TheService service) {
        this.service = service;
      }
    
      @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
      public ResponseEntity<Collection<JpaEntity>> getAll(final HttpServletRequest request) {
        log.info("{} {}", request.getMethod(), request.getRequestURI());
        return ResponseEntity.ok(service.searchAll());
      }
    
      @GetMapping(path = "{entity-id}", produces = MediaType.APPLICATION_JSON_VALUE)
      public ResponseEntity<?> getById(final @PathVariable("entity-id") String id, final HttpServletRequest request) {
        log.info("{} {}", request.getMethod(), request.getRequestURI());
        final Optional<JpaEntity> result = service.searchById(UUID.fromString(id));
        if (result.isPresent()) {
          return ResponseEntity.ok(result.get());
        }
        return ResponseEntity.notFound().build();
      }
    
      @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
      public ResponseEntity<?> post(@RequestBody @Valid final JpaEntity body, final HttpServletRequest request) {
        log.info("{} {}", request.getMethod(), request.getRequestURI());
        final URI uri = URI.create(String.format("%s/%s", request.getRequestURI(), service.save(body).getId())).normalize();
        return ResponseEntity.created(uri).build();
      }
    }
    

    服务

    @Service
    @Transactional
    public class TheService {
      private static final Logger log = LoggerFactory.getLogger(TheService.class);
    
      private TheRepository repository;
    
      public TheService(final TheRepository repository) {
        this.repository = repository;
      }
    
      @Transactional(readOnly = true)
      public Collection<JpaEntity> searchAll() {
        log.info("Retrieving all records...");
        return repository.findAll();
      }
    
      @Transactional(readOnly = true)
      public Optional<JpaEntity> searchById(final UUID id) {
        log.info("Retrieving record with ID [{}]", id);
        return repository.findById(id);
      }
    
      public JpaEntity save(final JpaEntity entity) {
        log.info("Persisting record [{}]", entity);
        return repository.save(entity);
      }
    }
    

    存储库

    public interface TheRepository extends JpaRepository<JpaEntity, UUID> { }
    

    领域模型

    @MappedSuperclass
    @EntityListeners(AuditingEntityListener.class)
    abstract class AbstractPersistable<T> implements Serializable {
      private static final long serialVersionUID = -537959523291969928L;
    
      @Id
      @JsonProperty("_id")
      @GeneratedValue(generator = "uuid2")
      @GenericGenerator(name = "uuid2", strategy = "uuid2")
      @Column(name = "id", nullable = false, updatable = false, unique = true)
      private T id;
    
      @CreatedDate
      @Column(name = "created_on", nullable = false, updatable = false)
      private Instant createdOn;
    
      @CreatedBy
      @Column(name = "created_by", nullable = false)
      private String createdBy;
    
      @LastModifiedDate
      @Column(name = "modified_on", nullable = false)
      private Instant modifiedOn;
    
      @LastModifiedBy
      @Column(name = "modified_by", nullable = false)
      private String modifiedBy;
    
      @Version
      @JsonProperty("_version")
      private Integer version;
    
      AbstractPersistable() { } // No-args constructor required by JPA spec
    
      public T getId() { return id; }
    
      public Instant getCreatedOn() { return createdOn; }
    
      public String getCreatedBy() { return createdBy; }
    
      public Instant getModifiedOn() { return modifiedOn; }
    
      public String getModifiedBy() { return modifiedBy; }
    
      public Integer getVersion() { return version; }
    
      @Override
      public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
            .append("id", id)
            .append("createdOn", createdOn)
            .append("createdBy", createdBy)
            .append("modifiedOn", modifiedOn)
            .append("modifiedBy", modifiedBy)
            .append("version", version)
            .toString();
      }
    }
    
    @Entity
    @Immutable
    @Table(name = "tb_name")
    public final class JpaEntity extends AbstractPersistable<UUID> {
      private static final long serialVersionUID = -1352464759104643303L;
    
      @Column(name = "column1", length = 128, nullable = false)
      private String one;
    
      @Column(name = "column2", length = 128, nullable = false)
      private String two;
    
      private JpaEntity() { } // No-args constructor required by JPA spec
    
      public JpaEntity(final String one, final String two) {
        this.one = one;
        this.two = two;
      }
    
      public String getOne() { return one; }
    
      public String getTwo() { return two; }
    
      @Override
      public int hashCode() {
        return Objects.hash(one, two);
      }
    
      @Override
      public boolean equals(final Object obj) {
        if (this == obj) { return true; }
        if (!(obj instanceof JpaEntity)) { return false; }
        final JpaEntity entity = (JpaEntity) obj;
        return Objects.equals(one, entity.one) &&
            Objects.equals(two, entity.two);
      }
    
      @Override
      public String toString() {
        return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
            .appendSuper(super.toString())
            .append("one", one)
            .append("two", two)
            .toString();
      }
    }
    

    就目前而言,当向用户代理发送响应时,Spring Boot 将为JpaEntity 生成适当的 JSON 表示。

    如果您想控制哪些字段被序列化,您可以利用@JsonView 注解在发生序列化/反序列化时包含/排除一组成员/字段,例如:

    // CustomView.java
    public interface CustomView { }
    
    // JpaEntity.java
    @JsonView(CustomView.class)
    @Column(name = "column1", length = 128, nullable = false)
    private String one;
    
    // TheController.java
    // When CustomView is used you are only going to get back what's annotated
    // with CustomView in the given response type (JpaEntity on this case)
    @JsonView(CustomView.class)
    @GetMapping(path = "{entity-id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getById(final @PathVariable("entity-id") String id, final HttpServletRequest request) {
      log.info("{} {}", request.getMethod(), request.getRequestURI());
      final Optional<JpaEntity> result = service.searchById(UUID.fromString(id));
      if (result.isPresent()) {
        return ResponseEntity.ok(result.get());
      }
      return ResponseEntity.notFound().build();
    }
    

    或者,您可以使用其他返回类型,例如:JpaEntityResponse

    public final class JpaEntityResponse {
      private String one;
    
      private String three;
    
      private String andFour;
    
      private JpaEntityResponse() { } // No-args constructor required by JPA spec
    
      public JpaEntityResponse(final String one, final String two, final String andFour) {
        this.one = one;
        this.three = three;
        this.andFour = andFour;
      }
    
      // ...getters
    }
    

    然后,在“服务”类的某个地方,假设您正在处理两个不同的域模型并希望将一些字段聚合到JpaEntityResponse,那么您只需要选择您所在的成员/字段有兴趣,构建您的JpaEntityResponse 并将其返回(也需要修改控制器的方法以返回此类型)。

    【讨论】:

      猜你喜欢
      • 2016-07-06
      • 2017-07-23
      • 2019-08-12
      • 2018-11-07
      • 1970-01-01
      • 2018-08-07
      • 2017-03-16
      • 2019-04-19
      • 2018-05-09
      相关资源
      最近更新 更多