Browse Source

多数据源事务统一管理,事务在切数据源之后拦截

NorthLan 7 years ago
parent
commit
7c112d8f37

+ 0 - 19
src/main/resources/mapping/bus/SysDicMapper.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.gxzc.zen.api.bus.mapper.SysDicMapper">
-
-    <!-- 通用查询映射结果 -->
-    <resultMap id="BaseResultMap" type="com.gxzc.zen.api.bus.model.SysDic">
-    <result column="id" property="id" />
-    <result column="enable" property="enable" />
-    <result column="remark" property="remark" />
-    <result column="create_time" property="createTime" />
-    <result column="create_by" property="createBy" />
-    <result column="update_time" property="updateTime" />
-    <result column="update_by" property="updateBy" />
-        <result column="key" property="key" />
-        <result column="value" property="value" />
-        <result column="sort" property="sort" />
-    </resultMap>
-
-</mapper>

+ 3 - 0
zen-api/src/main/kotlin/com/gxzc/zen/api/sys/mapper/SysDeptMapper.kt

@@ -2,6 +2,8 @@ package com.gxzc.zen.api.sys.mapper
 
 import com.gxzc.zen.api.sys.model.SysDept
 import com.gxzc.zen.common.base.BaseMapper
+import org.springframework.stereotype.Repository
+
 /**
  * <p>
  * 部门管理 Mapper 接口
@@ -10,4 +12,5 @@ import com.gxzc.zen.common.base.BaseMapper
  * @author NorthLan123
  * @since 2018-01-30
  */
+@Repository
 interface SysDeptMapper : BaseMapper<SysDept>

+ 33 - 0
zen-orm/src/main/kotlin/com/gxzc/zen/orm/DataSourceSwitchMethodInterceptor.kt

@@ -0,0 +1,33 @@
+package com.gxzc.zen.orm
+
+import com.gxzc.zen.orm.annotation.DynamicDataSource
+import com.gxzc.zen.orm.contants.DSKey
+import org.aopalliance.intercept.MethodInterceptor
+import org.aopalliance.intercept.MethodInvocation
+import java.lang.reflect.Method
+
+/**
+ *
+ * @author NorthLan at 2018/1/31
+ */
+class DataSourceSwitchMethodInterceptor : MethodInterceptor {
+    override fun invoke(invocation: MethodInvocation): Any {
+        val packageName = invocation.`this`::class.java.`package`.name
+        matchDataSource(invocation.method, packageName)
+        return invocation.proceed()
+    }
+
+    private fun matchDataSource(method: Method, packageName: String) {
+        DSKey.values()
+                .filter { it.pkgName in packageName }
+                .forEach { setDataSource(method, it.dsKey) }
+    }
+
+    /**
+     * 设置数据源key
+     */
+    private fun setDataSource(method: Method, defaultKey: String) {
+        val dynamicDataSource: DynamicDataSource? = method.getAnnotation(DynamicDataSource::class.java)
+        DynamicMultipleDataSource.setDataSource(dynamicDataSource?.value ?: defaultKey)
+    }
+}

+ 3 - 3
zen-orm/src/main/kotlin/com/gxzc/zen/orm/DynamicMultipleDataSource.kt

@@ -25,8 +25,8 @@ class DynamicMultipleDataSource : AbstractRoutingDataSource() {
     }
 
     override fun determineCurrentLookupKey(): Any {
-        val lookupKey = DATA_SOURCE_KEY.get()
-        clear()
-        return lookupKey
+        return DATA_SOURCE_KEY.get()
+//        clear()
+//        return lookupKey
     }
 }

+ 23 - 2
zen-orm/src/main/kotlin/com/gxzc/zen/orm/aspect/DataSourceSwitchAspect.kt

@@ -4,6 +4,8 @@ import com.gxzc.zen.orm.DynamicMultipleDataSource
 import com.gxzc.zen.orm.annotation.DynamicDataSource
 import com.gxzc.zen.orm.contants.DSKey
 import org.aspectj.lang.JoinPoint
+import org.aspectj.lang.ProceedingJoinPoint
+import org.aspectj.lang.annotation.Around
 import org.aspectj.lang.annotation.Aspect
 import org.aspectj.lang.annotation.Before
 import org.aspectj.lang.annotation.Pointcut
@@ -19,13 +21,15 @@ import java.lang.reflect.Method
  */
 @Suppress("unused")
 @Aspect
-@Order(-1) // 保证先于事务执行
+@Order(-100) // 保证先于事务执行
 @Component
 class DataSourceSwitchAspect {
     companion object {
         private val logger = LoggerFactory.getLogger(DataSourceSwitchAspect::class.java)
     }
 
+    private var isAnnotationAspect = false
+
     @Pointcut("execution(* com.gxzc.zen.api..*Service.*(..))")
     fun zenServicePointCut() {
     }
@@ -36,13 +40,30 @@ class DataSourceSwitchAspect {
 
     @Pointcut("execution(* com.baomidou.mybatisplus..*Mapper.*(..))")
     fun mpMapperPointCut() {
+    }
 
+    @Pointcut("@annotation(com.gxzc.zen.orm.annotation.DynamicDataSource)")
+    fun annotationCut() {
     }
 
-    // @Pointcut("execution(* Controller.*(..))")
+    @Around("annotationCut()")
+    fun annotationAround(joinPoint: ProceedingJoinPoint): Any? {
+        isAnnotationAspect = true
+        val method = (joinPoint.signature as MethodSignature).method
+        setDataSource(method, DSKey.DSKEY_SYS)
+        try {
+            return joinPoint.proceed()
+        } finally {
+            isAnnotationAspect = false
+            DynamicMultipleDataSource.clear()
+        }
+    }
 
     @Before("zenServicePointCut() || mpServicePointCut() || mpMapperPointCut()")
     fun dynamicDataSource(joinPoint: JoinPoint) {
+        if (isAnnotationAspect) {
+            return
+        }
         val target = joinPoint.target
 
         val packageName: String = try {

+ 138 - 0
zen-orm/src/main/kotlin/com/gxzc/zen/orm/config/TransactionalConfig.kt

@@ -0,0 +1,138 @@
+//package com.gxzc.zen.orm.config
+//
+//import com.gxzc.zen.orm.DataSourceSwitchMethodInterceptor
+//import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator
+//import org.springframework.context.annotation.Bean
+//import org.springframework.context.annotation.Configuration
+//import org.springframework.transaction.PlatformTransactionManager
+//import org.springframework.transaction.TransactionDefinition
+//import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource
+//import org.springframework.transaction.interceptor.RollbackRuleAttribute
+//import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
+//import org.springframework.transaction.interceptor.TransactionInterceptor
+//
+//
+///**
+// * 事务配置 支持动态数据源
+// * @author NorthLan at 2018/1/31
+// */
+//@Configuration
+//class TransactionalConfig {
+//    companion object {
+//        private const val CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME = "customizeTransactionInterceptor"
+//        private const val DATA_SOURCE_SWITCH_METHOD_INTERCEPTOR_NAME = "dataSourceSwitchMethodInterceptor"
+//        /**
+//         * 默认只对 "*Service" , "*ServiceImpl" Bean 进行事务处理,"*"表示模糊匹配, 比如 : userService,orderServiceImpl
+//         */
+//        private val DEFAULT_TRANSACTION_BEAN_NAMES = arrayOf("*Service", "*ServiceImpl", "*Mapper")
+//        /**
+//         * 可传播事务配置
+//         */
+//        private val DEFAULT_REQUIRED_METHOD_RULE_TRANSACTION_ATTRIBUTES = arrayOf(
+//                "add*",
+//                "save*",
+//                "insert*",
+//                "delete*",
+//                "update*",
+//                "edit*",
+//                "batch*",
+//                "create*",
+//                "remove*"
+//        )
+//        /**
+//         * 默认的只读事务
+//         */
+//        private val DEFAULT_READ_ONLY_METHOD_RULE_TRANSACTION_ATTRIBUTES = arrayOf(
+//                "get*",
+//                "count*",
+//                "find*",
+//                "query*",
+//                "select*",
+//                "list*",
+//                "*"
+//        )
+//    }
+//
+//    /**
+//     * 自定义事务 BeanName 拦截
+//     */
+//    private val customizeTransactionBeanNames = arrayOf<String>()
+//    /**
+//     * 自定义方法名的事务属性相关联,可以使用通配符(*)字符关联相同的事务属性的设置方法; 只读事务
+//     */
+//    private val customizeReadOnlyMethodRuleTransactionAttributes = arrayOf<String>()
+//    /**
+//     * 自定义方法名的事务属性相关联,可以使用通配符(*)字符关联相同的事务属性的设置方法;
+//     * 传播事务(默认的)[org.springframework.transaction.annotation.Propagation.REQUIRED]
+//     */
+//    private val customizeRequiredMethodRuleTransactionAttributes = arrayOf<String>()
+//
+//    /**
+//     * 配置事务拦截器
+//     * @param platformTransactionManager 事务管理器
+//     */
+//    @Bean(CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME)
+//    fun customizeTransactionInterceptor(platformTransactionManager: PlatformTransactionManager): TransactionInterceptor {
+//        val transactionAttributeSource = NameMatchTransactionAttributeSource()
+//        val readOnly = this.readOnlyTransactionRule()
+//        val required = this.requiredTransactionRule()
+//        // 默认的只读事务配置
+//        for (methodName in DEFAULT_READ_ONLY_METHOD_RULE_TRANSACTION_ATTRIBUTES) {
+//            transactionAttributeSource.addTransactionalMethod(methodName, readOnly)
+//        }
+//        // 默认的传播事务配置
+//        for (methodName in DEFAULT_REQUIRED_METHOD_RULE_TRANSACTION_ATTRIBUTES) {
+//            transactionAttributeSource.addTransactionalMethod(methodName, required)
+//        }
+//        // 定制的只读事务配置
+//        for (methodName in customizeReadOnlyMethodRuleTransactionAttributes) {
+//            transactionAttributeSource.addTransactionalMethod(methodName, readOnly)
+//        }
+//        // 定制的传播事务配置
+//        for (methodName in customizeRequiredMethodRuleTransactionAttributes) {
+//            transactionAttributeSource.addTransactionalMethod(methodName, required)
+//        }
+//        return TransactionInterceptor(platformTransactionManager, transactionAttributeSource)
+//    }
+//
+////    @Bean
+////    fun beanNameAutoProxyCreator(): BeanNameAutoProxyCreator {
+////        return BeanNameAutoProxyCreator().also {
+////            it.setInterceptorNames(DATA_SOURCE_SWITCH_METHOD_INTERCEPTOR_NAME,
+////                    CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME)
+////            // 归集
+////            it.setBeanNames(*arrayListOf<String>().also {
+////                it.addAll(DEFAULT_TRANSACTION_BEAN_NAMES) // 默认
+////                it.addAll(customizeTransactionBeanNames) // 定制
+////            }.toTypedArray())
+////            it.isProxyTargetClass = true
+////        }
+////    }
+////
+////    @Bean(DATA_SOURCE_SWITCH_METHOD_INTERCEPTOR_NAME)
+////    fun dataSourceSwitchMethodInterceptor(): DataSourceSwitchMethodInterceptor {
+////        return DataSourceSwitchMethodInterceptor()
+////    }
+//
+//    /**
+//     * 支持当前事务
+//     * 如果不存在创建一个新的
+//     */
+//    private fun requiredTransactionRule(): RuleBasedTransactionAttribute {
+//        return RuleBasedTransactionAttribute().also {
+//            it.rollbackRules = arrayListOf(RollbackRuleAttribute(Exception::class.java))
+//            it.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
+//            it.timeout = TransactionDefinition.TIMEOUT_DEFAULT
+//        }
+//    }
+//
+//    /**
+//     * 只读事务
+//     */
+//    private fun readOnlyTransactionRule(): RuleBasedTransactionAttribute {
+//        return RuleBasedTransactionAttribute().also {
+//            it.isReadOnly = true
+//            it.propagationBehavior = TransactionDefinition.PROPAGATION_NOT_SUPPORTED
+//        }
+//    }
+//}

+ 6 - 6
zen-orm/src/main/resources/application-orm.yml

@@ -35,9 +35,9 @@ spring:
 datasource:
   sys:
     name: archives_sys
-    url: jdbc:mysql://192.168.1.124:3307/archives_sys?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
-    username: archives
-    password: archives
+    url: jdbc:mysql://127.0.0.1:3306/archives_sys?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
+    username: root
+    password: root
     testWhileIdle: true
     testOnBorrow: false
     testOnReturn: false
@@ -54,9 +54,9 @@ datasource:
     minEvictableIdleTimeMillis: 30000
   bus:
     name: archives_receive
-    url: jdbc:mysql://192.168.1.124:3307/archives_receive?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
-    username: archives
-    password: archives
+    url: jdbc:mysql://127.0.0.1:3306/archives_rec?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
+    username: root
+    password: root
     testWhileIdle: true
     testOnBorrow: false
     testOnReturn: false

+ 2 - 2
zen-orm/src/main/resources/templates/entity.kt.vm

@@ -12,9 +12,9 @@ import ${pkg}
  * @author ${author}
  * @since ${date}
  */
-#if(${table.convert})
+###if(${table.convert})
 @TableName("${table.name}")
-#end
+###end
 data class ${entity}(
 ## ----------  BEGIN 字段循环遍历  ----------
 #foreach($field in ${table.fields})

+ 34 - 4
zen-web/src/main/kotlin/com/gxzc/zen/controller/ExampleController.kt

@@ -1,14 +1,44 @@
 package com.gxzc.zen.controller
 
+import com.gxzc.zen.api.sys.mapper.SysDeptMapper
+import com.gxzc.zen.api.sys.model.SysDept
+import com.gxzc.zen.api.sys.service.ISysDeptService
+import com.gxzc.zen.orm.annotation.DynamicDataSource
+import com.gxzc.zen.orm.contants.DSKey
 import org.slf4j.LoggerFactory
-import org.springframework.stereotype.Controller
-import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.transaction.annotation.Transactional
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
 
 
-@Controller
-@RequestMapping("example")
+@RestController
 class ExampleController {
     companion object {
         private val logger = LoggerFactory.getLogger(ExampleController::class.java)
     }
+
+    @Autowired
+    lateinit var sysDeptService: ISysDeptService
+
+    @Autowired
+    lateinit var sysDeptMapper: SysDeptMapper
+
+    @GetMapping("test")
+    @DynamicDataSource(DSKey.DSKEY_SYS)
+    @Transactional
+    fun test() {
+//        logger.info("a: {}", sysDeptService.selectCount(EntityWrapper<SysDept>().where("1","1000L")))
+//        logger.info("b: {}", sysDeptMapper.selectCount(EntityWrapper<SysDept>()))
+        sysDeptService.insert(SysDept().also {
+            it.deptName = "ahaha"
+            it.sort = Integer(2)
+            it.principal = "a"
+        })
+        sysDeptService.insert(SysDept().also {
+            it.deptName = "我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjf我日你哥slkdfjslkdjflksdjfklsdjklfsjdlkfjsdlkfjsdlkfjlksdjflksdjfklsdjfklsdjklfsjdklfjsdklfjslkdjfklsdjfklsdjkflsjdklfjskldfjklsdjflksjf;lkasjdkl;fjasdlkf"
+            it.principal = "aaa"
+            it.sort = Integer(1)
+        })
+    }
 }

+ 1 - 1
zen-web/src/main/resources/application.yml

@@ -21,4 +21,4 @@ spring:
     proxy-target-class: true #false为启用jdk默认动态代理,true为cglib动态代理
     auto: true
 logging:
-  level: info
+  level: debug

+ 10 - 0
zen-web/src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+|~) _    _  _ _|  |~)  .
+|~ (_)VV(/_| (_|  |_)\/.
+                     /${AnsiColor.BRIGHT_YELLOW}
+                        _______   __            ___________ ______
+                        _____  | / /______________  /___  /____  / ______ _______
+                        ____   |/ /_  __ \_  ___/  __/_  __ \_  /  _  __ `/_  __ \
+                        ___  /|  / / /_/ /  /   / /_ _  / / /  /___/ /_/ /_  / / /
+                        __/_/ |_/  \____//_/    \__/ /_/ /_//_____/\__,_/ /_/ /_/${AnsiColor.CYAN}
+Application Version: ${application.version}${application.formatted-version}
+Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}