這是作個數據庫幫助庫雛形 的當晚的再一次嘗試 ORZjava
在乎識到原來的 ConnectionProvider
提供的只是一個普通(實現了AutoCloseable
接口)的 Connection
,這在 RepositoryInvocationHandler.handleFind
中使用 try-with-resource
的狀況下就至關於 ConnectionProvier
沒啥卵用...mysql
所以,今天晚上進行了一些大改:sql
注:寫到最後我仍是想配個日誌了... 不過鑑於這麼晚了,仍是明天再搞吧 : P數據庫
ConnectionProvier
/** * Created by krun on 2017/9/22. */ public class ConnectionProvider { public static ConnectionProvider configure (Configuration configuration) { return new ConnectionProvider(configuration); } private Class driverClass; private Configuration configuration; // 大改的核心之處 private volatile MysqlPooledConnection pooledConnection; private ConnectionProvider (Configuration configuration) { this.configuration = configuration; try { this.driverClass = Class.forName(this.configuration.getDriverClass( )); System.out.println("加載驅動完畢"); } catch (ClassNotFoundException e) { throw new RuntimeException("沒法加載 JDBC 驅動: " + this.configuration.getDriverClass( )); } } private synchronized MysqlPooledConnection create ( ) { if (driverClass == null) { throw new RuntimeException("還沒有加載 JDBC 驅動."); } else { try { System.out.println("建立新的 MysqlPooledConnection"); return new MysqlPooledConnection((com.mysql.jdbc.Connection) DriverManager.getConnection( this.configuration.getConnectionURL( ), this.configuration.getUsername( ), this.configuration.getPassword( ))); } catch (SQLException e) { throw new RuntimeException(e); } } } public synchronized Connection provide ( ) throws SQLException { if (pooledConnection == null) { System.out.println("初始化 pooledConnection"); pooledConnection = create( ); } else if (pooledConnection.getConnection().isClosed()) { System.out.println("從新獲取 pooledConnection"); pooledConnection = create( ); } else { System.out.println("使用緩存 pooledConnection"); } return pooledConnection.getConnection(); } }
能夠發現,最大的改變之處在於 create
方法返回的是 MysqlPooledConnection
了。
我用記憶中殘存的 PooledConnection
做爲關鍵字搜索後,發現了這篇文章。segmentfault
這東西事實上並不算完整的鏈接池實現,有興趣能夠用 MysqlPooledConnection
做爲關鍵字進行搜索 : P緩存
改用這個東西后,後面其實沒啥改的,由於這個東西所建立的 PreparedStatement
是一個裝飾器。app
Connection pooledConnection = ConnectionProvider.provide(); //這裏返回的實際類型是 JDBC42PreparedStatementWrapper PreparedStatement ps = pooledConnection.prepareStatement(...);
JDBC42PreparedStatementWrapper
這個東西包裝了 com.mysql.jdbc.PreparedStatement
。
事實上,JDBC42PreparedStatementWrapper
實現了 java.sql.PreparedStatement
接口,而com.mysql.jdbc.PreparedStatement
也實現了 java.sql.PreparedStatement
接口。ide
那麼來看看修改了一些 connection
獲取邏輯的 RepositoryInvocationHandler
:this
RepositoryInvocationHandler
/** * Created by krun on 2017/9/22. */ public class RepositoryInvocationHandler implements InvocationHandler { //緩存表名,避免屢次從方法註解獲取該信息 private final String entityName; private RepositoryFactory factory; private Class<? extends Repository> invokeRepositoryClass; //對 PreparedStatement 作一層緩存,避免每次調用方法都建立一個 statement private LinkedHashMap<String, PreparedStatement> preparedStatementMap; //緩存鏈接,主要是批量建立`statement`時避免頻繁調用 ConnectionProvider.provide() private Connection connection; public RepositoryInvocationHandler (RepositoryFactory factory, Class<? extends Repository> invokeRepositoryClass) { this.factory = factory; this.invokeRepositoryClass = invokeRepositoryClass; this.preparedStatementMap = new LinkedHashMap<>( ); this.entityName = getEntityName( ); this.connection = getConnection(); try { PreparedStatement preparedStatementWrapper; Query query; //批量建立 statement,替換表名佔位符,存入緩存 for (Method method : invokeRepositoryClass.getMethods( )) { query = method.getAnnotation(Query.class); if (query == null) continue; preparedStatementWrapper = createPreparedStatementWrapper(String.format(query.value( ), entityName)); System.out.println("爲方法 [" + method.getName() + "] 緩存 preparedStatement" ); this.preparedStatementMap.put(method.getName( ), preparedStatementWrapper); } } catch (SQLException e) { e.printStackTrace( ); } } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName( ); if (methodName.startsWith("find")) { return handleFind(method, args); } else if (methodName.startsWith("save")) { } else if (methodName.startsWith("delete")) { } else if (methodName.startsWith("exist")) { } else if ("close".equals(methodName)) { // 暴露 Repository.close 接口來釋放一些資源 for (String key : this.preparedStatementMap.keySet( )) { this.preparedStatementMap.get(key).close( ); } this.connection.close(); //註釋掉這句,避免 close 後再次調用方法時拋出 Statement 已被關閉的錯誤。 //this.preparedStatementMap.clear(); System.out.println("釋放 " + invokeRepositoryClass.getSimpleName() + " 的資源"); } return null; } // 由於獲取鏈接的動做被抽出來給類裏的方法公用,因此要作一層緩存處理 private Connection getConnection() { try { synchronized ( this ) { if (this.connection == null) { System.out.println("第一次從 provider 獲取鏈接"); this.connection = this.factory.getConnectionProvider().provide(); } else if (this.connection.isClosed()) { System.out.println("從 provider 獲取新的鏈接"); this.connection = this.factory.getConnectionProvider().provide(); } else { System.out.println("使用緩存鏈接"); } } return this.connection; } catch (SQLException e) { throw new RuntimeException(e); } } //把建立 statement 的動做抽出來,配合 getConnection private PreparedStatement createPreparedStatementWrapper(String preparedSql) throws SQLException { System.out.println("爲 [" + preparedSql + "] 建立PreparedStatemen"); return getConnection() .prepareStatement(preparedSql); } private String getEntityName ( ) { if (! Repository.class.isAssignableFrom(this.invokeRepositoryClass)) { throw new RuntimeException(String.format("接口 [%s] 並無繼承 Repository", this.invokeRepositoryClass.getName( ))); } System.out.println("獲取表名"); ParameterizedType parameterizedType = (ParameterizedType) this.invokeRepositoryClass.getGenericInterfaces( )[0]; return ((Class) parameterizedType.getActualTypeArguments( )[0]).getSimpleName( ).toLowerCase( ); } //因爲緩存了 statement,須要對其持有的 connection 進行有效性檢查 private PreparedStatement keepAlive (PreparedStatement preparedStatementWrapper, Method method) { try { try { // 這裏有個坑,詳情見代碼塊下的說明 boolean isClosed = preparedStatementWrapper.isClosed( ); if (! isClosed) { System.out.println("使用緩存 [" + method.getName() + "] 的 PreparedStatemen"); return preparedStatementWrapper; } System.out.println("[" + method.getName() + "] 的緩存PreparedStatemen已被關閉,建立新的"); preparedStatementWrapper = createPreparedStatementWrapper(String.format(method.getAnnotation(Query.class).value( ), entityName)); this.preparedStatementMap.put(method.getName( ), preparedStatementWrapper); } catch (SQLException ignore) { System.out.println("[" + method.getName() + "] 的緩存PreparedStatemen的stm已被關閉,建立新的"); preparedStatementWrapper = createPreparedStatementWrapper(String.format(method.getAnnotation(Query.class).value( ), entityName)); this.preparedStatementMap.put(method.getName( ), preparedStatementWrapper); } return preparedStatementWrapper; } catch (SQLException e) { throw new RuntimeException(e); } } @SuppressWarnings ("unchecked") private Object handleFind (Method method, Object... args) { PreparedStatement preparedStatementWrapper = this.preparedStatementMap.get(method.getName( )); if (preparedStatementWrapper == null) { throw new IllegalArgumentException("也許你忘了爲 " + method.getDeclaringClass( ).getSimpleName( ) + "." + method.getName( ) + "() 設置 @Query 註解"); } try { System.out.println("檢查 [" + method.getName() + "] 的 preparedStatement 是否有效"); preparedStatementWrapper = this.keepAlive(preparedStatementWrapper, method); System.out.println("填充參數..."); for (int i = 1; i <= args.length; i++) { preparedStatementWrapper.setObject(i, args[i - 1]); } System.out.println(preparedStatementWrapper.toString( )); ResultSet resultSet = preparedStatementWrapper.executeQuery( ); ResultSetMetaData metaData = resultSet.getMetaData( ); while (resultSet.next( )) { for (int i = 1; i <= metaData.getColumnCount( ); i++) { System.out.print(String.valueOf(resultSet.getObject(i)) + "\t"); } System.out.println(); } resultSet.close( ); } catch (SQLException e) { throw new RuntimeException(e); } try { return method.getReturnType( ).newInstance( ); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace( ); } return null; } }
代碼塊中有個地方要單獨拿出來講一下:.net
0 try { 1 try { 2 boolean isClosed = preparedStatementWrapper.isClosed( ); 3 if (! isClosed) { 4 System.out.println("使用緩存 [" + method.getName() + "] 的 PreparedStatemen"); 5 return preparedStatementWrapper; 6 } 7 System.out.println("[" + method.getName() + "] 的緩存PreparedStatemen已被關閉,建立新的"); 8 preparedStatementWrapper = createPreparedStatementWrapper(String.format(method.getAnnotation(Query.class).value( ), entityName)); 9 this.preparedStatementMap.put(method.getName( ), preparedStatementWrapper); 10 } catch (SQLException ignore) { 11 System.out.println("[" + method.getName() + "] 的緩存PreparedStatemen的stm已被關閉,建立新的"); 12 preparedStatementWrapper = createPreparedStatementWrapper(String.format(method.getAnnotation(Query.class).value( ), entityName)); 13 this.preparedStatementMap.put(method.getName( ), preparedStatementWrapper); 14 } 15 return preparedStatementWrapper; 16 } catch (SQLException e) { 17 throw new RuntimeException(e); 18 }
重點在於第二行的 preparedStatementWrapper.isClosed()
。
還記得以前說的,使用 MysqlPooledConnection
以後,其返回的PreparedStatement
其實是一個裝飾器JDBC43PreparedStatementWrapper
嗎。
而在 invoke()
中咱們對 close
方法進行了一個釋放資源的操做,調用的是 statement.close()
。
那麼這個 statement
是裝飾器的話,它的 close
操做究竟是關得誰呢?看看源碼吧:
//JDBC42PreparedStatementWrapper的close實現是由JDBC4PreparedStatementWrapper作的。 public class JDBC4PreparedStatementWrapper extends PreparedStatementWrapper { public synchronized void close() throws SQLException { if (this.pooledConnection == null) { // no-op return; } MysqlPooledConnection con = this.pooledConnection; // we need this later... try { super.close(); } finally { try { StatementEvent e = new StatementEvent(con, this); // todo: pull this all up into base classes when we support *only* JDK6 or newer if (con instanceof JDBC4MysqlPooledConnection) { ((JDBC4MysqlPooledConnection) con).fireStatementEvent(e); } else if (con instanceof JDBC4MysqlXAConnection) { ((JDBC4MysqlXAConnection) con).fireStatementEvent(e); } else if (con instanceof JDBC4SuspendableXAConnection) { ((JDBC4SuspendableXAConnection) con).fireStatementEvent(e); } } finally { this.unwrappedInterfaces = null; } } } }
嗯,看來調用的是 super.close()
,那麼咱們須要再往上看 StatementWrapper
:
public void close() throws SQLException { try { if (this.wrappedStmt != null) { this.wrappedStmt.close(); } } catch (SQLException sqlEx) { checkAndFireConnectionError(sqlEx); } finally { this.wrappedStmt = null; this.pooledConnection = null; } }
問題就在 this.wrappedStmt = null;
這一句,它把所裝飾的 statement
實例置空了,再來看看 JDBC4PreparedStatementWrapper
的 isClosed
實現:
public boolean isClosed() throws SQLException { try { if (this.wrappedStmt != null) { return this.wrappedStmt.isClosed(); } else { throw SQLError.createSQLException("Statement already closed", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); } } catch (SQLException sqlEx) { checkAndFireConnectionError(sqlEx); } return false; // never get here - compiler can't tell }
在 wrappedStmt
爲空時,會直接拋出錯誤 ORZ
這就意味着,在調用 JDBC4PreparedStatementWrapper.close()
後,再調用 JDBC4PreparedStatementWrapper.isClosed()
必定會拋出錯誤 ORZ
這就致使咱們必須嘗試獲取一下 isClosed()
的結果,在獲取成功(雖然我不知道這在什麼狀況下才會出現)後,在 isClosed == true
的狀況下從新建立 JDBC4PreparedStatementWrapper
;而 catch
塊裏也須要作一個從新建立的操做。
看起來是重複語句,但實際上不能直接把 重建操做放在 finally
塊中,那樣會致使每次調用 keepAlive
時都重建 PreparedStatement
。
RepositoryFactory
寫完上篇筆記時,我就想起來沒有作一個檢查:
用戶建立一個給定名稱的Repository
時,確保這個給定名稱不是 GLOBAL
,由於這是全局工廠的名稱。
因此修改以下:
private static boolean isSelfCall (StackTraceElement[] stackTraceElements) { return stackTraceElements[1].getClassName( ).equals(RepositoryFactory.class.getName( )); } public static RepositoryFactory configure (String name, Configuration configure) { if (! isSelfCall(new Exception( ).getStackTrace( )) && FACTORY_GLOBAL.equals(name)) { throw new RuntimeException("GLOBAL 是默認全局工廠名稱,請使用別的名稱."); } RepositoryFactory factory; synchronized ( RepositoryFactory.factoryMap ) { factory = RepositoryFactory.factoryMap.get(name); if (factory != null) { throw new RuntimeException(name + " 工廠已經配置完成,請不要重複配置。"); } System.out.println("建立新的工廠: " + name); factory = new RepositoryFactory(ConnectionProvider.configure(configure)); RepositoryFactory.factoryMap.put(name, factory); } return factory; }