如何在Spring Data存储库接口中使用Kotlin默认方法?

时间:2018-03-09 09:03:16

标签: java kotlin spring-data

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

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!

如何在Spring Data存储库接口中使用Kotlin默认方法并阻止PropertyReferenceException

4 个答案:

答案 0 :(得分:6)

TL; DR

Kotlin 1.1 / 1.2首先编译抽象接口方法的默认方法。在Spring Data存储库接口中使用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 Data存储库可以使用下面的代理。存储库中的每个方法都必须是:

  1. 由特定于商店的存储库实现。
  2. 由自定义实现实现。
  3. Java 8默认方法。
  4. 使用查询注释进行注释。
  5. 调整方法命名方案以允许查询派生。
  6. 在这种情况下,根据您实现Java接口的方式,任何自定义代码都不会实现search(…)。 Spring Data尝试派生查询并将search(…)视为User域类的属性。查找失败并抛出PropertyReferenceException

    这是一个已知的限制。

    参考

答案 1 :(得分:2)

正如本指出,你现在可以(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上进行了测试,它确实有效。

答案 2 :(得分:2)

FWIW Kotlin extension methods 在这里非常适合我,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`
}

答案 3 :(得分:1)

最近发布的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)
}