diff --git a/build.gradle b/build.gradle index 345a5bc..c101714 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { subprojects { group 'com.synebula' - version '1.3.0' + version '1.4.0' buildscript { repositories { @@ -53,4 +53,23 @@ subprojects { compileTestKotlin { kotlinOptions.jvmTarget = "1.8" } + + publishing { +// repositories { +// maven { +// allowInsecureProtocol = true +// url = "$nexus_url" +// credentials { +// username = "$nexus_usr" +// password = "$nexus_pwd" +// } +// } +// } + + publications { + mavenJava(MavenPublication) { + from components.java + } + } + } } diff --git a/settings.gradle b/settings.gradle index eafaa71..2e16ffb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'gaea' include 'src:gaea' include 'src:gaea.app' -include 'src:gaea.mongodb' include 'src:gaea.spring' +include 'src:gaea.mongodb' +include 'src:gaea.jpa' diff --git a/src/gaea.app/build.gradle b/src/gaea.app/build.gradle index e137186..6e3cf39 100644 --- a/src/gaea.app/build.gradle +++ b/src/gaea.app/build.gradle @@ -19,11 +19,3 @@ dependencies { api group: 'com.auth0', name: 'java-jwt', version: '3.14.0' } -publishing { - publications { - publish(MavenPublication) { - from components.java - } - } -} - diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt index b390ad8..8577662 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt @@ -26,7 +26,7 @@ interface IQueryApp : IApplication { @Method("获取列表数据") @GetMapping - fun list(@RequestParam params: LinkedHashMap): HttpMessage { + fun list(@RequestParam params: LinkedHashMap): HttpMessage { val data = this.query.list(params) return HttpMessage(data) } @@ -36,7 +36,7 @@ interface IQueryApp : IApplication { fun paging( @PathVariable size: Int, @PathVariable page: Int, - @RequestParam parameters: LinkedHashMap + @RequestParam parameters: LinkedHashMap ): HttpMessage { val params = Params(page, size, parameters) val data = this.query.paging(params) diff --git a/src/gaea.jpa/build.gradle b/src/gaea.jpa/build.gradle new file mode 100644 index 0000000..1912d38 --- /dev/null +++ b/src/gaea.jpa/build.gradle @@ -0,0 +1,11 @@ +ext { + jassist_version = '3.29.0-GA' +} + +dependencies { + api project(":src:gaea") + + implementation("org.springframework.boot:spring-boot-starter-data-jpa:$spring_version") + implementation("org.javassist:javassist:$jassist_version") +} + diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaQuery.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaQuery.kt new file mode 100644 index 0000000..844e31b --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaQuery.kt @@ -0,0 +1,67 @@ +package com.synebula.gaea.jpa + +import com.synebula.gaea.query.IQuery +import com.synebula.gaea.query.Page +import com.synebula.gaea.query.Params +import org.springframework.data.jpa.repository.support.SimpleJpaRepository +import javax.persistence.EntityManager + +class JpaQuery(override var clazz: Class, entityManager: EntityManager) : IQuery { + protected var repo: SimpleJpaRepository + + init { + repo = SimpleJpaRepository(clazz, entityManager) + } + + override operator fun get(id: ID): TView? { + val view = this.repo.findById(id) + return if (view.isPresent) view.get() else null + } + + + /** + * 根据实体类条件查询所有符合条件记录 + *` + * @param params 查询条件。 + * @return 视图列表 + */ + override fun list(params: Map?): List { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return emptyList() + } + + /** + * 根据条件查询符合条件记录的数量 + * + * @param params 查询条件。 + * @return 数量 + */ + override fun count(params: Map?): Int { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return -1 + } + + /** + * 根据实体类条件查询所有符合条件记录(分页查询) + * + * @param params 分页条件 + * @return 分页数据 + */ + override fun paging(params: Params): Page { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return Page() + } + + /** + * 查询条件范围内数据。 + * @param field 查询字段 + * @param params 查询条件 + * + * @return 视图列表 + */ + override fun range(field: String, params: List): List { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return emptyList() + } + +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaRepository.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaRepository.kt new file mode 100644 index 0000000..14c4e64 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/JpaRepository.kt @@ -0,0 +1,91 @@ +package com.synebula.gaea.jpa + +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.repository.IRepository +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.support.SimpleJpaRepository +import javax.persistence.EntityManager + + +class JpaRepository, ID>( + override var clazz: Class, + entityManager: EntityManager +) : IRepository { + protected var repo: JpaRepository? = null + + init { + repo = SimpleJpaRepository(clazz, entityManager) + } + + /** + * 插入单个对象。 + * + * @param obj 需要插入的对象。 + * @return 返回原对象,如果对象ID为自增,则补充自增ID。 + */ + override fun add(obj: TAggregateRoot) { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + } + + /** + * 插入多个个对象。 + * + * @param list 需要插入的对象。 + * @return 返回原对象,如果对象ID为自增,则补充自增ID。 + */ + override fun add(list: List) { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + } + + /** + * 更新对象。 + * + * @param obj 需要更新的对象。 + * @return + */ + override fun update(obj: TAggregateRoot) { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + } + + /** + * 更新多个个对象。 + * + * @param list 需要新的对象。 + */ + override fun update(list: List) { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + } + + /** + * 通过id删除该条数据 + * + * @param id 对象ID。 + * @return + */ + override fun remove(id: ID) { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + } + + /** + * 根据ID获取对象。 + * + * @param id 对象ID。 + * @return + */ + override fun get(id: ID): TAggregateRoot? { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return null + } + + /** + * 根据条件查询符合条件记录的数量 + * + * @param params 查询条件。 + * @return int + */ + override fun count(params: Map?): Int { + // method proxy in JpaRepositoryProxy [SimpleJpaRepository] + return -1 + } + +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/Jpas.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/Jpas.kt new file mode 100644 index 0000000..8788e26 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/Jpas.kt @@ -0,0 +1,147 @@ +package com.synebula.gaea.jpa + +import com.synebula.gaea.data.date.DateTime +import com.synebula.gaea.query.Operator +import com.synebula.gaea.query.Where +import org.springframework.data.jpa.domain.Specification +import java.lang.reflect.Field +import java.util.* +import javax.persistence.criteria.CriteriaBuilder +import javax.persistence.criteria.CriteriaQuery +import javax.persistence.criteria.Predicate +import javax.persistence.criteria.Root + + +/** + * 类型转换 + * + * @param field 字段对象 + * @return object + */ +fun String.toFieldType(field: Field): Any? { + var result: Any? = this + val fieldType = field.type + if (fieldType != this.javaClass) { + if (Int::class.java == fieldType || Int::class.javaPrimitiveType == fieldType) { + result = this.toInt() + } + if (Double::class.java == fieldType || Double::class.javaPrimitiveType == fieldType) { + result = this.toDouble() + } + if (Float::class.java == fieldType || Float::class.javaPrimitiveType == fieldType) { + result = this.toFloat() + } + if (Date::class.java == fieldType) { + result = DateTime(this, "yyyy-MM-dd HH:mm:ss").date + } + } + return result +} + +/** + * 格式化数值类型 + * + * @param field 字段对象 + * @return double + */ +fun String.tryToDigital(field: Field): Double { + val result: Double + val fieldType = field.type + result = + if (Int::class.java == fieldType || Int::class.javaPrimitiveType == fieldType + || Double::class.java == fieldType || Double::class.javaPrimitiveType == fieldType + || Float::class.java == fieldType || Float::class.javaPrimitiveType == fieldType + ) { + this.toDouble() + } else throw RuntimeException( + String.format( + "class [%s] field [%s] is not digital", + field.declaringClass.name, + field.name + ) + ) + return result +} + +/** + * 参数 Map 转换查询 Specification + * + * @param clazz 类 + * @return Specification + */ +fun Map?.toSpecification(clazz: Class<*>): Specification<*> { + val rangeStartSuffix = "[0]" //范围查询开始后缀 + val rangeEndSuffix = "[1]" //范围查询结束后缀 + return Specification { root: Root, _: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder -> + val predicates: MutableList = ArrayList() + for (argumentName in this!!.keys) { + if (this[argumentName] == null) continue + var fieldName = argumentName + var operator: Operator + + // 判断是否为range类型(范围内查询) + var start = true + if (fieldName.endsWith(rangeStartSuffix) || fieldName.endsWith(rangeEndSuffix)) { + fieldName = fieldName.substring(fieldName.length - 3) + if (fieldName.endsWith(rangeEndSuffix)) start = false + } + val field = clazz.getDeclaredField(fieldName) + val where: Where = field.getDeclaredAnnotation(Where::class.java) + operator = where.operator + + // 如果是范围内容, 判断是数值类型还是时间类型 + if (operator === Operator.Range) { + if (clazz.getDeclaredField(fieldName).type != Date::class.java) { + operator = if (start) Operator.Gte else Operator.Lte + } + } + var predicate: Predicate + var digitalValue: Double + try { + when (operator) { + Operator.Ne -> predicate = + criteriaBuilder.notEqual(root.get(fieldName), this[fieldName]!!.toFieldType(field)) + + Operator.Lt -> { + digitalValue = this[fieldName]!!.tryToDigital(field) + predicate = criteriaBuilder.lessThan(root.get(fieldName), digitalValue) + } + + Operator.Gt -> { + digitalValue = this[fieldName]!!.tryToDigital(field) + predicate = criteriaBuilder.greaterThan(root.get(fieldName), digitalValue) + } + + Operator.Lte -> { + digitalValue = this[fieldName]!!.tryToDigital(field) + predicate = criteriaBuilder.lessThanOrEqualTo(root.get(fieldName), digitalValue) + } + + Operator.Gte -> { + digitalValue = this[fieldName]!!.tryToDigital(field) + predicate = criteriaBuilder.greaterThanOrEqualTo(root.get(fieldName), digitalValue) + } + + Operator.Like -> predicate = criteriaBuilder.like(root.get(fieldName), "%${this[fieldName]}%") + Operator.Range -> { + predicate = if (start) { + criteriaBuilder.greaterThanOrEqualTo(root.get(fieldName), this[argumentName]!!) + } else { + criteriaBuilder.lessThanOrEqualTo(root.get(fieldName), this[argumentName]!!) + } + } + + else -> predicate = + criteriaBuilder.equal(root.get(fieldName), this[fieldName]!!.toFieldType(field)) + } + predicates.add(predicate) + } catch (e: NoSuchFieldException) { + throw Error( + "class [${field.declaringClass.name}] field [${field.name}] can't annotation [@Where(${operator.declaringClass.simpleName}.${operator.name})]", + e + ) + } + } + criteriaBuilder.and(*predicates.toTypedArray()) + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryFactory.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryFactory.kt new file mode 100644 index 0000000..a54bfb3 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryFactory.kt @@ -0,0 +1,36 @@ +package com.synebula.gaea.jpa.proxy + +import org.springframework.beans.factory.BeanFactory +import org.springframework.beans.factory.FactoryBean +import org.springframework.cglib.proxy.Enhancer +import org.springframework.data.repository.Repository + +class JpaRepositoryFactory( + private val beanFactory: BeanFactory, + private val interfaceType: Class<*>, + private val implBeanNames: List +) : FactoryBean { + override fun getObject(): Any { + val handler: JpaRepositoryProxy<*, *, *> = JpaRepositoryProxy, Any, Any>( + beanFactory, + interfaceType, implBeanNames + ) + + //JDK 方式代理代码, 暂时选用cglib + //Object proxy = Proxy.newProxyInstance(this.interfaceType.getClassLoader(), new Class[]{this.interfaceType}, handler); + + //cglib代理 + val enhancer = Enhancer() + enhancer.setSuperclass(interfaceType) + enhancer.setCallback(handler) + return enhancer.create() + } + + override fun getObjectType(): Class<*> { + return interfaceType + } + + override fun isSingleton(): Boolean { + return true + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxy.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxy.kt new file mode 100644 index 0000000..fbb3cdc --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxy.kt @@ -0,0 +1,289 @@ +package com.synebula.gaea.jpa.proxy + +import com.synebula.gaea.jpa.proxy.method.JpaMethodProxy +import javassist.* +import javassist.bytecode.AnnotationsAttribute +import javassist.bytecode.MethodInfo +import javassist.bytecode.SignatureAttribute +import javassist.bytecode.annotation.Annotation +import javassist.bytecode.annotation.BooleanMemberValue +import javassist.bytecode.annotation.StringMemberValue +import org.springframework.beans.BeansException +import org.springframework.beans.factory.BeanFactory +import org.springframework.beans.factory.ObjectProvider +import org.springframework.cglib.proxy.MethodInterceptor +import org.springframework.cglib.proxy.MethodProxy +import org.springframework.data.jpa.repository.Modifying +import org.springframework.data.jpa.repository.Query +import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean +import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation +import org.springframework.data.mapping.context.MappingContext +import org.springframework.data.querydsl.EntityPathResolver +import org.springframework.data.querydsl.SimpleEntityPathResolver +import org.springframework.data.repository.Repository +import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import javax.persistence.EntityManager + +class JpaRepositoryProxy?, S, ID>( + beanFactory: BeanFactory, + interfaceType: Class<*>, + implementBeanNames: List? +) : MethodInterceptor { //InvocationHandler { + + //JPA 默认Entity Manager上下文, 如不用该上下文则没有事务管理器 + private val EntityManagerName = "org.springframework.orm.jpa.SharedEntityManagerCreator#0" + + /** + * JPA代理对象 + */ + private var jpaRepository: JpaRepositoryImplementation<*, *>? = null + + /** + * bean注册器 + */ + protected var beanFactory: BeanFactory + + /** + * 方法映射管理器 + */ + protected var jpaMethodProxy: JpaMethodProxy + + /** + * 需要代理的接口类型 + */ + protected var interfaceType: Class<*> + + /** + * 接口实现bean名称 + */ + protected var implementBeanNames: List + + init { + try { + this.beanFactory = beanFactory + this.implementBeanNames = implementBeanNames ?: listOf() + this.interfaceType = interfaceType + + // 设置方法映射查询参数类(Entity类型) + val type = interfaceType.genericInterfaces[0] + val typeArguments = (type as ParameterizedType).actualTypeArguments + jpaMethodProxy = JpaMethodProxy(Class.forName(typeArguments[0].typeName)) + + + // 创建虚假的JpaRepository接口 + val repoClazz = createJpaRepoClazz(*typeArguments) + val jpaRepositoryFactoryBean: JpaRepositoryFactoryBean<*, *, *> = createJPARepositoryFactoryBean(repoClazz) + jpaRepository = jpaRepositoryFactoryBean.getObject() as JpaRepositoryImplementation<*, *> + } catch (e: ClassNotFoundException) { + throw RuntimeException(e) + } + } + + /** + * JDK 方式代理代码, 暂时选用cglib + */ + @Deprecated("") + @Throws(Throwable::class) + operator fun invoke(proxy: Any?, method: Method, args: Array): Any? { + return if (Any::class.java == method.declaringClass) { + method.invoke(proxy, *args) + } else { + execMethod(method, args) + } + } + + /** + * 暂时选用cglib 方式代理代码 + */ + @Throws(Throwable::class) + override fun intercept(o: Any, method: Method, args: Array, methodProxy: MethodProxy): Any { + return if (Any::class.java == method.declaringClass) { + methodProxy.invoke(this, args) + } else { + execMethod(method, args)!! + } + } + + /** + * 执行代理方法 + * + * @param method 需要执行的方法 + * @param args 参数列表 + * @return 方法执行结果 + * @throws Throwable 异常 + */ + @Throws(Throwable::class) + private fun execMethod(method: Method, args: Array): Any? { + // 找到对应代理方法, 代理执行 + return if (jpaMethodProxy.match(method)) { + try { + jpaMethodProxy.proxyExecMethod(jpaRepository, method, args) + } catch (ex: Exception) { + throw RuntimeException( + String.format( + "对象[%s]代理执行方法[%s.%s]出错", + jpaMethodProxy.javaClass, interfaceType.name, method.name + ), ex + ) + } + } else { + // 找不到代理方法则查找具体实现类执行 + if (implementBeanNames.isEmpty()) throw RuntimeException( + String.format( + "找不到[%s.%s]对应的代理方法", + method.declaringClass.name, method.name + ) + ) else { + val bean = beanFactory.getBean(implementBeanNames[0]) + val proxyMethod = bean.javaClass.getMethod(method.name, *method.parameterTypes) + proxyMethod.invoke(bean, *args) + } + } + } + + /** + * 使用javassist创建虚拟的jpa repo类 + * + * @param typeArgs 泛型参数 + * @return 虚拟的jpa repo类形 + */ + @Suppress("unchecked_cast") + private fun createJpaRepoClazz(vararg typeArgs: Type): Class { + return try { + val pool = ClassPool.getDefault() + val jpaRepoCt = pool[JpaRepositoryImplementation::class.java.name] + val clazzName = String.format("%sRepository", typeArgs[0].typeName) + val repoCt = pool.makeInterface(clazzName, jpaRepoCt) + val typeArguments = arrayOfNulls(typeArgs.size) + for (i in typeArgs.indices) { + typeArguments[i] = SignatureAttribute.TypeArgument(SignatureAttribute.ClassType(typeArgs[i].typeName)) + } + val ac = SignatureAttribute.ClassSignature( + null, + null, + arrayOf(SignatureAttribute.ClassType(jpaRepoCt.name, typeArguments)) + ) + repoCt.genericSignature = ac.encode() + addClassQueryMethod(repoCt) + repoCt.toClass() as Class + } catch (ex: Exception) { + throw RuntimeException(ex) + } + } + + /** + * 给虚拟接口添加Query注解方法 + * + * @param ctClass jpa虚拟接口 + */ + @Throws(NotFoundException::class, CannotCompileException::class, ClassNotFoundException::class) + private fun addClassQueryMethod(ctClass: CtClass) { + // 找到Query注解方法并加入到虚拟接口中 + val interfaceCtClass = ClassPool.getDefault()[interfaceType.name] + for (ctMethod in interfaceCtClass.methods) { + val query = ctMethod.getAnnotation(Query::class.java) + if (query != null) { + val method = CtNewMethod.abstractMethod( + ctMethod.returnType, ctMethod.name, + ctMethod.parameterTypes, arrayOfNulls(0), ctClass + ) + val methodInfo = method.methodInfo + var modifying: Modifying? = null + //查找有无@Modifing注解,有的化虚拟接口也需要加上 + val annotation = ctMethod.getAnnotation(Modifying::class.java) + if (annotation != null) { + modifying = annotation as Modifying + } + + // 增加Query注解 + val attribute = buildQueryAttribute(methodInfo, query as Query, modifying) + methodInfo.addAttribute(attribute) + ctClass.addMethod(method) + } + } + + // Query注解方法加入到代理中 + for (method in interfaceType.methods) { + val annotation: Any? = method.getAnnotation(Query::class.java) + if (annotation != null) { + jpaMethodProxy.addQueryMethodMapper(method) + } + } + } + + /** + * 创建javassist方法Query注解 + * + * @param methodInfo 方法信息 + * @param query query注解实例 + * @param modifying Modifying注解 + * @return 注解信息 + */ + private fun buildQueryAttribute(methodInfo: MethodInfo, query: Query, modifying: Modifying?): AnnotationsAttribute { + val cp = methodInfo.constPool + val attribute = AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag) + val queryAnnotation = Annotation(Query::class.java.name, cp) + queryAnnotation.addMemberValue("value", StringMemberValue(query.value, cp)) + queryAnnotation.addMemberValue("countQuery", StringMemberValue(query.countQuery, cp)) + queryAnnotation.addMemberValue("countProjection", StringMemberValue(query.countProjection, cp)) + queryAnnotation.addMemberValue("nativeQuery", BooleanMemberValue(query.nativeQuery, cp)) + queryAnnotation.addMemberValue("name", StringMemberValue(query.name, cp)) + queryAnnotation.addMemberValue("countName", StringMemberValue(query.countName, cp)) + if (modifying != null) { + val modifyingAnnotation = Annotation( + Modifying::class.java.name, cp + ) + modifyingAnnotation.addMemberValue( + "flushAutomatically", + BooleanMemberValue(modifying.flushAutomatically, cp) + ) + modifyingAnnotation.addMemberValue( + "clearAutomatically", + BooleanMemberValue(modifying.clearAutomatically, cp) + ) + attribute.annotations = arrayOf(queryAnnotation, modifyingAnnotation) + } else attribute.setAnnotation(queryAnnotation) + return attribute + } + + /** + * 创建JpaRepositoryFactoryBean对象 + * + * @param jpaRepositoryClass 需要创建的 JPA JpaRepository Class + * @return JpaRepositoryFactoryBean对象 + */ + private fun createJPARepositoryFactoryBean(jpaRepositoryClass: Class): JpaRepositoryFactoryBean { + // jpa 默认使用改名成EntityManager, 若用默认则没有事务上下文 + val entityManager = beanFactory.getBean(EntityManagerName) as EntityManager + val repositoryFactoryBean = JpaRepositoryFactoryBean(jpaRepositoryClass) + repositoryFactoryBean.setEntityManager(entityManager) + repositoryFactoryBean.setBeanFactory(beanFactory) + repositoryFactoryBean.setBeanClassLoader(JpaRepositoryFactoryBean::class.java.classLoader) + repositoryFactoryBean.setMappingContext(beanFactory.getBean("jpaMappingContext") as MappingContext<*, *>) + repositoryFactoryBean.setEntityPathResolver(object : ObjectProvider { + @Throws(BeansException::class) + override fun getObject(vararg objects: Any): EntityPathResolver { + return SimpleEntityPathResolver("") + } + + @Throws(BeansException::class) + override fun getIfAvailable(): EntityPathResolver? { + return null + } + + @Throws(BeansException::class) + override fun getIfUnique(): EntityPathResolver? { + return null + } + + @Throws(BeansException::class) + override fun getObject(): EntityPathResolver { + return SimpleEntityPathResolver("") + } + }) + repositoryFactoryBean.afterPropertiesSet() + return repositoryFactoryBean + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxyScan.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxyScan.kt new file mode 100644 index 0000000..a191086 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryProxyScan.kt @@ -0,0 +1,12 @@ +package com.synebula.gaea.jpa.proxy + +import org.springframework.context.annotation.Import +import java.lang.annotation.Inherited +import kotlin.reflect.KClass + +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@Inherited +@Import(JpaRepositoryRegister::class) +annotation class JpaRepositoryProxyScan(val basePackages: Array = [], val scanInterfaces: Array> = []) \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryRegister.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryRegister.kt new file mode 100644 index 0000000..e96812f --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/JpaRepositoryRegister.kt @@ -0,0 +1,141 @@ +package com.synebula.gaea.jpa.proxy + +import org.springframework.beans.BeansException +import org.springframework.beans.factory.BeanClassLoaderAware +import org.springframework.beans.factory.BeanFactory +import org.springframework.beans.factory.BeanFactoryAware +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition +import org.springframework.beans.factory.config.BeanDefinition +import org.springframework.beans.factory.support.BeanDefinitionBuilder +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.beans.factory.support.GenericBeanDefinition +import org.springframework.context.EnvironmentAware +import org.springframework.context.ResourceLoaderAware +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar +import org.springframework.core.annotation.AnnotationAttributes +import org.springframework.core.env.Environment +import org.springframework.core.io.ResourceLoader +import org.springframework.core.type.AnnotationMetadata +import org.springframework.core.type.classreading.MetadataReader +import org.springframework.core.type.classreading.MetadataReaderFactory +import org.springframework.core.type.filter.TypeFilter +import org.springframework.util.ClassUtils +import java.util.* +import java.util.stream.Collectors + +class JpaRepositoryRegister : ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, + EnvironmentAware, + BeanFactoryAware { + private lateinit var environment: Environment + private lateinit var resourceLoader: ResourceLoader + private var classLoader: ClassLoader? = null + private var beanFactory: BeanFactory? = null + override fun registerBeanDefinitions(metadata: AnnotationMetadata, registry: BeanDefinitionRegistry) { + val attributes = AnnotationAttributes( + metadata.getAnnotationAttributes( + JpaRepositoryProxyScan::class.java.name + ) ?: mapOf() + ) + val basePackages = attributes.getStringArray("basePackages") + val scanInterfaces = attributes.getClassArray("scanInterfaces") + // 过滤scanInterfaces接口内容 + val filter = getSubObjectTypeFilter(scanInterfaces) + val beanDefinitions = scan(basePackages, arrayOf(filter)) + + // 遍历处理接口 + for (beanDefinition in beanDefinitions) { + // 获取RepositoryFor注解信息 + val beanClazz: Class<*> = try { + Class.forName(beanDefinition.beanClassName) + } catch (e: ClassNotFoundException) { + throw RuntimeException(e) + } + val beanClazzTypeFilter = getSubObjectTypeFilter(arrayOf(beanClazz)) + val implClazzDefinitions = scan(basePackages, arrayOf(beanClazzTypeFilter)) + for (definition in implClazzDefinitions) { + definition.isAutowireCandidate = false + registry.registerBeanDefinition(Objects.requireNonNull(definition.beanClassName), definition) + } + // 构建bean定义 + // 1 bean参数 + val implBeanNames = implClazzDefinitions.stream().map { obj: BeanDefinition -> obj.beanClassName } + .collect(Collectors.toList()) + val builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz) + builder.addConstructorArgValue(beanFactory) + builder.addConstructorArgValue(beanClazz) + builder.addConstructorArgValue(implBeanNames) + val definition = builder.rawBeanDefinition as GenericBeanDefinition + definition.beanClass = JpaRepositoryFactory::class.java + definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE + registry.registerBeanDefinition(beanClazz.name, definition) + } + } + + /** + * 根据过滤器扫描直接包下bean + * + * @param packages 指定的扫描包 + * @param filters 过滤器 + * @return 扫描后的bean定义 + */ + private fun scan(packages: Array?, filters: Array): List { + val scanner: ClassPathScanningCandidateComponentProvider = + object : ClassPathScanningCandidateComponentProvider() { + override fun isCandidateComponent(beanDefinition: AnnotatedBeanDefinition): Boolean { + try { + val metadata = beanDefinition.metadata + val target = ClassUtils.forName(metadata.className, classLoader) + return !target.isAnnotation + } catch (ignored: Exception) { + } + return false + } + } + scanner.environment = environment + scanner.resourceLoader = resourceLoader + for (filter in filters) { + scanner.addIncludeFilter(filter) + } + val beanDefinitions: MutableList = LinkedList() + for (basePackage in packages!!) { + beanDefinitions.addAll(scanner.findCandidateComponents(basePackage)) + } + return beanDefinitions + } + + /** + * 获取父接口实现对象的类型过滤器 + * + * @param interfaces 父接口 + * @return 类型过滤器 + */ + private fun getSubObjectTypeFilter(interfaces: Array>?): TypeFilter { + return TypeFilter { metadataReader: MetadataReader, _: MetadataReaderFactory? -> + val interfaceNames = metadataReader.classMetadata.interfaceNames + var matched = false + for (interfaceName in interfaceNames) { + matched = Arrays.stream(interfaces) + .anyMatch { clazz: Class<*> -> clazz.name == interfaceName } + } + matched + } + } + + override fun setResourceLoader(resourceLoader: ResourceLoader) { + this.resourceLoader = resourceLoader + } + + override fun setBeanClassLoader(classLoader: ClassLoader) { + this.classLoader = classLoader + } + + override fun setEnvironment(environment: Environment) { + this.environment = environment + } + + @Throws(BeansException::class) + override fun setBeanFactory(beanFactory: BeanFactory) { + this.beanFactory = beanFactory + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/JpaMethodProxy.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/JpaMethodProxy.kt new file mode 100644 index 0000000..107a1f9 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/JpaMethodProxy.kt @@ -0,0 +1,141 @@ +package com.synebula.gaea.jpa.proxy.method + +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.jpa.proxy.method.resolver.AbstractMethodResolver +import com.synebula.gaea.jpa.proxy.method.resolver.DefaultMethodResolver +import com.synebula.gaea.jpa.proxy.method.resolver.FindMethodResolver +import com.synebula.gaea.jpa.proxy.method.resolver.PageMethodResolver +import com.synebula.gaea.query.Params +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.domain.Specification +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method + +/** + * Jpa 方法映射包装类 + */ +class JpaMethodProxy( + /** + * 方法需要实现的实体类 + */ + private var entityClazz: Class<*> +) { + /** + * 默认的方法映射配置(IRepository, IQuery 接口中定义的方法) + */ + private val defaultMethodMapper: MutableMap = LinkedHashMap() + + /** + * 用户自定义的query注解方法处理 + */ + private val queryMethodMapper: MutableMap = LinkedHashMap() + + /** + * 方法参数映射 + */ + var argumentResolver: AbstractMethodResolver? = null + private set + + init { + initDefaultMethodMapper() + } + + /** + * 匹配方法是否需要代理 + * + * @param method 方法 + * @return ture/false + */ + fun match(method: Method): Boolean { + var isMatch = (defaultMethodMapper.containsKey(method.name) + && defaultMethodMapper[method.name]!!.match(method, AbstractMethodResolver.MethodType.SourceMethod)) + + // 如果默认代理方法没有匹配,则查找Query方法映射 + if (!isMatch) { + isMatch = queryMethodMapper.containsKey(method.toString()) + } + return isMatch + } + + /** + * 解析代理方法 + * + * @param proxy 代理对象 + * @param method 源方法 + * @param args 参数列表 + * @return 执行结果 + */ + @Throws(NoSuchMethodException::class, InvocationTargetException::class, IllegalAccessException::class) + fun proxyExecMethod(proxy: Any?, method: Method, args: Array): Any? { + // 匹配方法是否需要代理 + if (defaultMethodMapper.containsKey(method.name)) { + val resolver = defaultMethodMapper[method.name] + // 匹配参数是否相同 + if (resolver!!.match(method, AbstractMethodResolver.MethodType.SourceMethod)) { + //遍历代理对象, 找到合适的代理方法 + val targetMethod = + proxy!!.javaClass.getMethod(resolver.targetMethodName, *resolver.targetMethodParameters) + return try { + // 开始执行代理方法 + val mappingArguments = resolver.mappingArguments(args) + val result = targetMethod.invoke(proxy, *mappingArguments) + resolver.mappingResult(result) + } catch (e: IllegalAccessException) { + throw RuntimeException(e) + } catch (e: InvocationTargetException) { + throw RuntimeException(e) + } + } + } + // 如果默认代理方法没有匹配,则查找Query方法映射 + if (queryMethodMapper.containsKey(method.toString())) { + val targetMethod = proxy!!.javaClass.getMethod(method.name, *method.parameterTypes) + return targetMethod.invoke(proxy, *args) + } + throw RuntimeException( + String.format( + "方法[%s,%s]没有匹配的代理配置信息, 执行该方法前请先执行match方法判断", + method.declaringClass.name, method.name + ) + ) + } + + /** + * 初始化默认的方法映射列表 + */ + private fun initDefaultMethodMapper() { + defaultMethodMapper["add"] = DefaultMethodResolver("saveAndFlush") + .sourceMethodParameters(IAggregateRoot::class.java).targetMethodParameters(Any::class.java) + defaultMethodMapper["update"] = DefaultMethodResolver("saveAndFlush") + .sourceMethodParameters(IAggregateRoot::class.java).targetMethodParameters(Any::class.java) + defaultMethodMapper["remove"] = DefaultMethodResolver("deleteById") + .sourceMethodParameters(Any::class.java).targetMethodParameters(Any::class.java) + defaultMethodMapper["get"] = DefaultMethodResolver("findById") + .sourceMethodParameters(Any::class.java).targetMethodParameters(Any::class.java) + defaultMethodMapper["list"] = FindMethodResolver("findAll", entityClazz) + .sourceMethodParameters(MutableMap::class.java).targetMethodParameters(Specification::class.java) + defaultMethodMapper["count"] = FindMethodResolver("count", entityClazz) + .sourceMethodParameters(MutableMap::class.java).targetMethodParameters(Specification::class.java) + defaultMethodMapper["paging"] = PageMethodResolver("findAll", entityClazz) + .sourceMethodParameters(Params::class.java) + .targetMethodParameters(Specification::class.java, Pageable::class.java) + } + + /** + * 增加用户自定义Query注解方法映射信息 + * + * @param method 需要添加的方法 + */ + fun addQueryMethodMapper(method: Method) { + queryMethodMapper[method.toString()] = DefaultMethodResolver(method.name) + } + + fun setArgumentResolver(argumentResolver: AbstractMethodResolver?): JpaMethodProxy { + this.argumentResolver = argumentResolver + return this + } + + fun setEntityClazz(entityClazz: Class<*>) { + this.entityClazz = entityClazz + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/AbstractMethodResolver.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/AbstractMethodResolver.kt new file mode 100644 index 0000000..23eb4d7 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/AbstractMethodResolver.kt @@ -0,0 +1,100 @@ +package com.synebula.gaea.jpa.proxy.method.resolver + +import java.lang.reflect.Method + +/** + * 解决JPA方法参数的映射 + * + * @param targetMethodName 目标方法名称 + */ +abstract class AbstractMethodResolver(var targetMethodName: String) { + + + /** + * 方法相关实体类型 + */ + lateinit var entityClazz: Class<*> + + /** + * 目标方法形参类型列表 + * + */ + lateinit var targetMethodParameters: Array> + + /** + * 源方法形参类型列表 + * + */ + lateinit var sourceMethodParameters: Array> + + constructor(targetMethodName: String, entityClazz: Class<*>) : this(targetMethodName) { + this.entityClazz = entityClazz + } + + /** + * 解析映射实参 + * + * @param args 实参列表 + * @return 映射后的实参列表 + */ + abstract fun mappingArguments(args: Array): Array + + /** + * 解析映射方法结果 + * + * @param result 方法结果 + * @return 映射后的方法结果 + */ + abstract fun mappingResult(result: Any): Any + + /** + * 获取源方法形参类型列表 + */ + open fun sourceMethodParameters(vararg params: Class<*>): AbstractMethodResolver { + this.sourceMethodParameters = params + return this + } + + /** + * 设置目标方法形参类型列表 + */ + open fun targetMethodParameters(vararg params: Class<*>): AbstractMethodResolver { + this.targetMethodParameters = params + return this + } + + /** + * 匹配方法名(目标方法)/参数是否复合 + * + * @param method 需要匹配的方法 + * @param methodType 需要匹配的方法类型 + * @return ture/false + */ + fun match(method: Method, methodType: MethodType): Boolean { + var methodParameters = sourceMethodParameters + var matched = true + + // 匹配目标方法的时候额外匹配下方法名 + if (methodType == MethodType.TargetMethod) { + methodParameters = targetMethodParameters + matched = method.name == targetMethodName + } + + // 如果[目标]方法名匹配, 判断参数是否匹配 + matched = matched && method.parameterCount == methodParameters.size + if (matched) { + for (i in methodParameters.indices) { + val parameterTypes = method.parameterTypes + if (methodParameters[i] != parameterTypes[i]) { + matched = false + break + } + } + } + return matched + } + + enum class MethodType { + SourceMethod, TargetMethod + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/DefaultMethodResolver.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/DefaultMethodResolver.kt new file mode 100644 index 0000000..8bc0ad3 --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/DefaultMethodResolver.kt @@ -0,0 +1,20 @@ +package com.synebula.gaea.jpa.proxy.method.resolver + +import java.util.* + +/** + * 默认返回全部 + */ +class DefaultMethodResolver(targetMethodName: String) : AbstractMethodResolver(targetMethodName) { + + override fun mappingArguments(args: Array): Array { + return args + } + + override fun mappingResult(result: Any): Any { + if (result is Optional<*>) { + return result.orElse(null) + } + return result + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/FindMethodResolver.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/FindMethodResolver.kt new file mode 100644 index 0000000..cb5f73f --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/FindMethodResolver.kt @@ -0,0 +1,20 @@ +package com.synebula.gaea.jpa.proxy.method.resolver + +import com.synebula.gaea.jpa.toSpecification + +/** + * 查询方法参数映射 + */ +class FindMethodResolver(targetMethodName: String, clazz: Class<*>) : AbstractMethodResolver(targetMethodName, clazz) { + + @Suppress("UNCHECKED_CAST") + override fun mappingArguments(args: Array): Array { + val params = args[0] as Map? + val specification = params.toSpecification(entityClazz) + return arrayOf(specification) + } + + override fun mappingResult(result: Any): Any { + return result + } +} \ No newline at end of file diff --git a/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/PageMethodResolver.kt b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/PageMethodResolver.kt new file mode 100644 index 0000000..fec032c --- /dev/null +++ b/src/gaea.jpa/src/main/java/com/synebula/gaea/jpa/proxy/method/resolver/PageMethodResolver.kt @@ -0,0 +1,56 @@ +package com.synebula.gaea.jpa.proxy.method.resolver + +import com.synebula.gaea.jpa.toSpecification +import com.synebula.gaea.query.Order +import com.synebula.gaea.query.Params +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Sort +import java.util.* +import javax.persistence.EmbeddedId +import javax.persistence.Id + +/** + * 分页方法参数映射 + */ +class PageMethodResolver(targetMethodName: String, clazz: Class<*>) : AbstractMethodResolver(targetMethodName, clazz) { + + override fun mappingArguments(args: Array): Array { + return try { + val params: Params? = args[0] as Params? + val specification = params!!.parameters.toSpecification(entityClazz) + var sort = Sort.unsorted() + for (key in params.orders.keys) { + val direction = if (params.orders[key] === Order.ASC) Sort.Direction.ASC else Sort.Direction.DESC + sort = sort.and(Sort.by(direction, key)) + } + if (sort.isEmpty) { + val fields = entityClazz.declaredFields + for (field in fields) { + val isId = Arrays.stream(field.declaredAnnotations).anyMatch { annotation: Annotation -> + (annotation.annotationClass.java == Id::class.java + || annotation.annotationClass.java == EmbeddedId::class.java) + } + if (isId) { + sort = Sort.by(Sort.Direction.ASC, field.name) + break + } + } + } + + // Pageable 页面从0开始 + val pageable: Pageable = PageRequest.of(params.page - 1, params.size, sort) + arrayOf(specification, pageable) + } catch (e: Exception) { + throw RuntimeException(e) + } + } + + override fun mappingResult(result: Any): Any { + val page = result as Page<*> + + // Page 页面从0开始 + return com.synebula.gaea.query.Page(page.number + 1, page.size, page.totalElements.toInt(), page.content) + } +} \ No newline at end of file diff --git a/src/gaea.mongodb/build.gradle b/src/gaea.mongodb/build.gradle index b99693a..832dd81 100644 --- a/src/gaea.mongodb/build.gradle +++ b/src/gaea.mongodb/build.gradle @@ -3,11 +3,3 @@ dependencies { api project(":src:gaea.spring") api("org.springframework.boot:spring-boot-starter-data-mongodb:$spring_version") } - -publishing { - publications { - publish(MavenPublication) { - from components.java - } - } -} \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/Mongodbs.kt similarity index 80% rename from src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/Mongodbs.kt index e8f57a2..5f92c72 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/Mongodbs.kt @@ -30,7 +30,7 @@ fun Query.select(fields: Array): Query { * @param onWhere 获取字段查询方式的方法 */ fun Query.where( - params: Map?, + params: Map?, onWhere: ((v: String) -> Where?) = { null }, onFieldType: ((v: String) -> Class<*>?) = { null } ): Query { @@ -38,15 +38,15 @@ fun Query.where( if (params != null) { for (param in params) { val key = param.key - var value = param.value + var value: Any = param.value //日期类型特殊处理为String类型 val fieldType = onFieldType(key) if (fieldType != null && value.javaClass != fieldType) { when (fieldType) { - Date::class.java -> value = DateTime(value.toString(), "yyyy-MM-ddTHH:mm:ss").date - Int::class.java -> value = value.toString().toInt() - Integer::class.java -> value = value.toString().toInt() + Date::class.java -> value = DateTime(param.value, "yyyy-MM-ddTHH:mm:ss").date + Int::class.java -> value = param.value.toInt() + Integer::class.java -> value = param.value.toInt() } } @@ -58,14 +58,15 @@ fun Query.where( val field = where.children.ifEmpty { key } var criteria = Criteria.where(field) criteria = when (where.operator) { - Operator.eq -> criteria.`is`(value) - Operator.ne -> criteria.ne(value) - Operator.lt -> criteria.lt(value) - Operator.gt -> criteria.gt(value) - Operator.lte -> criteria.lte(value) - Operator.gte -> criteria.gte(value) - Operator.like -> criteria.regex(value.toString(), if (where.sensitiveCase) "" else "i") - Operator.default -> tryRangeWhere(param.key, value, onFieldType) + Operator.Eq -> criteria.`is`(value) + Operator.Ne -> criteria.ne(value) + Operator.Lt -> criteria.lt(value) + Operator.Gt -> criteria.gt(value) + Operator.Lte -> criteria.lte(value) + Operator.Gte -> criteria.gte(value) + Operator.Like -> criteria.regex(value.toString(), if (where.sensitiveCase) "" else "i") + Operator.Range -> tryRangeWhere(param.key, value, onFieldType) + Operator.Default -> tryRangeWhere(param.key, value, onFieldType) } list.add(if (where.children.isEmpty()) criteria else Criteria.where(key).elemMatch(criteria)) } @@ -103,7 +104,7 @@ private fun tryRangeWhere(key: String, value: Any, onFieldType: ((v: String) -> * * @param params 参数列表 */ -fun Query.where(params: Map?, clazz: Class<*>): Query { +fun Query.where(params: Map?, clazz: Class<*>): Query { var field: Field? return this.where(params, { name -> field = clazz.declaredFields.find { it.name == name } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryFactory.kt similarity index 76% rename from src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryFactory.kt index f654626..3673744 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryFactory.kt @@ -4,11 +4,11 @@ import com.synebula.gaea.spring.autoconfig.Factory import com.synebula.gaea.spring.autoconfig.Proxy import org.springframework.beans.factory.BeanFactory -class MongodbRepoFactory( +class MongodbRepositoryFactory( supertype: Class<*>, var beanFactory: BeanFactory, ) : Factory(supertype) { override fun createProxy(): Proxy { - return MongodbRepoProxy(supertype, this.beanFactory) + return MongodbRepositoryProxy(supertype, this.beanFactory) } } \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryProxy.kt similarity index 98% rename from src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryProxy.kt index 5fa6459..e4b611e 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryProxy.kt @@ -10,7 +10,7 @@ import org.springframework.beans.factory.BeanFactory import org.springframework.data.mongodb.core.MongoTemplate import java.lang.reflect.Method -class MongodbRepoProxy( +class MongodbRepositoryProxy( private var supertype: Class<*>, private var beanFactory: BeanFactory ) : Proxy() { diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryRegister.kt similarity index 91% rename from src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryRegister.kt index 85d4fee..5eca91c 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryRegister.kt @@ -9,14 +9,14 @@ import org.springframework.beans.factory.support.GenericBeanDefinition import org.springframework.core.annotation.AnnotationAttributes import org.springframework.core.type.AnnotationMetadata -class MongodbRepoRegister : Register() { +class MongodbRepositoryRegister : Register() { override fun scan(metadata: AnnotationMetadata): Map { val result = mutableMapOf() // 获取注解参数信息:basePackages val attributes = AnnotationAttributes( metadata.getAnnotationAttributes( - MongodbRepoScan::class.java.name + MongodbRepositoryScan::class.java.name ) ?: mapOf() ) val basePackages = attributes.getStringArray("basePackages") @@ -44,7 +44,7 @@ class MongodbRepoRegister : Register() { builder.addConstructorArgValue(beanClazz) builder.addConstructorArgValue(this._beanFactory) val definition = builder.rawBeanDefinition as GenericBeanDefinition - definition.beanClass = MongodbRepoFactory::class.java + definition.beanClass = MongodbRepositoryFactory::class.java definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE result[beanClazz.name] = definition } @@ -59,7 +59,7 @@ class MongodbRepoRegister : Register() { builder.addConstructorArgValue(this._beanFactory) builder.addConstructorArgValue(emptyArray()) val definition = builder.rawBeanDefinition as GenericBeanDefinition - definition.beanClass = MongodbRepoFactory::class.java + definition.beanClass = MongodbRepositoryFactory::class.java definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE result[IRepository::class.java.name] = definition } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryScan.kt similarity index 69% rename from src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryScan.kt index ef8aa03..08ba32c 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepositoryScan.kt @@ -8,5 +8,5 @@ import java.lang.annotation.Inherited @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented @Inherited -@Import(MongodbRepoRegister::class) -annotation class MongodbRepoScan(val basePackages: Array = []) \ No newline at end of file +@Import(MongodbRepositoryRegister::class) +annotation class MongodbRepositoryScan(val basePackages: Array = []) \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt index 27cabbf..251dcdb 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt @@ -31,7 +31,7 @@ open class MongodbQuery(override var clazz: Class, var templat return this.template.findOne(whereId(id), clazz, this.collection(clazz)) } - override fun list(params: Map?): List { + override fun list(params: Map?): List { val fields = this.fields(clazz) val query = Query() query.where(params, clazz) @@ -39,7 +39,7 @@ open class MongodbQuery(override var clazz: Class, var templat return this.find(query, clazz) } - override fun count(params: Map?): Int { + override fun count(params: Map?): Int { val query = Query() return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt() } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt index 823963e..a275ffd 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt @@ -30,7 +30,7 @@ open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery return this.template.findOne(whereId(id), clazz, this.collection(clazz)) } - override fun list(params: Map?, clazz: Class): List { + override fun list(params: Map?, clazz: Class): List { val fields = this.fields(clazz) val query = Query() query.where(params, clazz) @@ -38,7 +38,7 @@ open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery return this.find(query, clazz) } - override fun count(params: Map?, clazz: Class): Int { + override fun count(params: Map?, clazz: Class): Int { val query = Query() return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt() } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt index 02ba374..6c69f2a 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt @@ -40,7 +40,7 @@ open class MongodbRepository, ID>( this.repo.save(list) } - override fun count(params: Map?): Int { + override fun count(params: Map?): Int { val query = Query() return this.repo.count(query.where(params, clazz), clazz).toInt() } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt index 93a3d87..624911f 100644 --- a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt @@ -54,7 +54,7 @@ open class MongodbUniversalRepository(private var repo: MongoTemplate) : IUniver this.repo.insert(roots, clazz) } - override fun count(params: Map?, clazz: Class): Int { + override fun count(params: Map?, clazz: Class): Int { val query = Query() return this.repo.count(query.where(params, clazz), clazz).toInt() } diff --git a/src/gaea/build.gradle b/src/gaea/build.gradle index 69db5b0..e69de29 100644 --- a/src/gaea/build.gradle +++ b/src/gaea/build.gradle @@ -1,10 +0,0 @@ -publishing { - publications { - publish(MavenPublication) { - group "${project.group}" - artifactId "${project.name}" - version "$version" - from components.java - } - } -} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt index e4c2952..ea530f7 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt @@ -35,7 +35,7 @@ import java.util.logging.Logger * them together as a set ([Dagger](https://dagger.dev/dev-guide/multibindings), [Guice](https://github.com/google/guice/wiki/Multibindings), [Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation)). * * - * To react to messages, we recommend a reactive-streams framework like [RxJava](https://github.com/ReactiveX/RxJava/wiki) (supplemented with its [RxAndroid](https://github.com/ReactiveX/RxAndroid) extension if you are building for + * To react to messages, we recommend a reactive-streams framework Like [RxJava](https://github.com/ReactiveX/RxJava/wiki) (supplemented with its [RxAndroid](https://github.com/ReactiveX/RxAndroid) extension if you are building for * Android) or [Project Reactor](https://projectreactor.io/). (For the basics of * translating code from using a message bus to using a reactive-streams framework, see these two * guides: [1](https://blog.jkl.gg/implementing-an-message-bus-with-rxjava-rxbus/), [2](https://lorentzos.com/rxjava-as-message-bus-the-right-way-10a36bdd49ba).) Some usages @@ -49,7 +49,7 @@ import java.util.logging.Logger * * It makes the cross-references between producer and subscriber harder to find. This can * complicate debugging, lead to unintentional reentrant calls, and force apps to eagerly * initialize all possible subscribers at startup time. - * * It uses reflection in ways that break when code is processed by optimizers/minimizer like + * * It uses reflection in ways that break when code is processed by optimizers/minimizer Like * [R8 and Proguard](https://developer.android.com/studio/build/shrink-code). * * It doesn't offer a way to wait for multiple messages before taking action. For example, it * doesn't offer a way to wait for multiple producers to all report that they're "ready," nor @@ -137,7 +137,7 @@ import java.util.logging.Logger * @author Cliff * @since 10.0 * @param identifier a brief name for this bus, for logging purposes. Should/home/alex/privacy/project/myths/gaea be a valid Java - * @param executor the default executor this event bus uses for dispatching events to subscribers. + * @param executor the Default executor this event bus uses for dispatching events to subscribers. * @param dispatcher message dispatcher. * @param exceptionHandler Handler for subscriber exceptions. */ @@ -159,7 +159,7 @@ open class Bus( * identifier. */ @JvmOverloads - constructor(identifier: String = "default") : this( + constructor(identifier: String = "Default") : this( identifier, Executor { it.run() }, Dispatcher.perThreadDispatchQueue(), @@ -173,7 +173,7 @@ open class Bus( * @since 16.0 */ constructor(exceptionHandler: SubscriberExceptionHandler) : this( - "default", + "Default", Executor { it.run() }, Dispatcher.perThreadDispatchQueue(), exceptionHandler @@ -203,7 +203,7 @@ open class Bus( * @since 16.0 */ constructor(executor: Executor, subscriberExceptionHandler: SubscriberExceptionHandler) : this( - "default", + "Default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler @@ -216,7 +216,7 @@ open class Bus( * down the executor after the last message has been posted to this message bus. */ constructor(executor: Executor) : this( - "default", + "Default", executor, Dispatcher.legacyAsync(), LoggingHandler() diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt index e8fa6cd..30f0e7c 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt @@ -68,7 +68,7 @@ interface IRepository, ID> { * @param params 查询条件。 * @return int */ - fun count(params: Map?): Int + fun count(params: Map?): Int } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt index 8eaca7e..10b3c76 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt @@ -61,5 +61,5 @@ interface IUniversalRepository { * @param params 查询条件。 * @return int */ - fun count(params: Map?, clazz: Class): Int + fun count(params: Map?, clazz: Class): Int } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt index 1f2aa50..5915695 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt @@ -25,7 +25,7 @@ interface IQuery { * @param params 查询条件。 * @return 视图列表 */ - fun list(params: Map?): List + fun list(params: Map?): List /** * 根据条件查询符合条件记录的数量 @@ -33,7 +33,7 @@ interface IQuery { * @param params 查询条件。 * @return 数量 */ - fun count(params: Map?): Int + fun count(params: Map?): Int /** * 根据实体类条件查询所有符合条件记录(分页查询) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt index 05929ab..fc2b962 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt @@ -20,7 +20,7 @@ interface IUniversalQuery { * @param params 查询条件。 * @return 视图列表 */ - fun list(params: Map?, clazz: Class): List + fun list(params: Map?, clazz: Class): List /** * 根据条件查询符合条件记录的数量 @@ -28,7 +28,7 @@ interface IUniversalQuery { * @param params 查询条件。 * @return 数量 */ - fun count(params: Map?, clazz: Class): Int + fun count(params: Map?, clazz: Class): Int /** * 根据实体类条件查询所有符合条件记录(分页查询) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt index 523cf30..501f18e 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt @@ -4,40 +4,45 @@ enum class Operator { /** * 等于 */ - eq, + Eq, /** * 不等于 */ - ne, + Ne, /** * 小于 */ - lt, + Lt, /** * 大于 */ - gt, + Gt, /** * 小于或等于 */ - lte, + Lte, /** * 大于或等于 */ - gte, + Gte, /** * 模糊匹配 */ - like, + Like, + + /** + * 范围内 + */ + Range, /** * 默认查询, 未定义查询方式, 业务人员自己实现查询方式 */ - default + Default } \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt index ed6ed3b..7a2d724 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt @@ -9,7 +9,7 @@ package com.synebula.gaea.query */ data class Params(var page: Int = 1, var size: Int = 10) { - private var _parameters = linkedMapOf() + private var _parameters = linkedMapOf() private var _orders = linkedMapOf() /** @@ -27,41 +27,23 @@ data class Params(var page: Int = 1, var size: Int = 10) { this._orders = value } get() { - if (this._parameters.keys.count { it.startsWith("@") } > 0) { - val params = linkedMapOf() - this._parameters.forEach { - if (it.key.startsWith("@")) { - this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString()) - } else - params[it.key] = it.value - } - this._parameters = params - } + this.filterOrderParams() return this._orders } /** * 查询条件。 */ - var parameters: LinkedHashMap + var parameters: LinkedHashMap set(value) { this._parameters = value } get() { - if (this._parameters.keys.count { it.startsWith("@") } > 0) { - val params = linkedMapOf() - this._parameters.forEach { - if (it.key.startsWith("@")) { - this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value.toString()) - } else - params[it.key] = it.value - } - this._parameters = params - } + this.filterOrderParams() return this._parameters } - constructor(page: Int, size: Int, parameters: LinkedHashMap) : this(page, size) { + constructor(page: Int, size: Int, parameters: LinkedHashMap) : this(page, size) { this.page = page this.size = size this._parameters = parameters @@ -70,7 +52,7 @@ data class Params(var page: Int = 1, var size: Int = 10) { /** * 添加查询条件 */ - fun where(field: String, value: Any): Params { + fun where(field: String, value: String): Params { _parameters[field] = value return this } @@ -82,4 +64,20 @@ data class Params(var page: Int = 1, var size: Int = 10) { _orders[field] = order return this } + + /** + * 过滤参数中的排序字段 + */ + private fun filterOrderParams() { + if (this._parameters.keys.count { it.startsWith("@") } > 0) { + val params = linkedMapOf() + this._parameters.forEach { + if (it.key.startsWith("@")) { + this._orders[it.key.removePrefix("@")] = Order.valueOf(it.value) + } else + params[it.key] = it.value + } + this._parameters = params + } + } } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt index 95e1dce..588f8fb 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt @@ -1,5 +1,6 @@ package com.synebula.gaea.reflect +import java.lang.reflect.Field import java.lang.reflect.ParameterizedType /** @@ -24,5 +25,27 @@ fun Class<*>.getGenericInterface(interfaceClazz: Class<*>): ParameterizedType? { val type = this.genericInterfaces.find { it.typeName.startsWith(interfaceClazz.typeName) } return if (type == null) null else type as ParameterizedType - } + + +/** + * 查找类字段, 可以查找包括继承类的私有字段 + * + * @param name 字段名称 + * @return 字段类型 + */ +fun Class<*>.findField(name: String): Field? { + var field: Field? = null + for (f in this.declaredFields) { + if (f.name == name) { + field = f + } + } + if (field == null) { + val superclass = this.superclass + if (superclass != Any::class.java) { + field = superclass.findField(name) + } + } + return field +} \ No newline at end of file