如何将 Kotlin 默认方法与 Spring Data 存储库接口一起使用?

问题描述 投票:0回答:5

考虑以下存储库接口声明:

interface KotlinUserRepository : Repository<User, String> {

  fun findById(username: String): User

  fun search(username: String) = findById(username)
}

我正在声明一个默认接口方法

search(…)
,默认调用
findById(…)

启动我的应用程序失败并显示:

org.springframework.data.mapping.PropertyReferenceException: No property Search found for type User!

如何将 Kotlin 默认方法与 Spring Data 存储库接口结合使用并防止

PropertyReferenceException

java kotlin spring-data
5个回答
18
投票

TL;博士

Kotlin 1.1/1.2 首先将默认方法编译为抽象接口方法。无法在 Spring 数据存储库接口中使用 Kotlin 的默认方法。

说明

Kotlin 允许使用 Java 运行时版本 1.6 的默认接口方法。 Java 1.8 引入了 JVM 级默认接口方法。这导致 Kotlin 使用与 Java 不同的方法来编译默认接口方法。

KotlinUserRepository
中的代码编译为:

interface KotlinUserRepository extends Repository {

  User findById(String username);

  User search(String username);

  @Metadata(…)
  public static final class DefaultImpls {

    public static User search(KotlinUserRepository $this, String username) {
      Intrinsics.checkParameterIsNotNull(username, "username");
      return $this.findById(username);
    }
  }
}

方法

search(…)
编译为抽象接口方法。实现位编译为类
DefaultImpls
,它反映了默认方法签名。想要实现
KotlinUserRepository
的类需要实现
search(…)
。在纯 Kotlin 环境中使用该接口将使 Kotlin 编译器创建实现位。

Spring 数据存储库与底层代理一起使用。存储库中的每个方法都必须是:

  1. 由商店特定存储库实现。
  2. 由自定义实现实现。
  3. Java 8 默认方法。
  4. 使用查询注释进行注释。
  5. 适合方法命名方案以允许查询派生。

在这种情况下,

search(…)
不会根据您实现 Java 接口的方式由任何自定义代码实现。 Spring Data 尝试派生查询并将
search(…)
视为
User
域类的属性。查找失败并抛出
PropertyReferenceException

这是一个已知的限制。

参考文献


11
投票

正如 Ben 指出的,您现在(Kotlin 1.2.40+)可以使用

@JvmDefault

interface BatchRepository : PagingAndSortingRepository<Batch, Long> {
    fun getAllByOrderByPriorityAscNameAsc(): List<Batch>

    @JvmDefault
    fun getForAdmin() = getAllByOrderByPriorityAscNameAsc()
}

您需要使用如下方式在 build.gradle 中启用该选项:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs = ['-Xenable-jvm-default']
    }
}

我刚刚在 Kotlin 1.2.41 上测试了它,它可以工作。


5
投票

FWIW Kotlin 扩展方法 在这里对我来说效果很好,1 个 .kt 文件:

interface FooRepository : JpaRepository<FooDb, UUID>

object FooRepositoryExtensions {

  fun FooRepository.doFoo(something: String): FooDb { 
      // do whatever you want here, the 'FooRepository' instance is available via `this`
}

2
投票

最近发布的 Kotlin 1.2.40 现在支持一项实验性功能,可以通过

@JvmDefault
注释和设置功能标志将 Kotlin 默认方法编译为 Java 8 默认方法:
Xenable-jvm-default

https://blog.jetbrains.com/kotlin/2018/04/kotlin-1-2-40-is-out/#more-5922

我还没有尝试过,但理论上你的例子应该像这样工作:

interface KotlinUserRepository : Repository<User, String> {

  fun findById(username: String): User

  @JvmDefault
  fun search(username: String) = findById(username)
}

0
投票

@JvmDefault
现已弃用。工作正常:

build.gradle.kts
  kotlinOptions {
    freeCompilerArgs += listOf("-Xjsr305=strict", "-Xjvm-default=all")
  }

@JvmDefaultWithCompatibility
interface KotlinRepository<User> : JpaRepository<User, Int?> {

   fun getExisted(id: Int): User = findById(id).orElseThrow { 
              NotFoundException("Entity with id=$id not found") }

您需要接口上方的jvm选项

-Xjvm-default=all
和注释
@JvmDefaultWithCompatibility

© www.soinside.com 2019 - 2024. All rights reserved.