前言
想看实际产生的SQL,在一个数据源的情况下,最简单的方式是使用Log4jdbc。
但在spring-data-jpa通过Atomikos实现JTA事务中,我们通过Atomikos实现了分布式事务,配置的是支持XA的DataSource,Log4jdbc这种在Driver上做文章的方法肯定不行。
这里使用jdbcdslog的衍生项目jdbcdslog-exp来实现这个目标。jdbcdslog-exp比jdbcdslog更进了一步,输出的SQL,可以直接拷贝到PL/SQL等工具下执行。
依赖
jdbcdslog的依赖如下
com.googlecode.usc jdbcdslog 1.0.6.2
配置方式
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事务
${dev.jdbc.url} ${dev.jdbc.username} ${dev.jdbc.password}
问题
实际运行时会发生错误,原因是由于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配置文件
看名就知道输出的是哪一部分数据,如果只看SQL,用org.jdbcdslog.StatementLogger就够了。
至此,Atomikos+jdbcdslog配置成功