line / kotlin-jdsl

Kotlin library that makes it easy to build and execute queries without generated metamodel
https://kotlin-jdsl.gitbook.io/docs/
Apache License 2.0
651 stars 85 forks source link

[Question] AOP invocation IllegalStateException in CrudMethodMetadataPopulatingMethodInterceptor class #711

Closed kihwankim closed 1 month ago

kihwankim commented 1 month ago

Question

I wrote the code as below, but since CrudMethodMetadata(lockModeType, queryHints) were added in KotlinJdslJpqlExecutorImpl to version 3.4.0, the error as below has occurred. What should I do??

Error Example in 3.4.0 version

class CustomRepository(
   private val kotlinJdslJpqlExecutor: KotlinJdslJpqlExecutor
) {
  fun findByIdOrNull(id: Long) : TestEntity? {
    return kotlinJdslJpqlExecutor.select<TestEntity> {
       select(entity(TestEntity::class)).from(entity(TestEntity::class)).where(path(TestEntity::id).eq(id)).firstOrNull()
    }
  }
}

Reason

  1. the place error occur image

  2. reason the error occur the reason this error is that all functions in KotlinJdslJpqlExecutorImpl didn't added at implementationsvar

image

  1. only working way
interface BookRepository : JpaRepository<Book, Isbn>, KotlinJdslJpqlExecutor

the way I tried to fix(failed)

I tried to cache metadata from all interfaces of kotlinJdslJpqlExecutor through the JpaRepositoryFactoryBean.afterPropertieset() method, but failed.

package org.springframework.data.jpa.repository.support

import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor
import org.springframework.aop.framework.ProxyFactory
import org.springframework.data.repository.core.RepositoryInformation
import org.springframework.data.repository.core.support.JdslRepositoryInformationAdapter
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor

internal class CrudMethodMetadataRepositoryProxyPostProcessor(
    private val kotlinJdslJpqlExecutor: KotlinJdslJpqlExecutor
) : RepositoryProxyPostProcessor {
    override fun postProcess(factory: ProxyFactory, repositoryInformation: RepositoryInformation) {
        factory.addAdvice(
            CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor(
                JdslRepositoryInformationAdapter(kotlinJdslJpqlExecutor).repositoryInformation,
            ),
        )
    }
}

package org.springframework.data.repository.core.support

import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor
import org.springframework.data.repository.core.RepositoryInformation
import org.springframework.data.repository.core.RepositoryMetadata

internal class JdslRepositoryInformationAdapter(
    private val kotlinJdslJpqlExecutor: KotlinJdslJpqlExecutor
) {
    val repositoryInformation: RepositoryInformation = DefaultRepositoryInformation(
        createMetadata(kotlinJdslJpqlExecutor::class.java),
        kotlinJdslJpqlExecutor::class.java,
        createComposition(kotlinJdslJpqlExecutor::class.java),
    )

    private fun createComposition(repositoryBaseClass: Class<*>): RepositoryComposition {
        val fragments = RepositoryComposition.RepositoryFragments.just(kotlinJdslJpqlExecutor)
        val composition = RepositoryComposition.fromMetadata(createMetadata(repositoryBaseClass))
        val repositoryAspects = RepositoryComposition.RepositoryFragments.empty()
        return composition.append(fragments).append(repositoryAspects)
    }

    private fun createMetadata(repositoryBaseClass: Class<*>): RepositoryMetadata {
        return AbstractRepositoryMetadata.getMetadata(repositoryBaseClass)
    }
}

package com.linecorp.kotlinjdsl.support.spring.data.jpa.autoconfigure

import com.linecorp.kotlinjdsl.SinceJdsl
import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.BeanPostProcessor
import org.springframework.context.annotation.Lazy
import org.springframework.data.jpa.repository.support.CrudMethodMetadataRepositoryProxyPostProcessor
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
import org.springframework.stereotype.Component

@Component
@SinceJdsl("3.0.0")
open class KotlinJdslJpaRepositoryFactoryBeanPostProcessor : BeanPostProcessor {
    @Lazy
    @Autowired
    lateinit var kotlinJdslJpqlExecutor: KotlinJdslJpqlExecutor

    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
        if (bean is JpaRepositoryFactoryBean<*, *, *>) {
            bean.setCustomImplementation(kotlinJdslJpqlExecutor)
            bean.addRepositoryFactoryCustomizer { factory ->
                factory.addRepositoryProxyPostProcessor(
                    CrudMethodMetadataRepositoryProxyPostProcessor(kotlinJdslJpqlExecutor),
                )
            }
        }

        return super.postProcessAfterInitialization(bean, beanName)
    }
}
shouwn commented 1 month ago

Supporting CrudMethodMetadata breaks backwards compatibility, I should have paid more attention to that.

When I checked the code, supporting CrudMethodMetadata forced its use in Repository, and since KotlinJdslJpqlExecutor was originally created for use in Repository, there are no plans to support using KotlinJdslJpqlExecutor alone.

So if you want to use KotlinJdslJpqlExecutor, I recommend that you create a repository and use it.

Personally, I recommend using EntityManager directly. Because you can set limits, lockmode, etc. much more freely, and it's not dependent on Spring, so it's much easier to understand the library, and it doesn't add so much code compared to KotlinJdslJpqlExecutor.

shouwn commented 1 month ago

I will close this issue. If you have any additional questions, please reopen it.