在 spring 数据休息项目中使用任意查询作为投影
Using arbitrary query as projection in spring data rest project
如何在某些存储库中使用任意 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 界面。我试图用一些 projection/view 来丰富它,特别是由于网络 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,但是我在获取连接时遇到了问题(因为我不熟悉 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
类型的单个实例)。使用给定查询方法的方法确实出现在给定实体端点的搜索方法中。我想要更合理的东西,所以我想要projection as defined in spring data rest项目。
我的 DTO 对象完全在我的控制之下。我更喜欢它使用项目 lombok 中的 @Value
或 @Data
注释,但这不是必需的。我也尝试过这些版本的 DTO 定义(使用接口适用于简单数据,类似地 class 适用于简单数据)。
interface ACustomPage42DTO {
String getName();
long getCommentsCount();
Object getAsdf();
}
或者使用等价的 class 和一些奖励,比如自定义 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
}
}
可以使用 "search" url 而不是投影来调用这两种工作方法。我在 url http://localhost:9091/api/a/search listing. I would like to use it like (I think that is the "right" way) http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .
上看到了我的方法 getByIdProjectedForScreen42
问题很广泛,涉及几个方面:
- 自定义 JPA 存储库方法使用
@Query
- 在您的
@Query
中选择结果
- 映射
@Query
结果到接口
- 通过
@RepositoryRestResource
公开新的存储库方法
TLDR:写了一个例子,说明了几个基本测试所讨论的内容 https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
使用 @Query
的自定义 JPA 存储库方法
正如您所提到的,它非常简单,只需使用 @Query
注释方法并确保您的 return 类型对应于查询中 returned 的内容,例如:
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.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
}
我们现在想要 return 自定义结果集,其中包含 FooEntity.name
和 FooEntity.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
结果到接口
要在结果集上方进行映射,我们需要一个接口,其中 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);
通过 @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'
如何在某些存储库中使用任意 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 界面。我试图用一些 projection/view 来丰富它,特别是由于网络 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,但是我在获取连接时遇到了问题(因为我不熟悉 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
类型的单个实例)。使用给定查询方法的方法确实出现在给定实体端点的搜索方法中。我想要更合理的东西,所以我想要projection as defined in spring data rest项目。
我的 DTO 对象完全在我的控制之下。我更喜欢它使用项目 lombok 中的 @Value
或 @Data
注释,但这不是必需的。我也尝试过这些版本的 DTO 定义(使用接口适用于简单数据,类似地 class 适用于简单数据)。
interface ACustomPage42DTO {
String getName();
long getCommentsCount();
Object getAsdf();
}
或者使用等价的 class 和一些奖励,比如自定义 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
}
}
可以使用 "search" url 而不是投影来调用这两种工作方法。我在 url http://localhost:9091/api/a/search listing. I would like to use it like (I think that is the "right" way) http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .
上看到了我的方法getByIdProjectedForScreen42
问题很广泛,涉及几个方面:
- 自定义 JPA 存储库方法使用
@Query
- 在您的
@Query
中选择结果
- 映射
@Query
结果到接口 - 通过
@RepositoryRestResource
公开新的存储库方法
TLDR:写了一个例子,说明了几个基本测试所讨论的内容 https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
使用 @Query
的自定义 JPA 存储库方法
正如您所提到的,它非常简单,只需使用 @Query
注释方法并确保您的 return 类型对应于查询中 returned 的内容,例如:
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.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
}
我们现在想要 return 自定义结果集,其中包含 FooEntity.name
和 FooEntity.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
结果到接口
要在结果集上方进行映射,我们需要一个接口,其中 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);
通过 @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'