durid mysql 中文乱码_记一次mysql中文乱码问题,druid连接池+mysql8.0

本文记录了一次解决MySQL8.0中使用Druid连接池出现中文乱码问题的过程。问题源于字符集配置不一致,通过分析Druid源码和MySQL驱动,最终在数据库URL上添加了`characterSetResults=utf8`参数,或者在Druid连接池配置中指定字符集,成功解决了乱码问题。
摘要由CSDN通过智能技术生成

前言

乱码问题主要是字符编码不配对引起的,也就是编码和解码的字符集不一致,就造成了乱码。mysql的字符集编码可以参考:mysql字符集与比较规则

乱码问题:

76d08937d4326030bf8a03948bacd2ec.png

讲讲我的解决思路

1、一开始看到这个问题,首先我先看了该schema下的字符集:

show variables like '%char%';

a1478b37f78ea22448c958b2a4ca6df3.png

都正常,为utf8mb4。主要关注的是character_set_client、character_set_connection、character_set_results这三个变量。

他们的含义可以看以下例子:

首先client一般会发送一条sql给客户端,比如select * from tableA where name = ‘蜗牛’;,在分析的过程中,我们主要对查询参数“蜗牛”进行分析。

1、首先蜗牛两个字被client端进行编码为二进制流,传输给服务器。

2、服务器收到后,会认为客户端的编码为character_set_client对蜗牛进行解码,获取client发送的字符串sql。

3、解码之后,再把sql转换为character_set_connection格式,使得能与数据库连接的字符集相同。

4、解码成character_set_connection之后,就进行数据比对查询。此时如果要比对的列的字符集与character_set_connection不相等,那显然就是会有问题的。

5、数据查询出来之后,对结果要进行编码,编码的字符集就采用character_set_results字符集,此时如果客户端接收的字符集与character_set_connection不相同,那么客户端解码的数据也会出现问题。

所以,我们现在字符编码都一致,是没有问题的。

那是什么问题呢?

2af6122f84f8eea9340bb977a69d1fee.png

2、我用idea的客户端连接数据库,发现查询出来的数据是正常的。

f947985d106e0ce72f2dd209629b0bff.png

客户端的编码格式为:

9f5392e447db1598eec4524bdf5290c1.png

看来如果客户端是正常的utf8编码,查询出来的数据是正常的呀,符合预期。

9b70cdac367d9f11f6417abf3280f79e.png

3、那应该就是我代码连接数据库的字符集有问题了。的确,目前的连接url还是:

jdbc:mysql://ip:3306/数据库名称的格式。druid连接池的配置中也没有配置与字符集相关的配置。

bean>

按照网上说的思路,我再数据库连接url上加上了字符集的配置:

jdbc:mysql://ip:3306/数据库名?useUnicode=true&characterEncoding=utf8

结果还是不行。

d8ad89eacea8718c8b12b563698d35ad.png

我决定去翻下源码,沿着数据库查询的路子走下去。这是mysql-connector-j的github地址

从com.alibaba.druid.proxy.DruidDriver的connect方法往下走,

public Connection connect(String url, Properties info) throws SQLException {

if (!acceptsURL(url)) {

return null;

}

connectCount.incrementAndGet();

DataSourceProxyImpl dataSource = getDataSource(url, info);

return dataSource.connect(info);

}

走到mysql驱动中com.mysql.cj.jdbc.NonRegisteringDriver#connect:

case SINGLE_CONNECTION:

return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());

其中在创建数据库连接。

进到com.mysql.cj.jdbc.ConnectionImpl的构造方法 -> createNewIO() -> connectWithRetries()->initializePropsFromServer()其中从服务端初始化客户端的一些配置。

this.session.configureClientCharacterSet(false);

// We've already set it, and it might be different than what was originally on the server, which is why we use the "special" key to retrieve it

this.session.getServerSession().configureCharacterSets();

我主要关注了这两个和字符集相关的配置。其中有这样一段:

// We know how to deal with any charset coming back from the database, so tell the server not to do conversion if the user hasn't 'forced' a

// result-set character set

//

String onServer = this.protocol.getServerSession().getServerVariable("character_set_results");

if (characterSetResults.getValue() == null) {

//

// Only send if needed, if we're caching server variables we -have- to send, because we don't know what it was before we cached them.

//

if (onServer != null && onServer.length() > 0 && !"NULL".equalsIgnoreCase(onServer)) {

try {

sendCommand(this.commandBuilder.buildComQuery(null, "SET character_set_results = NULL"), false, 0);

} catch (PasswordExpiredException ex) {

if (this.disconnectOnExpiredPasswords.getValue()) {

throw ex;

}

}

this.protocol.getServerSession().getServerVariables().put(ServerSession.LOCAL_CHARACTER_SET_RESULTS, null);

} else {

this.protocol.getServerSession().getServerVariables().put(ServerSession.LOCAL_CHARACTER_SET_RESULTS, onServer);

}

}

它读取了服务器的character_set_results变量,如果没有自定义characterSetResults,则执行SET character_set_results = NULL将character_set_results置空了。注释说我们知道怎么转换数据库返回的结果,所以我们强制数据库不要对结果进行转换。 似乎他想提供一个更加灵活的字符集配置的效果。

在下面的configureCharacterSets,它又取出了CHARACTER_SET_RESULTS进行了处理。前面赋值的是null,所以走进了if里面。

public void configureCharacterSets() {

String characterSetResultsOnServerMysql = getServerVariable(LOCAL_CHARACTER_SET_RESULTS);

if (characterSetResultsOnServerMysql == null || StringUtils.startsWithIgnoreCaseAndWs(characterSetResultsOnServerMysql, "NULL")

|| characterSetResultsOnServerMysql.length() == 0) {

String defaultMetadataCharsetMysql = getServerVariable("character_set_system");

String defaultMetadataCharset = null;

if (defaultMetadataCharsetMysql != null) {

defaultMetadataCharset = CharsetMapping.getJavaEncodingForMysqlCharset(defaultMetadataCharsetMysql);

} else {

defaultMetadataCharset = "UTF-8";

}

this.characterSetMetadata = defaultMetadataCharset;

setErrorMessageEncoding("UTF-8");

}

} else {

this.characterSetResultsOnServer = CharsetMapping.getJavaEncodingForMysqlCharset(characterSetResultsOnServerMysql);

this.characterSetMetadata = this.characterSetResultsOnServer;

setErrorMessageEncoding(this.characterSetResultsOnServer);

}

我们发现,它再次读取了CHARACTER_SET_RESULTS变量。如果为空的话,会读取系统变量character_set_system的值,赋给defaultMetadataCharset,在if里面,没有看到characterSetResult的赋值,说明目前还是null。

我一开始以为我可以从源码里面找到,是不是我没有配置characterSetResult的字符集,使得系统采用了一个默认的字符集导致了乱码,但结果好像并不如人意。

反而在上面代码的else语句中看到了characterSetResultsOnServer 的赋值:

this.characterSetResultsOnServer = CharsetMapping.getJavaEncodingForMysqlCharset(characterSetResultsOnServerMysql);

所以最后我还是没有发现原因。

976821ff16fb430b01b5ecb6bc5c0670.png

还好我把问题是解决了。

我在数据库url上加上了characterSetResults:jdbc:mysql://ip:3306/数据库名称?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8

或者你也可以配置在druid连接池的参数中,加上:

clientEncoding=utf-8;serverEncoding=utf-8;characterSetResults=utf-8value>

property>

把编码通通一口气指定了。

乱码问题解决。

37ad0984651c569f45370d32f2ca20fe.png

小结

我使用的是mysql8.0,mysql驱动为8.0.16,druid的版本为1.1.21:

mysqlgroupId>

mysql-connector-javaartifactId>

8.0.16version>

dependency>

com.alibabagroupId>

druidartifactId>

1.1.21version>

dependency>

mysql配置文件为:

[mysqld]

port=3306

# 允许最大连接数

max_connections=200

# # 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统

max_connect_errors=10

# # 服务端使用的字符集默认为UTF8

character-set-server=utf8

# # 创建新表时将使用的默认存储引擎

default-storage-engine=INNODB

datadir=/mysql/data

socket=/var/lib/mysql/mysql.sock

log-error=/var/log/mysqld.log

pid-file=/var/run/mysqld/mysqld.pid

lower_case_table_names=1

skip-name-resolve

[mysql]

default-character-set = utf8

[mysql.server]

default-character-set = utf8

[mysqld_safe]

default-character-set = utf8

[client]

default-character-set = utf8

虽然问题看似解决了,但还是有一块疙瘩在这里,因为我记得以前是不需要单独配置characterSetResults的呀,是哪个环节出了问题呢?如果你也碰到这样的问题,指导我一下,不胜感激!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值