Druid setSchema无效
记一次springboot 集成Druid setSchema无效的问题查找
环境
我采用的是 druid-spring-boot-starter 1.2.6 版本,由于是SaaS项目,采用了多租户设计。数据库是MySQL8 每一个租户都是一个schema(即每个租户一个库)。
我的设计是想通过mybatis的prepare拦截器。拦截所有的数据库操作然后根据用户token获取到租户的schema然后通过如下代码设置schema:
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] objs = invocation.getArgs();
Connection conn = (Connection)objs[0];
conn.setSchema("mySchema");
}
但是经过调试发现无效。于是去网上搜了一通,看到有说Druid低版本没有实现这个功能。但是我的Druid是最新版本,我跟踪了Druid源码 DruidPooledConnection 类里的方法setSchema:
注意:由于采用了Druid所以拦截器里的Connection就是 DruidPooledConnection 这个可以通过断点调试看出
public void setSchema(String schema) throws SQLException {
if (JdbcUtils.isMysqlDbType(this.holder.dataSource.getDbType())) {
if (this.holder.initSchema == null) {
this.holder.initSchema = this.conn.getSchema();
}
this.conn.setSchema(schema);
if (this.holder.statementPool != null) {
this.holder.clearStatementCache();
}
} else {
throw new SQLFeatureNotSupportedException();
}
}
发现setSchema是实现了功能了的。于是又去网上搜了一通包括Druid的GitHub的issue上也找了,虽然有人提出和我类似的问题,但是都没有解答。可能Druid GitHub官方觉得这个问题太低级了吧 😃
最后没辙了只能自己去跟踪源码了以下是源码解析:
首先我们通过DruidPooledConnection 的setSchema() 方法看出核心是
this.conn.setSchema(schema);于是继续跟踪这句代码
this.conn 属性类型是 java.sql.Connection 这个是属于jdbc接口。我们要找到这接口实现类,于是可以通过debug调试可以发现实现类就是ConnectionImpl 这是mysql-connector-java8 里的实现类
于是我们接着跟踪ConnectionImpl 的setSchema() 方法
public void setSchema(String schema) throws SQLException {
try {
this.checkClosed();
if (this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) {
this.setDatabase(schema);
}
} catch (CJException var3) {
throw SQLExceptionsMapping.translateException(var3, this.getExceptionInterceptor());
}
}
通过断点调试发现 this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA 结果是false,this.setDatabase(schema);没有走。所以基本确定是这里的问题。但为啥是false,断点发现this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() 的值DatabaseTerm.CATALOG 和 DatabaseTerm.SCHEMA不一样 但DatabaseTerm其实就是个枚举类
public static enum DatabaseTerm {
CATALOG,
SCHEMA;
private DatabaseTerm() {
}
}
猜测、这个应该就是一个mysql的一个参数设置吧。
然后接着跟踪this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() 为啥是CATALOG。首先看一下this.propertySet.getEnumProperty源码
public <T> RuntimeProperty<T> getProperty(PropertyKey key) {
try {
RuntimeProperty<T> prop = (RuntimeProperty)this.PROPERTY_KEY_TO_RUNTIME_PROPERTY.get(key);
if (prop == null) {
prop = (RuntimeProperty)this.PROPERTY_NAME_TO_RUNTIME_PROPERTY.get(key.getKeyName());
}
return prop;
} catch (ClassCastException var3) {
throw (WrongArgumentException)ExceptionFactory.createException(WrongArgumentException.class, var3.getMessage(), var3);
}
}
然后再看一下PropertyKey.databaseTerm这里的PropertyKey也是一个枚举类
public enum PropertyKey {
USER("user", false),
PASSWORD("password", false),
HOST("host", false),
PORT("port", false),
PROTOCOL("protocol", false),
PATH("path", "namedPipePath", false),
TYPE("type", false),
ADDRESS("address", false),
PRIORITY("priority", false),
DBNAME("dbname", false),
allowLoadLocalInfile("allowLoadLocalInfile", true),
allowMasterDownConnections("allowMasterDownConnections", true),
allowMultiQueries("allowMultiQueries", true),
allowNanAndInf("allowNanAndInf", true),
allowPublicKeyRetrieval("allowPublicKeyRetrieval", true),
allowSlaveDownConnections("allowSlaveDownConnections", true),
allowUrlInLocalInfile("allowUrlInLocalInfile", true),
alwaysSendSetIsolation("alwaysSendSetIsolation", true),
authenticationPlugins(<