Druid setSchema无效

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(<
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值