From 96d3798acef1c806ea8b2faa1e3cfcec0274e664 Mon Sep 17 00:00:00 2001 From: Tigran Manasyan Date: Mon, 5 Apr 2021 03:18:03 +0700 Subject: [PATCH 1/2] Global refactor --- library/build.gradle.kts | 2 + .../kcache/aspect/KCacheEvictAspect.kt | 16 ++-- .../kcache/aspect/KCacheableAspect.kt | 73 ++++++++++++++----- .../strategy/GeneratedMetadataStrategy.kt | 37 ---------- .../strategy/KCacheableAspectStrategy.kt | 18 ----- .../strategy/ReflectionMetadataStrategy.kt | 29 -------- .../EntitiesToListenBeanPostProcessor.kt | 34 +++++++++ .../kcache/config/KCacheAutoConfiguration.kt | 12 +-- .../AspectStrategyConfiguration.kt | 31 -------- .../GeneratedMetadataStrategyConfiguration.kt | 59 --------------- ...tRequestStatesMappingsBeanPostProcessor.kt | 33 --------- .../kcache/core/annotations/KCacheable.kt | 5 +- .../kcache/core/annotations/KCacheableJpa.kt | 13 ++++ .../core/jpa/EntitiesToListenContainer.kt | 7 ++ .../core/state/holder/HazelcastStateHolder.kt | 2 +- .../example/app/controller/TestController.kt | 5 +- .../src/main/resources/application.yaml | 2 +- 17 files changed, 133 insertions(+), 245 deletions(-) delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/GeneratedMetadataStrategy.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/KCacheableAspectStrategy.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/ReflectionMetadataStrategy.kt create mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/AspectStrategyConfiguration.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/GeneratedMetadataStrategyConfiguration.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/InjectRequestStatesMappingsBeanPostProcessor.kt create mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheableJpa.kt create mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ce2aef1..96f6d2e 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -13,6 +13,8 @@ dependencies { implementation("org.redisson:redisson:3.15.1") implementation("com.hazelcast:hazelcast:4.1.1") +// implementation("org.reflections:reflections:0.9.11") + implementation("io.github.classgraph:classgraph:4.8.102") testImplementation("org.springframework.boot:spring-boot-starter-test") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheEvictAspect.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheEvictAspect.kt index ebe329e..4606fd8 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheEvictAspect.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheEvictAspect.kt @@ -3,7 +3,6 @@ package ru.nsu.manasyan.kcache.aspect import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.After import org.aspectj.lang.annotation.Aspect -import org.aspectj.lang.reflect.MethodSignature import ru.nsu.manasyan.kcache.core.annotations.KCacheEvict import ru.nsu.manasyan.kcache.core.state.holdermanager.StateHolderManager import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider @@ -20,17 +19,16 @@ class KCacheEvictAspect( * Processes section of code, which changes the state of the DB. * Updates state of each DB table, listed in the tables field of [KCacheEvict] */ - @After("@annotation(ru.nsu.manasyan.kcache.core.annotations.KCacheEvict)") - fun wrapKCacheEvictMethod(joinPoint: JoinPoint) { - val method = (joinPoint.signature as MethodSignature).method - // we know, that method has KCacheEvict annotation - // TODO: mb get tables from generated container as well as in KCacheable - val annotation = getAnnotationInstance(method)!! - annotation.tables.forEach { tableName -> + @After("@annotation(kCacheEvict)") + fun wrapKCacheEvictMethod( + joinPoint: JoinPoint, + kCacheEvict: KCacheEvict + ) { + kCacheEvict.tables.forEach { tableName -> val newState = newStateProvider.provide(tableName) stateHolderManager .getOrCreateStateHolder(tableName) - .setState(annotation.key, newState) + .setState(kCacheEvict.key, newState) logger.debug("Table $tableName was updated: $newState") } } diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt index c69bdce..5631484 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt @@ -10,20 +10,26 @@ import org.springframework.expression.spel.support.StandardEvaluationContext import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestHeader -import ru.nsu.manasyan.kcache.aspect.strategy.KCacheableAspectStrategy import ru.nsu.manasyan.kcache.core.annotations.KCacheable +import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa import ru.nsu.manasyan.kcache.core.etag.builder.ETagBuilder import ru.nsu.manasyan.kcache.core.etag.extractor.IfNoneMatchHeaderExtractor +import ru.nsu.manasyan.kcache.core.resultbuilder.ResultBuilderFactory import ru.nsu.manasyan.kcache.util.LoggerProperty import ru.nsu.manasyan.kcache.util.ifDebug +import kotlin.reflect.full.createInstance @Aspect -class KCacheableAspect( +open class KCacheableAspect( private val eTagBuilder: ETagBuilder, private val headerExtractor: IfNoneMatchHeaderExtractor, - private val strategy: KCacheableAspectStrategy, private val expressionParser: ExpressionParser ) { + private companion object { + private const val SPEL_PREFIX = "#" + private const val SPEL_CONTEXT_ARGS_KEY = "args" + } + private val logger by LoggerProperty() /** @@ -38,14 +44,40 @@ class KCacheableAspect( * then the wrapped method is called and its result ([ResponseEntity]) is returned * with the ETag header set to the current ETag value. */ - @Around("@annotation(ru.nsu.manasyan.kcache.core.annotations.KCacheable)") - fun wrapKCacheableControllerMethod(joinPoint: ProceedingJoinPoint): Any? { + @Around("@annotation(kCacheable)") + open fun wrapKCacheableMethod( + joinPoint: ProceedingJoinPoint, + kCacheable: KCacheable, + ): Any? = handle( + joinPoint, + kCacheable.tables.toList(), + kCacheable.key, + kCacheable.resultBuilderFactory.createInstance() + ) + + @Around("@annotation(kCacheableJpa)") + open fun wrapKCacheableJpaMethod( + joinPoint: ProceedingJoinPoint, + kCacheableJpa: KCacheableJpa, + ): Any? = handle( + joinPoint, + kCacheableJpa.entities.map { it.toString() }, + "", + kCacheableJpa.resultBuilderFactory.createInstance() + ) + + private fun handle( + joinPoint: ProceedingJoinPoint, + tables: List, + key: String, + resultBuilderFactory: ResultBuilderFactory + ): Any? { val methodSignature = joinPoint.signature as MethodSignature - strategy.methodSignature = methodSignature + val methodArgs = joinPoint.args val currentETag = eTagBuilder.buildETag( - strategy.getTableStates(), - getKey(strategy.getKeyExpression(), joinPoint.args) + tables, + getKey(key, methodArgs) ) val previousETag = headerExtractor.extract( @@ -53,14 +85,12 @@ class KCacheableAspect( joinPoint.args ) - val factory = strategy.getResultBuilderFactory() if (currentETag == previousETag) { logger.ifDebug( "Equal ETags for method ${methodSignature.getMethodName()}: " + "$currentETag, returning 304" ) - return factory.getOnHitResultBuilder() - .build(currentETag) + return resultBuilderFactory.getOnHitResultBuilder().build(currentETag) } logger.ifDebug( @@ -68,19 +98,24 @@ class KCacheableAspect( "Current [$currentETag] Previous[$previousETag]. Invoking method." ) - return factory.getOnMissResultBuilder() - .build(joinPoint.proceed(), currentETag) + return resultBuilderFactory.getOnMissResultBuilder().build( + joinPoint.proceed(), + currentETag + ) } - private fun getKey(keyExpression: String, parameters: Array): String { - if (!keyExpression.startsWith("#")) { + private fun getKey(keyExpression: String, args: Array): String { + if (!keyExpression.startsWith(SPEL_PREFIX)) { return keyExpression } - val expression = expressionParser.parseExpression(keyExpression) val context: EvaluationContext = StandardEvaluationContext().apply { - setVariable("params", parameters) + setVariable(SPEL_CONTEXT_ARGS_KEY, args) } - return expression.getValue(context)?.toString() + return expressionParser + .parseExpression(keyExpression) + .getValue(context) + ?.toString() ?: throw IllegalArgumentException("Key should not be null") } -} \ No newline at end of file +} + diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/GeneratedMetadataStrategy.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/GeneratedMetadataStrategy.kt deleted file mode 100644 index c797663..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/GeneratedMetadataStrategy.kt +++ /dev/null @@ -1,37 +0,0 @@ -package ru.nsu.manasyan.kcache.aspect.strategy - -import org.aspectj.lang.reflect.MethodSignature -import ru.nsu.manasyan.kcache.aspect.getMetadata -import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadata -import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadataContainer -import kotlin.reflect.full.createInstance - -class GeneratedMetadataStrategy( - private val requestHandlerMetadataContainer: RequestHandlerMetadataContainer -) : KCacheableAspectStrategy { - - private lateinit var metadata: RequestHandlerMetadata - - override var methodSignature: MethodSignature? = null - set(value) { - field = value - metadata = requestHandlerMetadataContainer.getMetadata(value!!) - } - - override fun getTableStates() = runIfInitialized { - metadata.tableStates - .ifEmpty { - throw IllegalArgumentException("KCacheable annotation should contain at list 1 table") - } - } - - // TODO: mb refactor (store class objects in BuilderLocator (map )) or add cache - override fun getResultBuilderFactory() = runIfInitialized { - metadata.resultBuilderFactory - .createInstance() - } - - override fun getKeyExpression() = runIfInitialized { - metadata.key - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/KCacheableAspectStrategy.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/KCacheableAspectStrategy.kt deleted file mode 100644 index ad3f11a..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/KCacheableAspectStrategy.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ru.nsu.manasyan.kcache.aspect.strategy - -import org.aspectj.lang.reflect.MethodSignature -import ru.nsu.manasyan.kcache.core.resultbuilder.ResultBuilderFactory - -interface KCacheableAspectStrategy { - var methodSignature: MethodSignature? - - fun getTableStates(): List - - fun getResultBuilderFactory(): ResultBuilderFactory - - fun getKeyExpression(): String - - fun runIfInitialized(action: () -> R): R = methodSignature?.let { - action() - } ?: throw IllegalStateException("First initiate method signature") -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/ReflectionMetadataStrategy.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/ReflectionMetadataStrategy.kt deleted file mode 100644 index 71a24f4..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/strategy/ReflectionMetadataStrategy.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ru.nsu.manasyan.kcache.aspect.strategy - -import org.aspectj.lang.reflect.MethodSignature -import ru.nsu.manasyan.kcache.core.annotations.KCacheable -import kotlin.reflect.full.createInstance - -class ReflectionMetadataStrategy : KCacheableAspectStrategy { - private lateinit var kCacheable: KCacheable - - override var methodSignature: MethodSignature? = null - set(value) { - field = value - kCacheable = value - ?.method - ?.getAnnotation(KCacheable::class.java)!! - } - - override fun getTableStates(): List = runIfInitialized { - kCacheable.tables.toList() - } - - override fun getResultBuilderFactory() = runIfInitialized { - kCacheable.resultBuilderFactory.createInstance() - } - - override fun getKeyExpression() = runIfInitialized { - kCacheable.key - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt new file mode 100644 index 0000000..b1c81c1 --- /dev/null +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt @@ -0,0 +1,34 @@ +package ru.nsu.manasyan.kcache.config + +import io.github.classgraph.ClassGraph +import org.springframework.beans.factory.config.BeanPostProcessor +import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa +import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadataContainer +import ru.nsu.manasyan.kcache.core.state.holder.StateHolder + +/** + * BeanPostProcessor, which injects initial states of all table states from [RequestHandlerMetadataContainer] to [StateHolder]. + */ +class EntitiesToListenBeanPostProcessor() : BeanPostProcessor { + override fun postProcessAfterInitialization(bean: Any, beanName: String): Any { + if (beanName != "entitiesToListenContainer") { + return bean + } + ClassGraph() + .enableAnnotationInfo() + .enableMethodInfo() + .scan() + .getClassesWithMethodAnnotation(KCacheableJpa::class.qualifiedName) + .forEach { + it.methodInfo.forEach { method -> + println( + method.getAnnotationInfo(KCacheableJpa::class.qualifiedName) + ?.parameterValues + ?.get("entities") + ?.value?.javaClass + ) + } + } + return bean + } +} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt index c32eb9f..6f9e562 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt @@ -9,12 +9,11 @@ import org.springframework.expression.ExpressionParser import org.springframework.expression.spel.standard.SpelExpressionParser import ru.nsu.manasyan.kcache.aspect.KCacheEvictAspect import ru.nsu.manasyan.kcache.aspect.KCacheableAspect -import ru.nsu.manasyan.kcache.aspect.strategy.KCacheableAspectStrategy -import ru.nsu.manasyan.kcache.config.aspectstrategy.AspectStrategyConfiguration import ru.nsu.manasyan.kcache.config.stateholdermanager.StateHolderConfiguration import ru.nsu.manasyan.kcache.core.etag.builder.ConcatenateETagBuilder import ru.nsu.manasyan.kcache.core.etag.builder.ETagBuilder import ru.nsu.manasyan.kcache.core.etag.extractor.IfNoneMatchHeaderExtractor +import ru.nsu.manasyan.kcache.core.jpa.EntitiesToListenContainer import ru.nsu.manasyan.kcache.core.state.holder.StateHolder import ru.nsu.manasyan.kcache.core.state.holdermanager.StateHolderManager import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider @@ -30,7 +29,6 @@ import ru.nsu.manasyan.kcache.util.LoggerProperty StateHolderConfiguration::class, ETagExtractorConfiguration::class, StateProviderConfiguration::class, - AspectStrategyConfiguration::class ] ) @EnableConfigurationProperties(KCacheProperties::class) @@ -60,18 +58,22 @@ class KCacheAutoConfiguration { fun kCacheAspect( eTagBuilder: ETagBuilder, extractor: IfNoneMatchHeaderExtractor, - strategy: KCacheableAspectStrategy, expressionParser: ExpressionParser ): KCacheableAspect { logger.debug("Building KCacheAspect") return KCacheableAspect( eTagBuilder, extractor, - strategy, expressionParser ) } + @Bean + fun entitiesToListenContainer() = EntitiesToListenContainer() + + @Bean + fun entitiesToListenContainerBpp() = EntitiesToListenBeanPostProcessor() + /** * Creates [KCacheEvictAspect] bean if * there are [StateHolder] and [ETagBuilder] beans in context diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/AspectStrategyConfiguration.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/AspectStrategyConfiguration.kt deleted file mode 100644 index 7ac69c2..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/AspectStrategyConfiguration.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ru.nsu.manasyan.kcache.config.aspectstrategy - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import -import org.springframework.context.annotation.ScopedProxyMode -import org.springframework.web.context.annotation.RequestScope -import ru.nsu.manasyan.kcache.aspect.strategy.KCacheableAspectStrategy -import ru.nsu.manasyan.kcache.aspect.strategy.ReflectionMetadataStrategy -import ru.nsu.manasyan.kcache.properties.KCacheProperties -import ru.nsu.manasyan.kcache.util.LoggerProperty - -@Import(GeneratedMetadataStrategyConfiguration::class) -@Configuration -class AspectStrategyConfiguration { - private val logger by LoggerProperty() - - @RequestScope(proxyMode = ScopedProxyMode.INTERFACES) - @Bean - @ConditionalOnProperty( - prefix = KCacheProperties.propertiesPrefix, - name = ["aspect.strategy"], - havingValue = "reflection-metadata", - matchIfMissing = true - ) - fun kCacheAspectStrategy(): KCacheableAspectStrategy { - logger.debug("Building ReflectionMetadataStrategy") - return ReflectionMetadataStrategy() - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/GeneratedMetadataStrategyConfiguration.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/GeneratedMetadataStrategyConfiguration.kt deleted file mode 100644 index 187e16f..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/GeneratedMetadataStrategyConfiguration.kt +++ /dev/null @@ -1,59 +0,0 @@ -package ru.nsu.manasyan.kcache.config.aspectstrategy - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.ScopedProxyMode -import org.springframework.web.context.annotation.RequestScope -import ru.nsu.manasyan.kcache.aspect.strategy.GeneratedMetadataStrategy -import ru.nsu.manasyan.kcache.aspect.strategy.KCacheableAspectStrategy -import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadataContainer -import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider -import ru.nsu.manasyan.kcache.properties.KCacheProperties -import ru.nsu.manasyan.kcache.util.LoggerProperty - -@Configuration -@ConditionalOnProperty( - prefix = KCacheProperties.propertiesPrefix, - name = ["aspect.strategy"], - havingValue = "generated-metadata" -) -class GeneratedMetadataStrategyConfiguration { - private val logger by LoggerProperty() - - /** - * Creates [RequestHandlerMetadataContainer] bean. - * Instance was obtained by deserialization from file, generated by KCacheable annotation processor. - */ - // TODO: change docs - @Bean - fun requestHandlerMetadataContainer(): RequestHandlerMetadataContainer { - return Class.forName(RequestHandlerMetadataContainer.GENERATED_METADATA_CLASS_NAME) - .getDeclaredConstructor() - .newInstance() as RequestHandlerMetadataContainer - } - - @RequestScope(proxyMode = ScopedProxyMode.INTERFACES) - @Bean - fun kCacheAspectStrategy( - container: RequestHandlerMetadataContainer - ): KCacheableAspectStrategy { - logger.debug("Building GeneratedMetadataStrategy") - return GeneratedMetadataStrategy(container) - } - - /** - * Creates [InjectRequestStatesMappingsBeanPostProcessor] bean - */ - @Bean - fun injectStatesBeanPostProcessor( - requestHandlerMetadataContainer: RequestHandlerMetadataContainer, - newStateProvider: NewStateProvider - ): InjectRequestStatesMappingsBeanPostProcessor { - logger.debug("Building InjectRequestStatesMappingsBeanPostProcessor") - return InjectRequestStatesMappingsBeanPostProcessor( - requestHandlerMetadataContainer, - newStateProvider - ) - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/InjectRequestStatesMappingsBeanPostProcessor.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/InjectRequestStatesMappingsBeanPostProcessor.kt deleted file mode 100644 index 11e58e8..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/aspectstrategy/InjectRequestStatesMappingsBeanPostProcessor.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.nsu.manasyan.kcache.config.aspectstrategy - -import org.springframework.beans.factory.config.BeanPostProcessor -import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadataContainer -import ru.nsu.manasyan.kcache.core.state.holder.StateHolder -import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider - -/** - * BeanPostProcessor, which injects initial states of all table states from [RequestHandlerMetadataContainer] to [StateHolder]. - */ -class InjectRequestStatesMappingsBeanPostProcessor( - private val metadataContainer: RequestHandlerMetadataContainer, - private val newStateProvider: NewStateProvider -) : BeanPostProcessor { - override fun postProcessAfterInitialization(bean: Any, beanName: String): Any { - if (bean !is StateHolder) { - return bean - } - metadataContainer.getAllMetadata() - .values - .map { it.tableStates } - .flatten() - .distinct() - .forEach { tableName -> - bean.setState( - tableName, - newStateProvider.provide(tableName) - ) - } - - return bean - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheable.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheable.kt index b0ef9fe..0826f92 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheable.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheable.kt @@ -8,7 +8,10 @@ import kotlin.reflect.KClass /** * Enables HTTP-caching of request which was processing by current method */ -@Target(AnnotationTarget.FUNCTION) +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.CLASS +) annotation class KCacheable( /** * Tables on which the return value of the HTTP-request handler method depends diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheableJpa.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheableJpa.kt new file mode 100644 index 0000000..b9cedb9 --- /dev/null +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/annotations/KCacheableJpa.kt @@ -0,0 +1,13 @@ +package ru.nsu.manasyan.kcache.core.annotations + +import ru.nsu.manasyan.kcache.core.resultbuilder.ResponseEntityResultBuilderFactory +import ru.nsu.manasyan.kcache.core.resultbuilder.ResultBuilderFactory +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION) +annotation class KCacheableJpa( + val entities: Array>, + + val resultBuilderFactory: KClass = + ResponseEntityResultBuilderFactory::class +) diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt new file mode 100644 index 0000000..32bf280 --- /dev/null +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt @@ -0,0 +1,7 @@ +package ru.nsu.manasyan.kcache.core.jpa + +import kotlin.reflect.KClass + +class EntitiesToListenContainer( + val entities: List> = listOf() +) \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/HazelcastStateHolder.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/HazelcastStateHolder.kt index e7fd0d6..a1043cc 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/HazelcastStateHolder.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/HazelcastStateHolder.kt @@ -46,7 +46,7 @@ class ReplicatedMapEntryListener : EntryListener { override fun mapEvicted(event: MapEvent?) = mapEventHandlerEvicted(event) private fun entryEventHandler(event: EntryEvent?) { - logger.debug("Map '${event?.name}' was ${event?.eventType}: ${event?.key}, ${event?.value}") + logger.debug("Map '${event?.name}' was ${event?.eventType}: ('${event?.key}', '${event?.value}')") } private fun mapEventHandlerEvicted(event: MapEvent?) { diff --git a/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt b/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt index 5f1a5e4..52ac4fc 100644 --- a/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt +++ b/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt @@ -6,12 +6,13 @@ import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import ru.nsu.manasyan.kcache.core.annotations.KCacheable +import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa @RequestMapping("/test") @RestController class TestController(private val service: TestUserServiceKt) { -// @KCacheable(tables = ["users"], key = "#params[0]") - @KCacheable(tables = ["users"]) +// @KCacheable(tables = ["users"]) + @KCacheableJpa(entities = [TestController::class]) @GetMapping("/users") fun getUsers( @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) diff --git a/test-web-app/src/main/resources/application.yaml b/test-web-app/src/main/resources/application.yaml index f878fee..f32da0a 100644 --- a/test-web-app/src/main/resources/application.yaml +++ b/test-web-app/src/main/resources/application.yaml @@ -3,7 +3,7 @@ kcache: state-holder: hazelcast aspect: strategy: - reflection-metadata + generated-metadata logging: level: ru: From 1fbc4f8a21654f7cc2c8de3b59b035d8ee512556 Mon Sep 17 00:00:00 2001 From: Tigran Manasyan Date: Tue, 6 Apr 2021 21:06:36 +0700 Subject: [PATCH 2/2] Refactored KCacheableJpa and friends --- library/build.gradle.kts | 4 +- .../kcache/aspect/KCacheableAspect.kt | 8 +-- .../EntitiesToListenBeanPostProcessor.kt | 34 ---------- .../kcache/config/KCacheAutoConfiguration.kt | 9 +-- .../jpa/HibernateListenerConfiguration.kt | 68 +++++++++++++++++++ .../core/jpa/EntitiesToListenContainer.kt | 7 -- .../core/jpa/KCacheableEntitiesListener.kt | 49 +++++++++++++ .../kcache/core/state/holder/StateHolder.kt | 4 ++ test-web-app/build.gradle.kts | 7 +- .../example/app/service/TestUserService.java | 4 +- .../example/app/controller/TestController.kt | 10 ++- .../app/controller/TestJpaController.kt | 28 ++++++++ .../kotlin/com/example/app/data/TestUser.kt | 13 +++- .../app/repository/DbUserRepository.kt | 10 ++- .../app/repository/RamTestUserRepository.kt | 9 ++- .../app/repository/TestUserRepository.kt | 4 +- .../example/app/service/TestUserService.kt | 15 +++- .../src/main/resources/application.yaml | 15 ++++ .../resources/db/migration/V1__init_db.sql | 5 ++ .../db/migration/V2__add_test_data.sql | 4 ++ 20 files changed, 236 insertions(+), 71 deletions(-) delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt create mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/config/jpa/HibernateListenerConfiguration.kt delete mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt create mode 100644 library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/KCacheableEntitiesListener.kt create mode 100644 test-web-app/src/main/kotlin/com/example/app/controller/TestJpaController.kt create mode 100644 test-web-app/src/main/resources/db/migration/V1__init_db.sql create mode 100644 test-web-app/src/main/resources/db/migration/V2__add_test_data.sql diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 96f6d2e..53166d2 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -13,8 +13,10 @@ dependencies { implementation("org.redisson:redisson:3.15.1") implementation("com.hazelcast:hazelcast:4.1.1") -// implementation("org.reflections:reflections:0.9.11") + implementation("org.reflections:reflections:0.9.11") implementation("io.github.classgraph:classgraph:4.8.102") + // TODO: make optional with nebula plugin + implementation("org.hibernate:hibernate-core:5.4.30.Final") testImplementation("org.springframework.boot:spring-boot-starter-test") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt index 5631484..4343f42 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/aspect/KCacheableAspect.kt @@ -48,7 +48,7 @@ open class KCacheableAspect( open fun wrapKCacheableMethod( joinPoint: ProceedingJoinPoint, kCacheable: KCacheable, - ): Any? = handle( + ): Any? = handleKCacheable( joinPoint, kCacheable.tables.toList(), kCacheable.key, @@ -59,14 +59,14 @@ open class KCacheableAspect( open fun wrapKCacheableJpaMethod( joinPoint: ProceedingJoinPoint, kCacheableJpa: KCacheableJpa, - ): Any? = handle( + ): Any? = handleKCacheable( joinPoint, - kCacheableJpa.entities.map { it.toString() }, + kCacheableJpa.entities.map { it.qualifiedName!! }, "", kCacheableJpa.resultBuilderFactory.createInstance() ) - private fun handle( + private fun handleKCacheable( joinPoint: ProceedingJoinPoint, tables: List, key: String, diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt deleted file mode 100644 index b1c81c1..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/EntitiesToListenBeanPostProcessor.kt +++ /dev/null @@ -1,34 +0,0 @@ -package ru.nsu.manasyan.kcache.config - -import io.github.classgraph.ClassGraph -import org.springframework.beans.factory.config.BeanPostProcessor -import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa -import ru.nsu.manasyan.kcache.core.handler.RequestHandlerMetadataContainer -import ru.nsu.manasyan.kcache.core.state.holder.StateHolder - -/** - * BeanPostProcessor, which injects initial states of all table states from [RequestHandlerMetadataContainer] to [StateHolder]. - */ -class EntitiesToListenBeanPostProcessor() : BeanPostProcessor { - override fun postProcessAfterInitialization(bean: Any, beanName: String): Any { - if (beanName != "entitiesToListenContainer") { - return bean - } - ClassGraph() - .enableAnnotationInfo() - .enableMethodInfo() - .scan() - .getClassesWithMethodAnnotation(KCacheableJpa::class.qualifiedName) - .forEach { - it.methodInfo.forEach { method -> - println( - method.getAnnotationInfo(KCacheableJpa::class.qualifiedName) - ?.parameterValues - ?.get("entities") - ?.value?.javaClass - ) - } - } - return bean - } -} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt index 6f9e562..6767535 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/KCacheAutoConfiguration.kt @@ -9,11 +9,11 @@ import org.springframework.expression.ExpressionParser import org.springframework.expression.spel.standard.SpelExpressionParser import ru.nsu.manasyan.kcache.aspect.KCacheEvictAspect import ru.nsu.manasyan.kcache.aspect.KCacheableAspect +import ru.nsu.manasyan.kcache.config.jpa.HibernateListenerConfiguration import ru.nsu.manasyan.kcache.config.stateholdermanager.StateHolderConfiguration import ru.nsu.manasyan.kcache.core.etag.builder.ConcatenateETagBuilder import ru.nsu.manasyan.kcache.core.etag.builder.ETagBuilder import ru.nsu.manasyan.kcache.core.etag.extractor.IfNoneMatchHeaderExtractor -import ru.nsu.manasyan.kcache.core.jpa.EntitiesToListenContainer import ru.nsu.manasyan.kcache.core.state.holder.StateHolder import ru.nsu.manasyan.kcache.core.state.holdermanager.StateHolderManager import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider @@ -29,6 +29,7 @@ import ru.nsu.manasyan.kcache.util.LoggerProperty StateHolderConfiguration::class, ETagExtractorConfiguration::class, StateProviderConfiguration::class, + HibernateListenerConfiguration::class ] ) @EnableConfigurationProperties(KCacheProperties::class) @@ -68,12 +69,6 @@ class KCacheAutoConfiguration { ) } - @Bean - fun entitiesToListenContainer() = EntitiesToListenContainer() - - @Bean - fun entitiesToListenContainerBpp() = EntitiesToListenBeanPostProcessor() - /** * Creates [KCacheEvictAspect] bean if * there are [StateHolder] and [ETagBuilder] beans in context diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/jpa/HibernateListenerConfiguration.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/jpa/HibernateListenerConfiguration.kt new file mode 100644 index 0000000..c222490 --- /dev/null +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/config/jpa/HibernateListenerConfiguration.kt @@ -0,0 +1,68 @@ +package ru.nsu.manasyan.kcache.config.jpa + +import org.hibernate.event.service.spi.EventListenerRegistry +import org.hibernate.event.spi.EventType +import org.hibernate.internal.SessionFactoryImpl +import org.reflections.Reflections +import org.reflections.scanners.MethodAnnotationsScanner +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa +import ru.nsu.manasyan.kcache.core.jpa.KCacheableEntitiesListener +import ru.nsu.manasyan.kcache.core.state.holdermanager.StateHolderManager +import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider +import ru.nsu.manasyan.kcache.properties.KCacheProperties +import ru.nsu.manasyan.kcache.util.LoggerProperty +import javax.persistence.EntityManagerFactory +import javax.persistence.PersistenceUnit + + +@Configuration +@ConditionalOnProperty( + prefix = KCacheProperties.propertiesPrefix, + name = ["jpa.listener.enable"], + havingValue = "true" +) +class HibernateListenerConfiguration( + @PersistenceUnit + private val managerFactory: EntityManagerFactory, +) { + private companion object { + private const val ALL_PACKAGES = "" + } + + private val logger by LoggerProperty() + + @Bean + fun kCacheableEntitiesHibernateListener( + stateHolderManager: StateHolderManager, + stateProvider: NewStateProvider, + ): KCacheableEntitiesListener = KCacheableEntitiesListener( + stateHolderManager, + stateProvider, + getKCacheableEntities() + ).also { + initListener(it) + logger.debug("KCacheableEntitiesListener was initialized") + } + + private fun getKCacheableEntities() = Reflections(ALL_PACKAGES, MethodAnnotationsScanner()) + .getMethodsAnnotatedWith(KCacheableJpa::class.java) + .flatMap { method -> + method.getAnnotation(KCacheableJpa::class.java) + .entities + .asList() + }.toSet() + + private fun initListener(listener: KCacheableEntitiesListener) { + managerFactory.unwrap(SessionFactoryImpl::class.java) + .serviceRegistry + .getService(EventListenerRegistry::class.java) + .apply { + getEventListenerGroup(EventType.POST_INSERT).appendListener(listener) + getEventListenerGroup(EventType.POST_UPDATE).appendListener(listener) + getEventListenerGroup(EventType.POST_DELETE).appendListener(listener) + } + } +} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt deleted file mode 100644 index 32bf280..0000000 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/EntitiesToListenContainer.kt +++ /dev/null @@ -1,7 +0,0 @@ -package ru.nsu.manasyan.kcache.core.jpa - -import kotlin.reflect.KClass - -class EntitiesToListenContainer( - val entities: List> = listOf() -) \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/KCacheableEntitiesListener.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/KCacheableEntitiesListener.kt new file mode 100644 index 0000000..c2f002a --- /dev/null +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/jpa/KCacheableEntitiesListener.kt @@ -0,0 +1,49 @@ +package ru.nsu.manasyan.kcache.core.jpa + +import org.hibernate.event.spi.* +import org.hibernate.persister.entity.EntityPersister +import ru.nsu.manasyan.kcache.core.state.holder.StateHolder +import ru.nsu.manasyan.kcache.core.state.holdermanager.StateHolderManager +import ru.nsu.manasyan.kcache.core.state.provider.NewStateProvider +import ru.nsu.manasyan.kcache.util.LoggerProperty +import kotlin.reflect.KClass + +class KCacheableEntitiesListener( + private val stateHolderManager: StateHolderManager, + private val stateProvider: NewStateProvider, + private val entities: Set> +) : PostInsertEventListener, + PostUpdateEventListener, + PostDeleteEventListener { + + private val logger by LoggerProperty() + + override fun requiresPostCommitHanding(persister: EntityPersister?): Boolean = false + + override fun onPostDelete(event: PostDeleteEvent?) { + updateState(event?.entity) + } + + override fun onPostUpdate(event: PostUpdateEvent?) { + updateState(event?.entity) + } + + override fun onPostInsert(event: PostInsertEvent?) { + updateState(event?.entity) + } + + private fun updateState(entity: Any?) = entity?.let { + entity::class.let { clazz -> + if (entities.contains(clazz)) { + logger.debug("Updating $clazz") + stateHolderManager + .getOrCreateStateHolder(clazz.qualifiedName!!) + .setState( + StateHolder.WHOLE_TABLE_KEY, + stateProvider.provide(clazz.qualifiedName!!) + ) + } + } + } + +} \ No newline at end of file diff --git a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/StateHolder.kt b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/StateHolder.kt index c328846..0c9c5ca 100644 --- a/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/StateHolder.kt +++ b/library/src/main/kotlin/ru/nsu/manasyan/kcache/core/state/holder/StateHolder.kt @@ -4,6 +4,10 @@ package ru.nsu.manasyan.kcache.core.state.holder * Entity, which contains information about current DB tables state */ interface StateHolder { + companion object { + const val WHOLE_TABLE_KEY = "" + } + /** * @return state of table with id == tableId or null if such not found */ diff --git a/test-web-app/build.gradle.kts b/test-web-app/build.gradle.kts index 6494f5d..db89a44 100644 --- a/test-web-app/build.gradle.kts +++ b/test-web-app/build.gradle.kts @@ -9,8 +9,7 @@ plugins { } dependencies { -// implementation("org.springframework.boot:spring-boot-starter-data-jdbc") -// implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-aop") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") @@ -19,9 +18,13 @@ dependencies { implementation("org.redisson:redisson:3.14.1") implementation("com.google.code.gson:gson:2.8.6") + runtimeOnly("com.h2database:h2") + implementation(project(":library")) kapt(project(":processor")) + implementation("org.flywaydb:flyway-core") + testImplementation("com.natpryce:hamkrest:1.8.0.1") testImplementation("org.springframework.boot:spring-boot-starter-test") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/test-web-app/src/main/java/com/example/app/service/TestUserService.java b/test-web-app/src/main/java/com/example/app/service/TestUserService.java index e530e0b..fb70c44 100644 --- a/test-web-app/src/main/java/com/example/app/service/TestUserService.java +++ b/test-web-app/src/main/java/com/example/app/service/TestUserService.java @@ -16,12 +16,12 @@ public TestUserService(TestUserRepository userRepository) { } public List getUsers() { - return repository.getUsers(); + return repository.findAll(); } @KCacheEvict(tables = {"users"}) public void addUser(TestUser user) { - repository.addUser(user); + repository.save(user); } public TestUser getUser(String id) { diff --git a/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt b/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt index 52ac4fc..11c1e5f 100644 --- a/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt +++ b/test-web-app/src/main/kotlin/com/example/app/controller/TestController.kt @@ -6,14 +6,12 @@ import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import ru.nsu.manasyan.kcache.core.annotations.KCacheable -import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa -@RequestMapping("/test") +@RequestMapping("/test/users") @RestController class TestController(private val service: TestUserServiceKt) { -// @KCacheable(tables = ["users"]) - @KCacheableJpa(entities = [TestController::class]) - @GetMapping("/users") + @KCacheable(tables = ["users"]) + @GetMapping fun getUsers( @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) ifNoneMatch: String? @@ -28,7 +26,7 @@ class TestController(private val service: TestUserServiceKt) { service.evictUsersCache() } - @PostMapping("/users") + @PostMapping fun addUser(@RequestBody user: TestUser) { service.addUser(user) } diff --git a/test-web-app/src/main/kotlin/com/example/app/controller/TestJpaController.kt b/test-web-app/src/main/kotlin/com/example/app/controller/TestJpaController.kt new file mode 100644 index 0000000..f2092ae --- /dev/null +++ b/test-web-app/src/main/kotlin/com/example/app/controller/TestJpaController.kt @@ -0,0 +1,28 @@ +package com.example.app.controller + +import com.example.app.data.TestUser +import com.example.app.service.TestUserServiceKt +import org.springframework.http.HttpHeaders +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import ru.nsu.manasyan.kcache.core.annotations.KCacheableJpa + +@RequestMapping("/test/users") +@RestController +class TestJpaController(private val service: TestUserServiceKt) { + @KCacheableJpa(entities = [TestUser::class]) + @GetMapping("/jpa") + fun getUsers( + @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) + ifNoneMatch: String? + ): ResponseEntity<*> { + return ResponseEntity.ok().body( + service.getUsers() + ) + } + + @GetMapping("/generate") + fun generateUser() { + service.generateUser() + } +} \ No newline at end of file diff --git a/test-web-app/src/main/kotlin/com/example/app/data/TestUser.kt b/test-web-app/src/main/kotlin/com/example/app/data/TestUser.kt index 041101e..846bce7 100644 --- a/test-web-app/src/main/kotlin/com/example/app/data/TestUser.kt +++ b/test-web-app/src/main/kotlin/com/example/app/data/TestUser.kt @@ -1,3 +1,14 @@ package com.example.app.data -data class TestUser(val id: String? = null, val name: String, val age: Int) \ No newline at end of file +import javax.persistence.Entity +import javax.persistence.Id +import javax.persistence.Table + +@Entity +@Table(name = "Users") +data class TestUser( + @Id + val id: String? = null, + val name: String, + val age: Int +) \ No newline at end of file diff --git a/test-web-app/src/main/kotlin/com/example/app/repository/DbUserRepository.kt b/test-web-app/src/main/kotlin/com/example/app/repository/DbUserRepository.kt index 3fb1641..eb3c600 100644 --- a/test-web-app/src/main/kotlin/com/example/app/repository/DbUserRepository.kt +++ b/test-web-app/src/main/kotlin/com/example/app/repository/DbUserRepository.kt @@ -1,6 +1,14 @@ package com.example.app.repository +import com.example.app.data.TestUser +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface DbUserRepository : TestUserRepository \ No newline at end of file +@ConditionalOnProperty( + name = ["repository.user.type"], + havingValue = "jpa", + matchIfMissing = true +) +interface DbUserRepository : TestUserRepository, JpaRepository \ No newline at end of file diff --git a/test-web-app/src/main/kotlin/com/example/app/repository/RamTestUserRepository.kt b/test-web-app/src/main/kotlin/com/example/app/repository/RamTestUserRepository.kt index 9347bd7..e0f322a 100644 --- a/test-web-app/src/main/kotlin/com/example/app/repository/RamTestUserRepository.kt +++ b/test-web-app/src/main/kotlin/com/example/app/repository/RamTestUserRepository.kt @@ -1,10 +1,15 @@ package com.example.app.repository import com.example.app.data.TestUser +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Repository import java.util.* @Repository +@ConditionalOnProperty( + name = ["repository.user.type"], + havingValue = "ram" +) class RamTestUserRepository : TestUserRepository { // TODO: make concurrent private val users = mutableMapOf( @@ -14,11 +19,11 @@ class RamTestUserRepository : TestUserRepository { "FOUR" to TestUser("FOUR", "Anna", 49), ) - override fun getUsers(): List { + override fun findAll(): List { return users.values.toList() } - override fun addUser(user: TestUser) { + override fun save(user: TestUser) { UUID.randomUUID().toString().apply { users[this] = TestUser( this, diff --git a/test-web-app/src/main/kotlin/com/example/app/repository/TestUserRepository.kt b/test-web-app/src/main/kotlin/com/example/app/repository/TestUserRepository.kt index 887ec37..06f62bf 100644 --- a/test-web-app/src/main/kotlin/com/example/app/repository/TestUserRepository.kt +++ b/test-web-app/src/main/kotlin/com/example/app/repository/TestUserRepository.kt @@ -3,9 +3,9 @@ package com.example.app.repository import com.example.app.data.TestUser interface TestUserRepository { - fun getUsers(): List + fun findAll(): List - fun addUser(user: TestUser) + fun save(user: TestUser) fun findUserById(id: String): TestUser? } \ No newline at end of file diff --git a/test-web-app/src/main/kotlin/com/example/app/service/TestUserService.kt b/test-web-app/src/main/kotlin/com/example/app/service/TestUserService.kt index 733c8c7..105478d 100644 --- a/test-web-app/src/main/kotlin/com/example/app/service/TestUserService.kt +++ b/test-web-app/src/main/kotlin/com/example/app/service/TestUserService.kt @@ -4,18 +4,29 @@ import com.example.app.data.TestUser import com.example.app.repository.TestUserRepository import org.springframework.stereotype.Service import ru.nsu.manasyan.kcache.core.annotations.KCacheEvict +import java.util.* @Service class TestUserServiceKt( private val repository: TestUserRepository ) { fun getUsers(): List { - return repository.getUsers() + return repository.findAll() } @KCacheEvict(tables = ["users"]) fun addUser(user: TestUser) { - repository.addUser(user) + repository.save(user) + } + + fun generateUser() { + repository.save( + TestUser( + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + 777 + ) + ) } @KCacheEvict(tables = ["users"]) diff --git a/test-web-app/src/main/resources/application.yaml b/test-web-app/src/main/resources/application.yaml index f32da0a..2fe7039 100644 --- a/test-web-app/src/main/resources/application.yaml +++ b/test-web-app/src/main/resources/application.yaml @@ -1,5 +1,20 @@ # for tests only +spring: + datasource: + url: jdbc:h2:mem:testdb + driverClassName: org.h2.Driver + username: sa + password: password + jpa: + database-platform: org.hibernate.dialect.H2Dialect + h2: + console: + enabled: true + kcache: + jpa: + listener: + enable: true state-holder: hazelcast aspect: strategy: diff --git a/test-web-app/src/main/resources/db/migration/V1__init_db.sql b/test-web-app/src/main/resources/db/migration/V1__init_db.sql new file mode 100644 index 0000000..2907c77 --- /dev/null +++ b/test-web-app/src/main/resources/db/migration/V1__init_db.sql @@ -0,0 +1,5 @@ +create table Users( + id varchar(256) primary key, + name varchar(256) not null, + age int not null +); \ No newline at end of file diff --git a/test-web-app/src/main/resources/db/migration/V2__add_test_data.sql b/test-web-app/src/main/resources/db/migration/V2__add_test_data.sql new file mode 100644 index 0000000..6e591bf --- /dev/null +++ b/test-web-app/src/main/resources/db/migration/V2__add_test_data.sql @@ -0,0 +1,4 @@ +insert into Users values ( 'one', 'Peter', 21 ), + ( 'two', 'Jane', 25 ), + ( 'three', 'Jack', 63 ), + ( 'four', 'Anna', 49 )