这是做个数据库帮助库雏形 的当晚的再一次尝试 ORZ
在意识到原来的 ConnectionProvider
提供的只是一个普通(实现了AutoCloseable
接口)的 Connection
,这在 RepositoryInvocationHandler.handleFind
中使用 try-with-resource
的情况下就相当于 ConnectionProvier
没啥卵用...
因此,今天晚上进行了一些大改:
注:写到最后我还是想配个日志了... 不过鉴于这么晚了,还是明天再搞吧 : 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
作为关键字搜索后,发现了这篇文章。
这东西事实上并不算完整的连接池实现,有兴趣可以用 MysqlPooledConnection
作为关键字进行搜索 : P
改用这个东西后,后面其实没啥改的,因为这个东西所创建的 PreparedStatement
是一个装饰器。
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
接口。
那么来看看修改了一些 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;
//对 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;
}
}
代码块中有个地方要单独拿出来说一下:
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;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。