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

时间:2017-07-10 10:56:21

标签: spring-data-jpa kotlin

Spring Data JPA引入了一个很好的功能"query by example" (QBE)。您可以通过构建实体的实例来表达您的搜索条件。

您不必编写JPQL。它使用的“魔法”比repository query derivation少。语法很好。它可以防止普通存储库代码的爆炸。它很好地存在于重构中。

但是有一个问题:QBE只有在部分构建对象时才有效。

这是我的实体:

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

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

@Repository
interface ProductRepository : JpaRepository<Product, String>

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

productRepository.findAll(Example.of(Product(city = "London", shopName="OkayTea")))

或者至少,这就是我想要做的事情。有一个问题。构建此对象不可能

Product(city = "London", shopName="OkayTea")

这是因为Product的构造函数要求 all 定义其字段。事实上:这就是我大部分时间都想要的。

Java中通常的妥协是:使用no-args构造函数构造实体,使所有内容都可变,不保证完成。

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

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

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

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

2 个答案:

答案 0 :(得分:3)

您可以使用带有非空字段的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))

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

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

并像这样使用它:

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

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

答案 1 :(得分: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
)

但是,您必须使用安全召唤Product运营?.个属性,例如:

val product = Product()

//                   safe-call ---v
val cityToLowerCase = product.city?.toLowerCase()