使用Spring Data JPA的"按示例查询"的Kotlinic模式

Bir*_*abs 10 spring-data-jpa kotlin

Spring Data JPA引入了一个很好的功能,"按示例查询"(QBE).您可以通过构建实体实例来表达搜索条件.

您不必编写JPQL.与使用存储库查询派生相比,它使用的"魔法"更少.语法很好.它可以防止普通存储库代码的爆炸.它很好地存在于重构中.

但是有一个问题:QBE只有在你可以部分构造一个对象时才有效.

这是我的实体:

@Entity
@Table(name="product")
data class Product(
        @Id val id: String,
        val city: String,
        val shopName: String,
        val productName: String,
        val productVersion: Short
)
Run Code Online (Sandbox Code Playgroud)

这是我的存储库(空!这是关于QBE的一件好事):

@Repository
interface ProductRepository : JpaRepository<Product, String>
Run Code Online (Sandbox Code Playgroud)

以下是您将如何获取List<Product>- 在某些城市的某些商店销售的所有产品:

productRepository.findAll(Example.of(Product(city = "London", shopName="OkayTea")))
Run Code Online (Sandbox Code Playgroud)

或者至少,这就是我想要做的.有一个问题.这是不可能的构造此对象:

Product(city = "London", shopName="OkayTea")
Run Code Online (Sandbox Code Playgroud)

这是因为Product构造函数要求定义其所有字段.事实上:这就是我大多数时候都想要的.

Java中通常的折衷方案是:使用no-args构造函数构造实体,使所有内容都可变,不保证完成性.

是否有一个很好的Kotlin模式来解决这个问题:

  • 通常要求所有args在构造时实例化
  • 提供了一些机制来生成部分构造的实例以与Example API一起使用

不可否认,这些目标看起来完全相互矛盾.但也许有另一种方法来解决这个问题?

例如:也许我们可以制作一个模拟/代理对象,它似乎是一个产品,但没有相同的构造约束?

r6q*_*r6q 5

您可以使用具有非空字段的 kotlin 数据类通过示例进行查询,但它看起来不如 java 代码。

val matcher = ExampleMatcher.matching()
    .withMatcher("city", ExampleMatcher.GenericPropertyMatcher().exact())
    .withMatcher("shopName", ExampleMatcher.GenericPropertyMatcher().exact())
    .withIgnorePaths("id", "productName", "productVersion")

val product = Product(
    id = "",
    city = "London",
    shopName = "OkayTea",
    productName = "",
    productVersion = 0
)

productRepository.findAll(Example.of(product, matcher))
Run Code Online (Sandbox Code Playgroud)

如果您将它用于集成测试并且您不想使用Repository仅在所述测试中使用的方法污染您的接口并且数据库实体类中有很多字段,您可以创建一个提取字段的扩展函数这将在查询中被忽略。

private fun <T : Any> KClass<T>.ignoredProperties(vararg exclusions: String): Array<String> {
    return declaredMemberProperties
        .filterNot { exclusions.contains(it.name) }
        .map { it.name }
        .toTypedArray()
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

val ignoredFields = Product::class.ignoredProperties("city", "shopName")

val matcher = ExampleMatcher.matching()
    .withMatcher("city", ExampleMatcher.GenericPropertyMatcher().exact())
    .withMatcher("shopName", ExampleMatcher.GenericPropertyMatcher().exact())
    .withIgnorePaths(*ignoredFields)

Run Code Online (Sandbox Code Playgroud)


hol*_*ava -1

因为主构造函数上的参数不是可选的,也不能为空。您可以使参数可为空并为每个参数设置默认值null,例如:

@Entity
@Table(name = "product")
data class Product(
        @Id val id: String? = null,
        val city: String? = null,
        val shopName: String? = null,
        val productName: String? = null,
        val productVersion: Short? = null
)
Run Code Online (Sandbox Code Playgroud)

但是,您必须使用Productsafe-call 操作属性?.,例如:

val product = Product()

//                   safe-call ---v
val cityToLowerCase = product.city?.toLowerCase()
Run Code Online (Sandbox Code Playgroud)

  • 虽然它是一个解决方案,但它并没有回答问题。@Birchlabs 明确表示它应该“通常要求所有参数在构造时实例化”,甚至在他的问题中认识到这个解决方案,认为这是不可取的。 (5认同)
  • 谢谢; 所以我们放弃所有的完整性保证。但这是一个很大的妥协:我们显着改变了我们的完整性合同,只是为了支持 QBE。我在想也许还有另一种方法——例如使用模拟或代理——来生成“Product”实例。模拟/代理将满足示例 API 的要求,并且_不_具有 Product 类的构造函数约束。 (2认同)