7
回答
使用OSChina的 DBManager 提示 $Proxy0 cannot cast to jav
终于搞明白,存储TCO原来是这样算的>>>   

研究红薯大哥的 DBManager 代码,发现里面使用 Proxy 跟踪打印 SQL 方法非常的巧妙,于是自己在本地测试了下,结果没有成功,后台报 $Proxy0 cannot cast to java.sql.Connection 错误,很是不解,我的代码如下:

DBManager(我进行了一些删减):

package my.db;

import java.sql.*;
import java.util.*;
import java.lang.reflect.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 数据库管理
 * @author Winter Lau
 * @date 2010-2-2 下午10:18:50
 */
public class DBManager {

	private final static Log log = LogFactory.getLog(DBManager.class);
	private final static ThreadLocal conns = new ThreadLocal();

	private static boolean show_sql = true;
	

	public final static Connection getConnection() throws SQLException {
		Connection conn = conns.get();
		if(conn ==null || conn.isClosed()){
			
			// 这里使用我定义的一个简单的 ConnectionProvider 替代 dataSource 获取Connection
			conn = ConnectionProvider.getConnection();
			conns.set(conn);
		}
		return (show_sql && !Proxy.isProxyClass(conn.getClass()))?
                      new _DebugConnection(conn).getConnection():conn;
	}
	
	/**
	 * 关闭连接
	 */
	public final static void closeConnection() {
		Connection conn = conns.get();
		try {
			if(conn != null && !conn.isClosed()){
				conn.setAutoCommit(true);
				conn.close();
			}
		} catch (SQLException e) {
			log.error("Unabled to close connection!!! ", e);
		}
		conns.set(null);
	}

	/**
	 * 用于跟踪执行的SQL语句
	 * @author Winter Lau
	 */
	static class _DebugConnection implements InvocationHandler {
		
		private final static Log log = LogFactory.getLog(_DebugConnection.class);
		
		private Connection conn = null;

		public _DebugConnection(Connection conn) {
			this.conn = conn;
		}

		/**
		 * Returns the conn.
		 * @return Connection
		 */
		public Connection getConnection() {
			return (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), 
                             conn.getClass().getInterfaces(), this);
		}
		
		public Object invoke(Object proxy, Method m, Object[] args) throws Throwable 
		{
			try 
			{
					String method = m.getName();
					if("prepareStatement".equals(method) || "createStatement".equals(method))
					{
						log.info("[SQL] >>> " + args[0]);		
					}
								
					return m.invoke(conn, args);
				
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			}
		}

	}
	
}

ConnectionProvider(很简单):

 

import java.sql.*;

public class ConnectionProvider
{
		public static Connection getConnection()
		{
		   try{
		   
		       Class.forName("com.mysql.jdbc.Driver").newInstance();
		       
		       return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/testdb", "root", "123456");
		   	
		   }catch(Exception e){
		   	
		   }	
		}
}

TestMain(测试类):

 

public class TestMain
{
	 public static void main( String[] args )
	 {
	 		 Connection conn = null;
	 		 Statement stmt = null;
	 		 try
	 		 {
	 		 	
	 		 	 conn = DBManager.getConnection();
	 		 	 stmt = conn.createStatement();
	 		 	 stmt.executeUpdate( "insert into aa(desc) values('0123456789')" );
	 		 	
	 		 }catch(SQLException e){
	 		 	
	 		 }finally{
	 		 	  try{
	 		 	  	if( stmt != null ){
	 		 	  	    stmt.close();
	 		 	  	    stmt = null;	
	 		 	  	}
	 		 	  }catch(SQLException e){
	 		 	  	
	 		 	  }	
	 		 	  
	 		 	  DBManager.closeConnection();
	 		 }	
	 }	
}

运行上面的 TestMain ,后台就报错误了:

Exception in thread "main" java.lang.ClassCastException: $Proxy0 cannot be cast to java.sql.Connection

   at DBManager_$DebugConnection.getConnection( DBManager.java:70);

   at DBManager.getConnection( DBManager.java:32)


举报
山哥
发帖于7年前 7回/1K+阅
共有7个答案 最后回答: 7年前

可以将 getConnection 方法内的 conn.getClass().getInterfaces() 换成 new Class[]{Connection.class} 即可。

另外你这个代码会出空指针异常,因为 createStatement 没有参数,而底下打印日志的时候是 ("[SQL] >>> " + args[0])

引用来自#2楼“红薯”的帖子

可以将 getConnection 方法内的 conn.getClass().getInterfaces() 换成 new Class[]{Connection.class} 即可。

另外你这个代码会出空指针异常,因为 createStatement 没有参数,而底下打印日志的时候是 ("[SQL] >>> " + args[0])

谢谢红薯大哥

同时,我也搜了下 Connection.getInterfaces() 与 new Class[]{Connection.class} 2个使用方式的原因:

原来Connection.getInterfaces() 与数据库驱动有关,数据库驱动不同 Connection.getInterfaces() 的结果也就不同;Connection.getInterfaces() 返回的是 Class[] 数组,此数组的第一个元素必须是Connection才能把创建的代理类转为Connection对象,否则就会报:java.lang.ClassCastException 了,呵呵:

Class[] interfaces = conn.getClass().getInterfaces();
if( interfaces == null || interfaces.length <= 0 ){
    interfaces = new Class[1];
    interfaces[0] = Connection.class;
}

 

红薯大哥,

我测试了,现在不提示 $Proxy0 cannot be cast to java.sql.Connection 错误了,同时也遇到了你说的空指针问题,我对此进行了修改:

stmt = conn.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE );
stmt.executeUpdate( "insert into ....." );

但是,打印的SQL却是:[SQL] >>> 1005,不是SQL语句

我又使用 PreparedStatement 替换 Statement:

PreparedStatement pstmt = conn.preparedStatement( "insert into aa(desc) values(?)" );
pstmt.setString(1, "123456");
pstmt.execute();

这次打印出来的SQL是: [SQL] >>> insert into aa(desc) values(?) , 占位符没有被参数替换

打印出来的SQL都很奇怪

我还没有看DBManager ,问一下 常的巧妙 是指什么呢

是它对SQL的输出格式控制上有什么特别之处吗?

可以支持下DBCP

jdbc.dbcp.show_sql=true
jdbc.dbcp.datasource=org.apache.commons.dbcp.BasicDataSource
jdbc.dbcp.driverClassName=com.mysql.jdbc.Driver
jdbc.dbcp.url=jdbc:mysql://localhost:3306/my
jdbc.dbcp.username=root
jdbc.dbcp.password=123456
jdbc.dbcp.initialSize=1
jdbc.dbcp.maxActive=200
jdbc.dbcp.maxIdle=10
jdbc.dbcp.maxWait=2000
jdbc.dbcp.defaultAutoCommit=false
jdbc.dbcp.poolPreparedStatements=true
jdbc.dbcp.maxOpenPreparedStatements=1000
jdbc.dbcp.encoding=false

截取的时候取10个

skey.substring(10);

 conn = DBManager.getConnection();  
                stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);  
                stmt.executeUpdate( "insert into test1(id,name,card,age,address) values(9,'liuwj','12312345',24,'hubeitianmen')" );  

这样的方式还是不能跟踪sql语句的输出啊?

请问通过上面的方式怎样才能实现sql语句的跟踪?
               

顶部