我们都知道,编程语言在与数据库打交道的时候,中间需要一个Driver,这个驱动,一般由数据库厂商开发提供,根据不同的数据库版本而提供不同版本的驱动,Oracle数据库对Java的版本就有好几种:比如Ojdbc14.jar等,这些驱动,通常实现了有JSR定义好的接口规范,比如Java.sql.connection,java.sql.Driver;接口等。
public interface Connection extends Wrapper {
Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql)throws SQLException;
CallableStatement prepareCall(String sql) throws SQLException;
在Java通过JDBC驱动与Oracle数据的交互过程中,有没有办法在驱动层面实现对Sql语句执行过程的监控呢?利用Java的动态代理即可以实现。Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。我们知道,通过动态代理,可以拦截请求(或则说对请求做增强),那么我们通过这一点可以对JDBC的执行过程做增强处理。
JDBC在和Oracle交互的过程,主要经过以下几个步骤:
首先获取连接 getConnection,获取连接的过程需要注册驱动
public class DriverLoggingProxy implements Driver {
static final Logger logger = LoggerFactory.getLogger(DriverLoggingProxy.class);
static final String urlPrefix = "jdbc:jdbcdslog:";
static final String targetDriverParameter = "targetDriver";
private static DriverLoggingProxy defaultDriver;
static {
try {
if(defaultDriver == null)
{
defaultDriver = new DriverLoggingProxy();
DriverManager.registerDriver(defaultDriver);
}
}catch (Exception exception) {
}
}
接着:构造Statement对象
然后:执行Execute
最后:获取结果,提交事务(Commit)
大致可可以分为这四个步骤
我们知道JDBC对数据库的处理构造Statement对象主要提供了三种接口方式:如下
createStatement() //最容用的方式,缺点容易被注入攻击
prepareStatement(String sql) //防止SQL注入
prepareCall(String sql) //存储过程调用
利用Java的动态代理,我们则可以实现在Execute的前后增加业务逻辑,来实现我们对Sql语句的执行时间的一个监控(也就是Sql的性能监控)
public class StatementLoggingProxy implements InvocationHandler {
static final Logger logger = LoggerFactory.getLogger(StatementLoggingProxy.class);
Object targetStatement = null;
static List executeMethods = Arrays.asList(new String[] {
"execute", "executeQuery", "executeUpdate","executeBatch" });
public StatementLoggingProxy(Object statement) {
targetStatement = statement;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object r = null;
try {
boolean toLog = (StatementLogger.logger.isInfoEnabled() || SlowQueryLogger.logger
.isInfoEnabled())
&& executeMethods.contains(method.getName());
long t1 = 0;
if (toLog)
t1 = System.currentTimeMillis();
if(executeMethods.contains(method.getName())){
ConnectionAspectFacade.createStatementAdvice(this.targetStatement);
}
r = method.invoke(targetStatement, args);
if (toLog) {
long t2 = System.currentTimeMillis();
StringBuffer sb = LogUtils.createLogEntry(method, args[0],
null, null);
long time = t2 - t1;
String logEntry = sb.append(" ").append(time).append(" ms.")
.toString();
StatementLogger.logger.info(logEntry);
if (time >= ConfigurationParameters.slowQueryThreshold)
SlowQueryLogger.logger.info(logEntry);
}
return r;
} catch (Throwable t) {
LogUtils.handleException(t, StatementLogger.logger, LogUtils
.createLogEntry(method, args[0], null, null));
}
return r;
}
同时,也可以利用Oracle的Identifi_context来实现Sql语句与执行的终端绑定,在Oracle的Session列表里有一个Identifi字段,可以通过JDBC 驱动的setEndToEndMetrics 方法来设置值(浏览器端的身份信息),这样,任何一个SQL语句执行的时候,Oracle都能知道是哪个终端发起的,那么我要怎么才知道浏览器端的人是谁呢?这是一个身份识别的问题,利用Filter就可以实现这一点。
我们BS程序通常都是有Session的,用来存放登录的身份相关信息
在向Server发起Http请求的时候,身份信息是要被验证的,当我们的身份信息在登录的时候,验证成功,那么身份信息会被保存到Session里面,我们在Web程序最前面加入一个Filter(拦截器),拦截Http请求,当有请求发生的时候,首先我们可以拿到他的Ip地址
HttpServletRequest request = (HttpServletRequest) req;
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && !"".equals(ip)) {
logger.debug("origin ip:" + ip + " will replace proxy ip:"
+ req.getRemoteAddr());
} else {
ip = req.getRemoteAddr();
}
return ip;
X-Forwarded-For //这里指硬件的负载均衡(F5,Array,RedWare等)需要配置的参数
而后,我们可以采用Session迭代的方式,迭代出当前的Session信息,通过Session信息枚举的分析,分析出Session的结构(这个步骤需要测试阶段完成),然后记录下来,在每次Http请求的过程中,根据分析到的Session结构去获取Session信息(这里我们其实主要要取的是Username),加上前面的Ip地址,这样就可以确定到底是哪个”人“了。
拿到人以后,需要送去给后台的JDBC驱动,以至于让Oracle也知道这条SQL语句是谁执行的,那么我们可以通过 ThreadLocal identityContexts = new ThreadLocal() 绑定实现,把身份信息通过ThreadLocal与后台Http请求绑定,通过
setEndToEndMetrics方法传递给Jdbc的Connection
示例代码:
public class GenericLoggingProxy implements InvocationHandler {
static final Logger logger = LoggerFactory.getLogger(GenericLoggingProxy.class);
static List methodsBlackList = Arrays.asList(new String[] {
"getAutoCommit", "getCatalog", "getTypeMap", "clearWarnings",
"setAutoCommit", "getFetchSize", "setFetchSize", "commit" });
private Object target = null;
public GenericLoggingProxy(Object target) {
this.target = target;
}
public GenericLoggingProxy(Object target, String sql) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object r=null;
try {
r = method.invoke(target, args);
if ("getConnection".equals(method.getName())) {
r = (Connection) ConnectionLoggingProxy.wrap((Connection) r);
return r;
}
if (method.getName().equals("prepareCall")
|| method.getName().equals("prepareStatement"))
r = wrap(r, (String) args[0]);
else
r = wrap(r, null);
return r;
} catch (Throwable t) {
LogUtils.handleException(t, ConnectionLogger.logger, LogUtils
.createLogEntry(method, null, null, null));
}
return r;
}
if (r instanceof CallableStatement)
return wrapByCallableStatementProxy(r, sql);
if (r instanceof PreparedStatement)
return wrapByPreparedStatementProxy(r, sql);
if (r instanceof Statement)
return wrapByStatementProxy(r);
// if (r instanceof ResultSet)
// return ResultSetLoggingProxy.wrapByResultSetProxy((ResultSet) r);
return r;
Action
This is a String
ClientId
This is a String
ExecutionContextId
This is a combination of String and short SequenceNumber)
Module
This is a String
String metrics[] = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
String clientIdentifier = identityContext.getClientIdentifier(true);
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX] = clientIdentifier;
metrics[OracleConnection.END_TO_END_ACTION_INDEX] = identityContext
.getAction();
// metrics[OracleConnection.END_TO_END_MODULE_INDEX] = identityContext
// .getModule();
Statement st=null;
OracleConnection conns =null;
try {
if (logger.isDebugEnabled()) {
logger.debug("call setEndToEndMetrics with [clientId="
+ metrics[OracleConnection.END_TO_END_CLIENTID_INDEX]
+ "]");
}
// OracleConnection conn = (OracleConnection) adviceContext
// .getCurrentConnection();
st=(Statement)statement;
conns = (OracleConnection)st.getConnection();
conns.setEndToEndMetrics(metrics, (short) 0);
在Oracle段,可以 利用Oracle的VPD技术,可以实现WEB程序的审计系统