Atomikos+jdbcdslog——分布式事务管理输出实际log日志

前言

    想看实际产生的SQL,在一个数据源的情况下,最简单的方式是使用Log4jdbc。

    但在spring-data-jpa通过Atomikos实现JTA事务中,我们通过Atomikos实现了分布式事务,配置的是支持XA的DataSource,Log4jdbc这种在Driver上做文章的方法肯定不行。

    这里使用jdbcdslog的衍生项目jdbcdslog-exp来实现这个目标。jdbcdslog-exp比jdbcdslog更进了一步,输出的SQL,可以直接拷贝到PL/SQL等工具下执行。

依赖

jdbcdslog的依赖如下

<dependency>
    <groupId>com.googlecode.usc</groupId>
    <artifactId>jdbcdslog</artifactId>
    <version>1.0.6.2</version>
</dependency>

配置方式

properties配置信息

这里以Oracle为例,properties内容如下,当然dataSource配置信息等需要两份

dev.jdbc.dataSource=org.jdbcdslog.ConnectionPoolXADataSourceProxy
dev.jdbc.url=jdbc:oracle:thin:@192.168.3.129:1521:gtf?targetDS=oracle.jdbc.xa.client.OracleXADataSource
dev.jdbc.username=adp_dev
dev.jdbc.password=adp_dev

 其中url相当于url + targetDS ,targetDS的值为普通方式下的dataSource。

配置文件

一个数据源的配置方式如下,另一个同理。其它配置参照spring-data-jpa通过Atomikos实现JTA事务

<bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
    destroy-method="close">
    <property name="uniqueResourceName" value="XA1DBMS" />
    <property name="xaDataSourceClassName" value="${dev.jdbc.dataSource}" />
    <property name="xaProperties">
        <props>
            <prop key="URL">${dev.jdbc.url}</prop>
            <prop key="user">${dev.jdbc.username}</prop>
            <prop key="password">${dev.jdbc.password}</prop>
        </props>
    </property>
    <property name="poolSize" value="10" />
    <property name="minPoolSize" value="10" />
    <property name="maxPoolSize" value="30" />
</bean>

问题

实际运行时会发生错误,原因是由于AtomikosDataSourceBean中doInit方法中在该方法的最后才调用的PropertyUtils.setProperties(xaDataSource, xaProperties );这段代码,导致上面调用setLogWriter的时候缺少参数。

解决方法

1.提供一个无视set/get方法的反射工具;

2.AtomikosDataSourceBean中用到的参数AtomikosXAConnectionFactory 没有访问修饰符,无法访问的问题;

 3.重新实现一个AtomikosDataSourceBean类,并重写doInit方法。

(1)set/get方法的反射工具

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/**
 * 反射工具类.
 * 
 * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
 * 
 * @author calvin
 */
public class Reflections {

	private static Logger logger = LoggerFactory.getLogger(Reflections.class);

    /**
     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
     */
    public static Object getFieldValue(final Object obj, final String fieldName) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }

        Object result = null;
        try {
            result = field.get(obj);
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常{}", e.getMessage());
        }
        return result;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
     *
     * 如向上转型到Object仍无法找到, 返回null.
     */
    public static Field getAccessibleField(final Object obj, final String fieldName) {
        Validate.notNull(obj, "object can't be null");
        Validate.notBlank(fieldName, "fieldName can't be blank");
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                makeAccessible(field);
                return field;
            } catch (NoSuchFieldException e) {//NOSONAR
                // Field不在当前类定义,继续向上转型
            }
        }
        return null;
    }

    /**
     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Field field) {
        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
                .isFinal(field.getModifiers())) && !field.isAccessible()) {
            field.setAccessible(true);
        }
    }
}

(2)MyAtomikosXAConnectionFactory

解决自定义的AtomikosDataSourceBean无法访问问题,创建一个MyAtomikosXAConnectionFactory类,代码同AtomikosXAConnectionFactory完全一致;

(3)MyAtomikosDataSourceBean的doInit()方法

    protected com.atomikos.datasource.pool.ConnectionFactory doInit() throws Exception {
        if (xaDataSource == null) {
            if (xaDataSourceClassName == null)
                throwAtomikosSQLException("Property 'xaDataSourceClassName' cannot be null");
            if (xaProperties == null)
                throwAtomikosSQLException("Property 'xaProperties' cannot be null");
        }

        if (LOGGER.isInfoEnabled())
            LOGGER.logInfo(this + ": initializing with [" + " xaDataSourceClassName=" + xaDataSourceClassName
                    + "," + " uniqueResourceName=" + getUniqueResourceName() + "," + " maxPoolSize="
                    + getMaxPoolSize() + "," + " minPoolSize=" + getMinPoolSize() + ","
                    + " borrowConnectionTimeout=" + getBorrowConnectionTimeout() + "," + " maxIdleTime="
                    + getMaxIdleTime() + "," + " reapTimeout=" + getReapTimeout() + ","
                    + " maintenanceInterval=" + getMaintenanceInterval() + "," + " testQuery="
                    + getTestQuery() + "," + " xaProperties=" + printXaProperties() + "," + " loginTimeout="
                    + getLoginTimeout() + "," + " maxLifetime=" + getMaxLifetime() + "]");

        if (xaDataSource == null) {
            Class xadsClass = null;
            try {
                xadsClass = ClassLoadingHelper.loadClass(getXaDataSourceClassName());
            } catch (ClassNotFoundException nf) {
                AtomikosSQLException
                        .throwAtomikosSQLException(
                                "The class '"
                                        + getXaDataSourceClassName()
                                        + "' specified by property 'xaDataSourceClassName' could not be found in the classpath. Please make sure the spelling is correct, and that the required jar(s) are in the classpath.",
                                nf);

            }
            Object driver = xadsClass.newInstance();
            if (!(driver instanceof XADataSource)) {
                AtomikosSQLException
                        .throwAtomikosSQLException("The class '"
                                + getXaDataSourceClassName()
                                + "' specified by property 'xaDataSourceClassName' does not implement the required interface javax.jdbc.XADataSource. Please make sure the spelling is correct, and check your JDBC driver vendor's documentation.");
            }
            xaDataSource = (XADataSource) driver;
            PropertyUtils.setProperties(xaDataSource, xaProperties);
            ConnectionPoolXADataSourceProxy proxy = (ConnectionPoolXADataSourceProxy) xaDataSource;
            XADataSource targetDs = (XADataSource) Reflections.getFieldValue(proxy, "targetDS");
            targetDs.setLoginTimeout(getLoginTimeout());
            // xaDataSource.setLoginTimeout ( getLoginTimeout() );
            targetDs.setLogWriter(getLogWriter());
        }

        JdbcTransactionalResource tr = new JdbcTransactionalResource(getUniqueResourceName(), xaDataSource);
        com.atomikos.datasource.pool.ConnectionFactory cf = new MyAtomikosXAConnectionFactory(xaDataSource,
                tr, this);
        Configuration.addResource(tr);

        return cf;
    }

(4)logback配置文件

<logger name="org.jdbcdslog.ConnectionLogger" level="INFO" />
<logger name="org.jdbcdslog.StatementLogger" level="INFO" />
<logger name="org.jdbcdslog.ResultSetLogger" level="INFO" />

看名就知道输出的是哪一部分数据,如果只看SQL,用org.jdbcdslog.StatementLogger就够了。
 

至此,Atomikos+jdbcdslog配置成功

猜你喜欢

转载自sgq0085.iteye.com/blog/2039534