【问题标题】:Spring Projections not returning the State DetailsSpring Projections 不返回状态详细信息
【发布时间】:2018-11-10 19:21:33
【问题描述】:

我有一个已与 Spring Data JPA 集成的 Country 和 State 表。我在 CountryServiceImpl 中创建了一个函数public Page<CountryDetails> getAllCountryDetails,用于获取所有国家和相应的国家详细信息。该服务运行良好,并为我提供以下输出:

{
  "content": [
    {
      "id": 123,
      "countryName": "USA",
      "countryCode": "USA",
      "countryDetails": "XXXXXXXX",
      "countryZone": "XXXXXXX",
      "states": [
        {
          "id": 23,
          "stateName": "Washington DC",
          "countryCode": "USA",
          "stateCode": "WAS",
          "stateDetails": "XXXXX",
          "stateZone": "YYYYYY"
        },
        {
          "id": 24,
          "stateName": "Some Other States",
          "countryCode": "USA",
          "stateCode": "SOS",
          "stateDetails": "XXXXX",
          "stateZone": "YYYYYY"
        }
      ]
    }
  ],
  "last": false,
  "totalPages": 28,
  "totalElements": 326,
  "size": 12,
  "number": 0,
  "sort": null,
  "numberOfElements": 12,
  "first": true
}

我的完整代码如下:

CountryRepository.java

@Repository
public interface CountryRepository extends JpaRepository<CountryDetails, Integer> {

    @Query(value = "SELECT country FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}", 
    countQuery = "SELECT COUNT(*) FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}")
    public Page<CountryDetails> findAll(Pageable pageRequest);
}

CountryServiceImpl.java

@Service
public class CountryServiceImpl implements CountryService {

    @Autowired
    private CountryRepository countryRepository;

    @Override
    public Page<CountryDetails> getAllCountryDetails(final int page, final int size) {
        return countryRepository.findAll(new PageRequest(page, size));
    }
}

CountryDetails.java

@Entity
@Table(name = "country", uniqueConstraints = @UniqueConstraint(columnNames = "id"))
public class CountryDetails {

    @Id
    @GeneratedValue
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;
    private String countryName;
    private String countryCode;
    private String countryDetails;
    private String countryZone;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "countryDetails")
    private List<State> states;

    // getters / setters omitted
}

State.java

@Entity
@Table(name = "state", uniqueConstraints = @UniqueConstraint(columnNames = "id"))
public class State {

    @Id
    @GeneratedValue
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;
    private String stateName;
    private String countryCode;
    private String stateCode;
    private String stateDetails;
    private String stateZone;

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "countryCode", nullable = false, insertable = false, updatable = false, foreignKey = @javax.persistence.ForeignKey(name="none",value = ConstraintMode.NO_CONSTRAINT))
    private CountryDetails countryDetails;

    // getters / setters omitted
}

现在的问题

实际上,我希望国家/地区服务以最少的信息返回,如下所示

{
  "content": [
    {
      "countryName": "USA",
      "countryCode": "USA",
      "states": [
        {
          "stateCode": "WAS"
        },
        {
          "stateCode": "SOS"
        }
      ]
    }
  ],
  "last": false,
  "totalPages": 28,
  "totalElements": 326,
  "size": 12,
  "number": 0,
  "sort": null,
  "numberOfElements": 12,
  "first": true
}

为了实现这一点,我使用了如下所示的投影

CountryProjection .java

public interface CountryProjection {
    public String getCountryName();
    public String getCountryCode();
    public List<StateProjection> getStates();
}

StateProjection .java

public interface StateProjection {
    public String getStateCode();
}

CountryServiceImpl.java

@Repository
public interface CountryRepository extends JpaRepository<CountryDetails, Integer> {

    @Query(value = "SELECT country.countryName AS countryName, country.countryCode AS countryCode FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}", 
    countQuery = "SELECT COUNT(*) FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}")
    public Page<CountryProjection> findAll(Pageable pageRequest);
}

但现在服务正在返回如下所示的任何状态详细信息

{
  "content": [
    {
      "countryName": "USA",
      "countryCode": "USA"
    }
  ],
  "last": false,
  "totalPages": 28,
  "totalElements": 326,
  "size": 12,
  "number": 0,
  "sort": null,
  "numberOfElements": 12,
  "first": true
} 

我们怎样才能得到最小的状态细节,如下所示

{
  "content": [
    {
      "countryName": "USA",
      "countryCode": "USA",
      "states": [
        {
          "stateCode": "WAS"
        },
        {
          "stateCode": "SOS"
        }
      ]
    }
  ],
  "last": false,
  "totalPages": 28,
  "totalElements": 326,
  "size": 12,
  "number": 0,
  "sort": null,
  "numberOfElements": 12,
  "first": true
}

谁能帮我解决这个问题

【问题讨论】:

  • 为什么接口中的 getter 使用 private 修饰符?
  • @Turo 对此感到抱歉......它只公开
  • 你没有在 CountryProjection 中使用 StateProjection,这些接口不应该扩展 Serializable 吗?
  • @Turo 我没听懂....你能给我看个例子吗
  • @Turo 你的意思是我使用State 而不是StateProjection,只有stateCode。即使我尝试过,但状态详细信息没有打印出来

标签: java mysql hibernate spring-data-jpa spring-projections


【解决方案1】:

您可以让您的 CountryService 返回一个 DTO,而不是只包含您需要的字段的实体。

服务

@Service
public class CountryServiceImpl implements CountryService {

    @Autowired
    private CountryRepository countryRepository;

    @Override
    public Page<CountryDetailsDto> getAllCountryDetails(final int page, final int size) {
        return countryRepository.findAll(new PageRequest(page, size))
                .map(c -> {
                    CountryDetailsDTO dto = new CountryDetailsDTO();
                    dto.setCountryCode(c.getCountryCode());
                    dto.setCountryName(c.getCountryName());

                    dto.setStates(c.getStates().stream().map(s -> {
                        StateDto stateDto = new StateDto();
                        stateDto.setStateCode(s.getStateCode());

                        return stateDto;
                    }).collect(Collectors.toSet()));

                    return dto;
                });
    }
}

DTO

public class CountryDetailsDTO {

    private String countryName;

    private String countryCode;

    private Set<StateDto> states;
}
public class StateDto {

    private String stateCode;
}

【讨论】:

    【解决方案2】:

    尝试将 JsonIgnore 与返回 JSON 中不需要的字段一起使用

    @JsonIgnore
    private String stateDetails;
    

    【讨论】:

    • 我无法使用@JsonIgnore,因为我需要其他服务中的这些详细信息......
    • 在@Query 中您不是在查询状态代码表单状态表,请尝试加入状态表
    【解决方案3】:

    您可以将transient 关键字与json 中不需要的变量一起使用。

    否则使用

    @Expose String myString;
    

    【讨论】:

    • @manishsingh....谢谢您的回复....你能给我举个例子吗
    【解决方案4】:

    第一个想法

    IMO 你做事的方式有些不对劲。我不明白你为什么要这样直接定义查询:

    @Query(value = "SELECT country.countryName AS countryName, country.countryCode AS countryCode FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}", 
        countQuery = "SELECT COUNT(*) FROM Country country GROUP BY country.countryId ORDER BY ?#{#pageable}")
    

    基本上就是通过select创建一个投影。

    另一方面,您正在使用 Interface-based Projections 再次进行投影,但通过公开您想要投影的属性的吸气剂。据我所知,您通过接口很好地定义了层次结构,它应该是有效的方法。

    所以我要问的是,您是否尝试过将@Query 部分全部删除?

    第二个想法(在评论中)

    另一个想法可以是在jpql 中使用join fetch construct,它用于告诉休眠以通过查询热切地加载关联。

    @Query(value = "SELECT country.countryName AS countryName, country.countryCode AS countryCode, countryStates FROM Country country join fetch country.states countryStates GROUP BY country.countryId ORDER BY ?#{#pageable}"
    

    【讨论】:

    • 感谢您的回复....实际上我无法删除@Query,因为我还有一个搜索查询也可以针对多个字段进行搜索(我没有在当前的 SO 示例中显示)... .
    • @AlexMan 当您不按原样提出问题时,您如何期望可行的解决方案?
    • 我试过了...并得到了一个例外Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
    • 好的,似乎该关联已经被其他东西配置为渴望。您可以再尝试 2 件事:1. 从查询中仅删除 fetch 或 2:完全删除连接子句,但将 country. states 添加到选择中。
    • 你的意思是这样@Query(value = "SELECT country.countryName AS countryName, country.countryCode AS countryCode, countryStates FROM Country country country.states countryStates GROUP BY country.countryId ORDER BY ?#{#pageable}"
    【解决方案5】:

    您可以查看注释的文档:

    @JsonIgnoreProperties("字段名")

    此注释需要在您的案例 Country*、State* 中应用于您的 POJO 类,提及您不需要成为响应的一部分的字段的逗号分隔列表。

    您可以尝试这种方法,而不是更改为投影样式的实现。

    @JsonIgnoreProperties({ "id","countryDetails","countryZone"})
    public class CountryDetails
    
    @JsonIgnoreProperties({ "id","stateName","countryCode","stateDetails","stateZone"})
    public class State
    

    【讨论】:

    • 感谢您的回复......我应该在哪里应用 json 忽略注释......在 CountryDetails 和 State 模型类......实际上如果我这样做,那么它将总是忽略这些字段...... ..还有其他服务我需要忽略的值
    • 是的,你是对的,在返回响应时它们会被忽略。与您的情况一样,如果您必须处理相同的 Object 结构以在不同的 API 调用中表现不同,那么您必须提出自定义 Mapper Utility 来检查请求调用并派生要包含在响应中的必要属性。寻找具有此类定制的以下项目。 github.com/monitorjbl/json-view
    猜你喜欢
    • 2019-07-05
    • 1970-01-01
    • 2020-09-18
    • 2014-07-17
    • 1970-01-01
    • 2016-10-27
    • 1970-01-01
    • 2022-08-22
    • 1970-01-01
    相关资源
    最近更新 更多