java - 在 Spring Data Rest 项目中使用任意查询作为投影

标签 java spring jpa spring-data-jpa spring-data-rest

如何在某些存储库中使用任意 sql 查询(我的意思是 native sql 查询)?我的实际问题是这样的:

@Data //lombok thing
@Entity
public class A extends AuditModel {
  private long id;
  private String name;

  @OneToMany(mappedBy="a") //Comments.a is owning side of association, i.e. comments table does have column called a_id as foreign key
  @ToString.Exclude
  private Set<Comments> comments = new HashSet();

  @OneToMany(mappedBy="a") //SimpleFile.a is owning side of association
  private Set<SimpleFile> comments = new HashSet();
}

比我有我的存储库,它使用 HAL+json 表示公开了很好的 CRUD 接口(interface)。我试图通过一些投影/ View 来丰富它,特别是由于 Web UI 在单个请求中加载一页数据。我知道摘录和预测,但它们似乎不够强大。

@Repository
@RepositoryRestResource
@Transactional(readOnly = true)
public interface ARepository extends PagingAndSortingRepository<A, Long> {
  Page<A> findByNameContaining(String namePart, Pageable pageable);
  @Query(
    value = "SELECT a.name,\n" +
      "(SELECT CAST(count(ac.id) AS int) FROM COMMENTS ac WHERE ac.a_id = a.id),\n" +
      "(SELECT listagg(asf.id) FROM SIMPLE_FILES asf WHERE asf.a_id = a.id)\n" +
      "FROM AS a\n" +
      "WHERE a.id = :id",
    nativeQuery = true
  )
  Optional<ACustomPage42DTO> getByIdProjectedForScreen42(Long id);
}

我也尝试过使用 JPQL,但是我在 fetch join 方面遇到了问题(因为我不熟悉 JPQL)。我的最后一个评估查询是这样的:

@Query("SELECT new sk.qpp.qqq.documents.projections.ACustomPage42DTO(" +
  "a " +
  "(SELECT CAST(count(ac) AS int) FROM COMMENTS ac WHERE ac.a = a)" +
  ")\n" +
  "FROM A a\n" +
  "LEFT JOIN FETCH a.simpleFiles\n" +
  "WHERE a.id = :id"
)

我想获得一些关于什么方法最适合实现在 DTO 中返回的自定义和复杂查询的一般建议(最好在需要时提供一些指向操作的特定链接)。

PS:实现接口(interface)并返回简单(原始)数据的工作。还可以使用 JPQL 创建自定义 DAO 实例(例如,使用简单类型和类型 A 的单个实例)。使用给定查询方法的方法确实出现在给定实体端点的搜索方法中。我想要更合理的东西,所以我想要projection as defined in spring data rest项目。

我的 DTO 对象完全在我的控制之下。我更喜欢使用 lombok 项目中的 @Value@Data 注释,但这不是必需的。我也尝试过这些版本的 DTO 定义(使用接口(interface)适用于简单数据,类似的类适用于简单数据)。

interface ACustomPage42DTO {
    String getName();
    long getCommentsCount();
    Object getAsdf();
}

或者使用具有一些额外功能的等效类,例如可能的自定义 toString() 方法,或者用于计算数据的一些自定义 getter:

@Value //lombok thing, imutable "POJO"
public class ACustomPage42DTO {
    String name;
    long commentsCount;
    Set<SimpleFile> simpleFiles;
    public ACustomPage42DTO(A a, long count) {
        // constructor used by JPQL, if it works
        name = a.getName();
        this.commentsCount = count;
        this.simpleFiles = a.getSimpleFiles(); // should be already fetched, due to fetch join in JPQL
    }
}

这两种工作方法都可以使用“搜索”url 来调用,而不是投影。我在 url http://localhost:9091/api/a/search 上看到我的方法 getByIdProjectedForScreen42 list 。我想像这样使用它(我认为这是“正确”的方式)http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .

最佳答案

问题相当广泛,涉及几个方面:

  • 使用 @Query 自定义 JPA 存储库方法
  • @Query 中选择结果
  • @Query结果映射到接口(interface)
  • 通过@RepositoryRestResource公开新的存储库方法

TLDR:写了一个关于几个基本测试所讨论内容的示例 https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http

使用@Query自定义JPA存储库方法

正如您所提到的,这非常简单,只需使用 @Query 注释一个方法,并确保您的返回类型与查询返回的内容相对应,例如:

public interface FooRepository extends JpaRepository<FooEntity, Long> {
    @Query(nativeQuery = true, value = "select f from foo f where f.name = :myParam")
    Optional<FooEntity> getInSomeAnotherWay(String myParam);
}

@Query 中选择结果

您已经给出了一个示例,但我将进行简化,使其更容易、更简短。

给定实体FooEntity.javaBarEntity.java:

@Entity
@Table(name = "foo")
public class FooEntity {

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @OneToMany(mappedBy = "foo")
    private Set<BarEntity> bars = new HashSet<>();

    // getter setters excluded for brevity
}

@Entity
@Table(name = "bar")
public class BarEntity {

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @ManyToOne(targetEntity = FooEntity.class)
    @JoinColumn(name = "foo_id", nullable = false, foreignKey = @ForeignKey(name = "fk_bar_foo"))
    private FooEntity foo;

    // getter setters excluded for brevity
}

我们现在想要返回包含 FooEntity.nameFooEntity.bars 数量的自定义结果集:

SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id


+-----------------+----------+
| name            | barCount |
+-----------------+----------+
| Jonny tables    | 1        |
+-----------------+----------+

@Query结果映射到接口(interface)

为了映射上面的结果集,我们需要一个接口(interface),其中 getter 可以很好地反射(reflect)所选择的内容:

public interface ProjectedFooResult {
    String getName();
    Long getBarCount();
}

现在我们可以将存储库方法重写为:

@Query(nativeQuery = true, 
    value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);

通过@RepositoryRestResource公开新的存储库方法

我对此不是很熟悉,但是在添加 org.springframework.data:spring-data-rest-hal-browser 依赖项后,我得到了这个很好的界面,它在存储库注释后公开了可用的方法@RepositoryRestResource。对于包含上述详细信息的给定存储库:

@RepositoryRestResource(path = "foo")
public interface FooRepository extends JpaRepository<FooEntity, Long> {
    @Query(nativeQuery = true, value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
    Optional<ProjectedFooResult> getByIdToProjected(Long id);
}

本地运行时,该方法将通过 http://localhost:8080/foo/search/getByIdToProjected?id=1 公开。

如上所述,引用实现位于 Github https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http

Additional helpful documentation for 'Custom Implementations for Spring Data Repositories'

关于java - 在 Spring Data Rest 项目中使用任意查询作为投影,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60622337/

相关文章:

java - 缓存数据库查询结果集需要好的设计模式

spring - 如何在 Spring 安全性中刷新 token

java - @OneToMany 映射 JPA 中的父 ID 为空

JPA EntityManager.detach() 仍然加载惰性关系

java - 如何在JPA中查找重复项?

java - 解压输入文件 Java 时 Ant 类路径错误

java - 如何从从 Facebook 检索的日期字符串中解析时间

java - 暂时强制 LazyInitializationExceptions

java - 在 Java 中创建带注释的对象时收到通知

java - 如何使用 java 配置配置 Spring ConversionService?