多数据源事务提交

本文展示的代码均以Spring为基础,使用切面的方式进行多数据源事务提交。

1. 数据源配置

定义了2个数据源和2个事务管理器。

<!-- 定义数据源 -->
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url2}" />
    <property name="username" value="${jdbc.username2}" />
    <property name="password" value="${jdbc.password2}" />
</bean>

<!-- 定义事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource2"/>
</bean>

2. 编写切面相关代码

2.1 自定义注解

此注解用于方法上面,进行事务关联。

import java.lang.annotation.ElemntType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
public @interface MultiTxManager {
    /**
     * 事务管理器名称
     */
    String[] txManagerNames() default {};
}

2.2 切面代码

用于事务提交和回滚。事务管理器的2大作用:提交和回滚。


import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Stack;

@Aspect
@Component
public class SelfTxManagerAspect {

    @Resource
    private ApplicationContext context;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(MultiTxManager)")
    public void pointCut() {
    }

    /**
     * 环绕
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        MultiTxManager tm = method.getAnnotation(MultiTxManager.class);

        // 事务管理器
        Stack<DataSourceTransactionManager> tmStack = new Stack<>();
        // 事务状态
        Stack<TransactionStatus> tsStack = new Stack<>();

        // 是否需要开启事务标识
        boolean enableTransaction = true;
        if (tm.txManagerNames().length < 1) {
            enableTransaction = false;
        }

        if (enableTransaction ) {
            for(String tmName : tm.txManagerNames()) {
                DataSourceTransactionManager dtxm = context.getBean(tmName,DataSourceTransactionManager.class);
                if (dtxm == null) { continue; }
                // 开启事务
                TransactionStatus ts = dtxm.getTransaction(new DefaultTransactionDefinition());
                tmStack.push(dtxm);
                tsStack.push(ts);
            }
        }
        if (CollectionUtils.isEmpty(tmStack)) {
            enableTransaction = false;
        }
        try {
            Object result = point.proceed();
            // 方法执行完成,若有事务则提交
            while (enableTransaction && !tmStack.isEmpty()) {
                DataSourceTransactionManager manager = tmStack.pop();
                TransactionStatus status = tsStack.pop();
                manager.commit(status);
            }
            return result;
        } catch (Throwable e) {
            // 发生异常时回滚
            while (enableTransaction && !tmStack.isEmpty()) {
                DataSourceTransactionManager manager = tmStack.pop();
                TransactionStatus status = tsStack.pop();
                manager.rollback(status);
            }
            throw e;
        }
    }
}

2.3 在方法上应用


public class xxxxServiceImpl {

    @MultiTxManager(txManagerNames={"txManager", "txManager2"})
    public Object saveOrUpdate() {
        // 做一些数据库更新操作
    }
}

如有问题请留言。

flex的开始状态

是在flex定义部分中声明,开始状态(start states),也称为 开始条件(start conditions) 或者 开始规则(start rules)。

开始状态的作用

是用来限制特定规则的作用范围,或者针对文件的部分内容来改变扫描器的工作方式。即根据开始状态激活对应的规则(rules)。

开始状态的模式

开始状态有2种模式:

  1. %s :inclusive (包含模式),它允许未标记任何开始状态的rule 可以进行匹配。
  2. %x:exclusive(独占模式),它只允许标记为该开始状态下的rule 进行匹配。通常独占模式更有用。

注意:在 flex定义部分中声明时 使用非缩进的形式。例如:

/* definitions 定义部分 */
%{

%}
%s simple
%x test
%%
/*rule action  规则和执行动作 部分*/
<simple>[0-9]+ {}
<test>[a-z]+ {}
%%
/* helper function 辅助函数部分*/

注意在规则部分中:开始状态名称和rule之间是没有空格的

开始状态的激活

开始状态的激活需要使用:宏 BEGIN 进行激活。BEGIN的语法:

BEGIN  statename;

statename 是使用 %x 或 %s 声明的开始状态名称。Flex默认的开始状态是 零 状态,也称为 INITIAL。BEGIN(0)等价于 BEGIN(INITIAL)。

注意:宏 BEGIN 本身是没有任何参数的,状态名字也不应该被括号括起来,但是加括号是一种良好的风格。

示例

分别使用2种状态模式对字符串数字”123.45″进行识别:

%{
    #include <stdio.h>
    #include <math.h>
%}

%s expect

%%

"expect-floats" BEGIN(expect);

<expect>[0-9]+"."[0-9]+ {
    printf("found a float,=%f\n", atof(yytext));
}

<expect>\n {
    /* BEGIN(INITIAL); */
}

[0-9]+ {
    printf("found an integer, = %d\n", atoi(yytext));
}

"." {
    printf("found a dot \n");
}

%%

int main()
{
    yylex();
    return 1;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

project(startConditionTest LANGUAGES C)

find_package(FLEX)

FLEX_TARGET(myscanner startcondition_test.l ${CMAKE_CURRENT_BINARY_DIR}/startConditionTest.lex.c)

# 设置主要包含的c文件
set(MAIN_SRC ${CMAKE_CURRENT_BINARY_DIR}/startConditionTest.lex.c)

# 使用源文件生成可以执行文件
add_executable(conditionTest01 ${MAIN_SRC})

# libfl.a
find_library(LEX_LIB fl)
TARGET_LINK_LIBRARIES(conditionTest01 ${LEX_LIB})

包含模式%s

使用包含模式:%s expect.

编译并执行:

mkdir build
cd build
cmake ../
make
# 执行
./conditionTest01 
123.45
found an integer, = 123
found a dot 
found an integer, = 45

"expect-floats" 123.45
"" found a float,=123.450000
123
found an integer, = 123

执行结果如下图所示:

独占模式%x

使用独占模式:%x expect

编译执行:

└─# ./conditionTest01 
123.456
found an integer, = 123
found a dot 
found an integer, = 456

"expect-floats"
""123.45
found a float,=123.450000
123456
123456

789
789

如下图所示:

总结

从执行结果上面可以看到:

  1. 独占模式被激活时,没有标记任何开始状态的rule,将不会被匹配。
  2. 包含模式被激活后,未标记开始状态的rule 也是可以进行匹配的。
  3. 和开始状态的模式小节中描述的是一致的。