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

7cjasjjr  于 2023-10-23  发布在  Kotlin
关注(0)|答案(5)|浏览(140)

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

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 repository接口的Kotlin默认方法并防止PropertyReferenceException

iyzzxitl

iyzzxitl1#

TL;DR

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

说明

Kotlin允许使用Java运行时版本1.6的默认接口方法。JVM级别的默认接口方法在Java 1.8中引入。这导致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 repositories使用下面的代理。仓库中的每个方法必须是:
1.由特定于存储的存储库实现。
1.由自定义实现实现。

  1. Java 8默认方法。
    1.使用查询注解进行注解。
    1.调整方法命名方案以允许查询派生。
    在本例中,search(…)没有根据您如何实现Java接口而由任何自定义代码实现。Spring Data尝试派生查询并将search(…)视为User域类的属性。测试失败并抛出PropertyReferenceException
    这是已知的限制。

参考资料

hts6caw3

hts6caw32#

正如Ben所指出的,你现在可以(Kotlin1.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']
    }
}

我刚刚在Kotlin1.2.41上测试了它,它工作正常。

h43kikqp

h43kikqp3#

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`
}
rkue9o1l

rkue9o1l4#

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

bweufnob5#

@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以上的接口。

相关问题