当前位置: 首页 > 知识库问答 >
问题:

在Spring data rest项目中使用任意查询作为投影

包唯
2023-03-14

如何在某个存储库中使用任意sql查询(我指的是本机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接口。我试图通过一些投影/视图来丰富它,特别是由于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:实现接口并返回简单(原始)数据作品。还使用JPQL创建自定义DAO实例作品(例如简单类型和A类型的单个实例)。使用给定查询方法的方法确实出现在给定实体endpoint的搜索方法中。我想有更合理的东西,所以我想有Spring data rest项目中定义的投影。

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

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上看到了我的方法getByIdProjectedForScreen42http://localhost:9091/api/a/search表册我想这样使用它(我认为这是“正确的”方式)http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .

共有1个答案

干京
2023-03-14

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

  • 使用查询自定义JPA存储库方法
  • 在查询中选择结果
  • 将查询结果映射到接口
  • 通过RepositoryRestResource公开新的存储库方法

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

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

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

您已经给出了一个示例,但我将简化以使其更简单、更简短。

给定实体ntity.java和BarEntity.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        |
+-----------------+----------+

要映射上述结果集,我们需要一个接口,其中getter可以很好地反映所选内容:

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

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

@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

有关“Spring数据存储库的自定义实现”的其他有用文档

 类似资料:
  • 问题内容: 我认为,对于上述查询,我​​只能将行插入到的值为的位置。但是,我能够插入除for以外的值。 问题: 为什么我们需要像上面这样的查询?使用它的现实生活场景是什么? 问题答案: 它基本上是一种允许更新视图的结构。对于多表方案,只能对基础表之一进行INSERT。视图和要插入的表之间必须存在一对一的关系。 您显示的查询是一个内联视图,扩展了相同的概念。 在这里阅读更多文档 http://doc

  • 问题内容: 因此,这不适用于mysql_query。 我严格使用c ++,并且我没有使用php。 我希望执行此双重查询,以便在由并发用户创建ID的交易系统中,我将始终具有唯一ID。 它可以完美地在MySql数据库中运行,但是我需要将其添加到Eclipse(我正在使用Ubuntu 12.04 LTS)。 我的应用程序很大,如果可能的话,我不想更改为mysqli,但是如果没有其他方法,那就可以了。 你

  • 问题内容: 您好,我想按查询排除某些字段。我正在使用nodejs 但在结果集中,我一直在获取密码字段。 问题答案: 投影不适用于新的nodejs mongodb驱动程序…相反,您将不得不在 此处使用游标方法

  • 我有两个表,用户和角色,它们是多对多的 我正在使用EntityManager进行查询。 我如何使JPA知道映射一个用户和多个角色?

  • 我有一个这样的endpoint: 在这里,是向用户公布的关键字。但是和是任意参数。对于另一个资源,如,任意参数可以是。 在 openapi 规范中,我有 我正在使用代码生成插件 ,在服务器控制器中,我想捕获所有任意参数。如何扩展我的 openapi 规范以使参数超出。如果有一种方法可以在单个数组中获取所有查询参数,那也将有所帮助。