摘要:事實(shí)上,實(shí)現(xiàn)了接口,而也實(shí)現(xiàn)了接口。還記得之前說的,使用之后,其返回的實(shí)際上是一個(gè)裝飾器嗎。所以修改如下是默認(rèn)全局工廠名稱,請(qǐng)使用別的名稱工廠已經(jīng)配置完成,請(qǐng)不要重復(fù)配置。
這是做個(gè)數(shù)據(jù)庫幫助庫雛形 的當(dāng)晚的再一次嘗試 ORZ
在意識(shí)到原來的 ConnectionProvider 提供的只是一個(gè)普通(實(shí)現(xiàn)了AutoCloseable接口)的 Connection,這在 RepositoryInvocationHandler.handleFind中使用 try-with-resource 的情況下就相當(dāng)于 ConnectionProvier沒啥卵用...
因此,今天晚上進(jìn)行了一些大改:
ConnectionProvier注:寫到最后我還是想配個(gè)日志了... 不過鑒于這么晚了,還是明天再搞吧 : P
/** * 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("加載驅(qū)動(dòng)完畢"); } catch (ClassNotFoundException e) { throw new RuntimeException("無法加載 JDBC 驅(qū)動(dòng): " + this.configuration.getDriverClass( )); } } private synchronized MysqlPooledConnection create ( ) { if (driverClass == null) { throw new RuntimeException("尚未加載 JDBC 驅(qū)動(dòng)."); } else { try { System.out.println("創(chuàng)建新的 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(); } }
可以發(fā)現(xiàn),最大的改變之處在于 create方法返回的是 MysqlPooledConnection 了。
我用記憶中殘存的 PooledConnection 作為關(guān)鍵字搜索后,發(fā)現(xiàn)了這篇文章。
這東西事實(shí)上并不算完整的連接池實(shí)現(xiàn),有興趣可以用 MysqlPooledConnection 作為關(guān)鍵字進(jìn)行搜索 : P
改用這個(gè)東西后,后面其實(shí)沒啥改的,因?yàn)檫@個(gè)東西所創(chuàng)建的 PreparedStatement 是一個(gè)裝飾器。
Connection pooledConnection = ConnectionProvider.provide(); //這里返回的實(shí)際類型是 JDBC42PreparedStatementWrapper PreparedStatement ps = pooledConnection.prepareStatement(...);
JDBC42PreparedStatementWrapper 這個(gè)東西包裝了 com.mysql.jdbc.PreparedStatement。
事實(shí)上,JDBC42PreparedStatementWrapper 實(shí)現(xiàn)了 java.sql.PreparedStatement接口,而com.mysql.jdbc.PreparedStatement也實(shí)現(xiàn)了 java.sql.PreparedStatement接口。
那么來看看修改了一些 connection 獲取邏輯的 RepositoryInvocationHandler:
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; //對(duì) PreparedStatement 做一層緩存,避免每次調(diào)用方法都創(chuàng)建一個(gè) statement private LinkedHashMappreparedStatementMap; //緩存連接,主要是批量創(chuàng)建`statement`時(shí)避免頻繁調(diào)用 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; //批量創(chuàng)建 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 后再次調(diào)用方法時(shí)拋出 Statement 已被關(guān)閉的錯(cuò)誤。 //this.preparedStatementMap.clear(); System.out.println("釋放 " + invokeRepositoryClass.getSimpleName() + " 的資源"); } return null; } // 因?yàn)楂@取連接的動(dòng)作被抽出來給類里的方法公用,所以要做一層緩存處理 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); } } //把創(chuàng)建 statement 的動(dòng)作抽出來,配合 getConnection private PreparedStatement createPreparedStatementWrapper(String preparedSql) throws SQLException { System.out.println("為 [" + preparedSql + "] 創(chuàng)建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,需要對(duì)其持有的 connection 進(jìn)行有效性檢查 private PreparedStatement keepAlive (PreparedStatement preparedStatementWrapper, Method method) { try { try { // 這里有個(gè)坑,詳情見代碼塊下的說明 boolean isClosed = preparedStatementWrapper.isClosed( ); if (! isClosed) { System.out.println("使用緩存 [" + method.getName() + "] 的 PreparedStatemen"); return preparedStatementWrapper; } System.out.println("[" + method.getName() + "] 的緩存PreparedStatemen已被關(guān)閉,創(chuàng)建新的"); 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已被關(guān)閉,創(chuàng)建新的"); 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( ) + "() 設(shè)置 @Query 注解"); } try { System.out.println("檢查 [" + method.getName() + "] 的 preparedStatement 是否有效"); preparedStatementWrapper = this.keepAlive(preparedStatementWrapper, method); System.out.println("填充參數(shù)..."); 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)) + " "); } 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; } }
代碼塊中有個(gè)地方要多帶帶拿出來說一下:
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已被關(guān)閉,創(chuàng)建新的"); 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已被關(guān)閉,創(chuàng)建新的"); 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 }
重點(diǎn)在于第二行的 preparedStatementWrapper.isClosed()。
還記得之前說的,使用 MysqlPooledConnection之后,其返回的PreparedStatement實(shí)際上是一個(gè)裝飾器JDBC43PreparedStatementWrapper嗎。
而在 invoke() 中我們對(duì) close方法進(jìn)行了一個(gè)釋放資源的操作,調(diào)用的是 statement.close()。
那么這個(gè) statement 是裝飾器的話,它的 close 操作到底是關(guān)得誰呢?看看源碼吧:
//JDBC42PreparedStatementWrapper的close實(shí)現(xiàn)是由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; } } } }
嗯,看來調(diào)用的是 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 實(shí)例置空了,再來看看 JDBC4PreparedStatementWrapper 的 isClosed實(shí)現(xiàn):
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 為空時(shí),會(huì)直接拋出錯(cuò)誤 ORZ
這就意味著,在調(diào)用 JDBC4PreparedStatementWrapper.close() 后,再調(diào)用 JDBC4PreparedStatementWrapper.isClosed() 一定會(huì)拋出錯(cuò)誤 ORZ
這就導(dǎo)致我們必須嘗試獲取一下 isClosed()的結(jié)果,在獲取成功(雖然我不知道這在什么情況下才會(huì)出現(xiàn))后,在 isClosed == true 的情況下重新創(chuàng)建 JDBC4PreparedStatementWrapper;而 catch 塊里也需要做一個(gè)重新創(chuàng)建的操作。
看起來是重復(fù)語句,但實(shí)際上不能直接把 重建操作放在 finally 塊中,那樣會(huì)導(dǎo)致每次調(diào)用 keepAlive 時(shí)都重建 PreparedStatement。
RepositoryFactory寫完上篇筆記時(shí),我就想起來沒有做一個(gè)檢查:
用戶創(chuàng)建一個(gè)給定名稱的Repository時(shí),確保這個(gè)給定名稱不是 GLOBAL,因?yàn)檫@是全局工廠的名稱。
所以修改如下:
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 是默認(rèn)全局工廠名稱,請(qǐng)使用別的名稱."); } RepositoryFactory factory; synchronized ( RepositoryFactory.factoryMap ) { factory = RepositoryFactory.factoryMap.get(name); if (factory != null) { throw new RuntimeException(name + " 工廠已經(jīng)配置完成,請(qǐng)不要重復(fù)配置。"); } System.out.println("創(chuàng)建新的工廠: " + name); factory = new RepositoryFactory(ConnectionProvider.configure(configure)); RepositoryFactory.factoryMap.put(name, factory); } return factory; }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70523.html
摘要:前者是數(shù)據(jù)庫驅(qū)動(dòng),由于這是個(gè)挖坑性質(zhì)的東西,所以只針對(duì)做功能了后者是代碼生成框架,挺好用的,強(qiáng)烈推薦也就是說,并不使用常見的數(shù)據(jù)庫連接池,比如。的工廠已經(jīng)被初始化了,不能再對(duì)其進(jìn)行配置。 在以往的編碼中,使用過 spring-data-jpa,也用過 hibernate 和 mybatis。在簡(jiǎn)單的數(shù)據(jù)庫操作中,spring-data-jpa 是用起來最爽的,畢竟在 IntelliJ ...
摘要:注意供應(yīng)器只會(huì)在倉庫工廠第一次創(chuàng)建工廠時(shí)調(diào)用,而參數(shù)處理器和結(jié)果解析器將在每次倉庫方法被調(diào)用時(shí)調(diào)用。解析器接收一個(gè)語句表模型的類聲明觸發(fā)解析器的倉庫方法聲明。因此當(dāng)您配置了一個(gè)結(jié)果解析器,語句的執(zhí)行時(shí)機(jī)將推遲到這里。 Juice 這是我自己做的一個(gè)小項(xiàng)目,也可能會(huì)棄坑... 留作紀(jì)念吧。GitHub 地址 簡(jiǎn)介 Juice 是一個(gè)簡(jiǎn)易的、尚不完善的基于 Java 的SQL數(shù)據(jù)庫工具,它...
摘要:沒有耐心閱讀的同學(xué),可以直接前往學(xué)習(xí)全棧最后一公里。我下面會(huì)羅列一些,我自己錄制過的一些項(xiàng)目,或者其他的我覺得可以按照這個(gè)路線繼續(xù)深入學(xué)習(xí)的項(xiàng)目資源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技術(shù)軟文,閱讀需謹(jǐn)慎,長(zhǎng)約 7000 字,通讀需 5 分鐘 大家好,我是 Scott,本文通過提供給大家學(xué)習(xí)的方法,...
摘要:就在最新的指數(shù)中,數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)項(xiàng)目的首選語言,現(xiàn)在排名僅次于語言,排在第二位,將打落到第三位。特別是在深度學(xué)習(xí)機(jī)器學(xué)習(xí)等領(lǐng)域的廣泛使用,讓一躍成為人工智能時(shí)代的網(wǎng)紅語言。 ...
閱讀 1794·2023-04-25 22:42
閱讀 2218·2021-09-22 15:16
閱讀 3496·2021-08-30 09:44
閱讀 493·2019-08-29 16:44
閱讀 3316·2019-08-29 16:20
閱讀 2521·2019-08-29 16:12
閱讀 3395·2019-08-29 16:07
閱讀 673·2019-08-29 15:08