前言
想看实际产生的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配置成功