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

前言

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

乱码问题:
记一次mysql中文乱码问题,druid连接池+mysql8.0_第1张图片

讲讲我的解决思路

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

show variables like '%char%';

记一次mysql中文乱码问题,druid连接池+mysql8.0_第2张图片
都正常,为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不相同,那么客户端解码的数据也会出现问题。

所以,我们现在字符编码都一致,是没有问题的。
那是什么问题呢?
记一次mysql中文乱码问题,druid连接池+mysql8.0_第3张图片
2、我用idea的客户端连接数据库,发现查询出来的数据是正常的。
记一次mysql中文乱码问题,druid连接池+mysql8.0_第4张图片
客户端的编码格式为:
记一次mysql中文乱码问题,druid连接池+mysql8.0_第5张图片
看来如果客户端是正常的utf8编码,查询出来的数据是正常的呀,符合预期。
记一次mysql中文乱码问题,druid连接池+mysql8.0_第6张图片
3、那应该就是我代码连接数据库的字符集有问题了。的确,目前的连接url还是:
jdbc:mysql://ip:3306/数据库名称的格式。druid连接池的配置中也没有配置与字符集相关的配置。

<bean id="nw_DataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
			
			<property name="driverClassName" value="${data.datasource.job.JDBC.DRIVER}" />
			<property name="url" value="${data.datasource.job.JDBC.URL}" />
			<property name="username" value="${data.datasource.job.JDBC.USERNAME}" />
			<property name="password" value="${data.datasource.job.JDBC.PASSWORD}" />

			
			<property name="initialSize" value="${data.datasource.job.JDBC.INITIAL.SIZE}" />
			<property name="minIdle" value="${data.datasource.job.JDBC.MIN.IDLE}" />
			<property name="maxActive" value="${data.datasource.job.JDBC.MAX.ACTIVE}" />

			
			<property name="maxWait" value="60000" />

			
			<property name="timeBetweenEvictionRunsMillis" value="2000" />

			
			<property name="minEvictableIdleTimeMillis" value="600000" />
			<property name="maxEvictableIdleTimeMillis" value="900000" />

			<property name="validationQuery" value="${data.datasource.job.JDBC.validationQuery}" />
			<property name="testWhileIdle" value="true" />
			<property name="testOnBorrow" value="false" />
			<property name="testOnReturn" value="false" />

			<property name="keepAlive" value="true" />
			<property name="phyMaxUseCount" value="1000" />

			
			<property name="filters" value="stat" />

			
			<property name="asyncInit" value="true" />
		bean>

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

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

结果还是不行。
记一次mysql中文乱码问题,druid连接池+mysql8.0_第7张图片
我决定去翻下源码,沿着数据库查询的路子走下去。这是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 = NULLcharacter_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);

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

记一次mysql中文乱码问题,druid连接池+mysql8.0_第8张图片
还好我把问题是解决了。
我在数据库url上加上了characterSetResults:jdbc:mysql://ip:3306/数据库名称?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
或者你也可以配置在druid连接池的参数中,加上:

			<property name="connectionProperties">
				
				<value>clientEncoding=utf-8;serverEncoding=utf-8;characterSetResults=utf-8value>
			property>

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

乱码问题解决。
记一次mysql中文乱码问题,druid连接池+mysql8.0_第9张图片

小结

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

		
		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<version>8.0.16version>
		dependency>
		
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>druidartifactId>
			<version>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的呀,是哪个环节出了问题呢?如果你也碰到这样的问题,指导我一下,不胜感激!!!

你可能感兴趣的:(mysql,&,oracle,&,数据库)