これは、なにをしたくて書いたもの?
MySQLのJDBCドライバー、Connector/JのcharacterEncoding
、characterSetResults
、connectionCollation
あたりの説明を見ていて、
不思議な感じがしたので調べてみることにしました。
MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.3.3 Session
どう指定したらいいか、よくわからなくなるんですよね。
Connector/Jの説明を読む
characterEncoding
、characterSetResults
、connectionCollation
の説明を、それぞれ見てみます。
MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.3.3 Session
なお、このドキュメントを見ている時のMySQL Connector/Jのバージョンは、8.0.28です。
characterEncoding
は、character_set_client
およびcharacter_set_connection
を「指定されたJavaのエンコーディングのデフォルトの
Character Setに設定し、collation_connection
をCharacter SetのデフォルトのCollationに設定する」と書かれています。
Instructs the server to set session system variables 'character_set_client' and 'character_set_connection' to the default character set for the specified Java encoding and set 'collation_connection' to the default collation for this character set.
characterEncoding
もconnectionCollation
も指定されていない場合は、characterEncoding
としては8.0.26以降はutf8mb4
が指定されると
書かれています。
If neither this property nor the property 'connectionCollation' is set:
For Connector/J 8.0.25 and earlier, the driver will try to use the server default character set; For Connector/J 8.0.26 and later, the driver will use "utf8mb4".
utf8mb4
はJavaのエンコーディングではありませんが…。
ちなみに、現在のMySQLはCharacter Encodingはutf8mb4
がデフォルトであり、utf8mb4
のデフォルトのCollationはutf8mb4_0900_ai_ci
です。
MySQL Server にはサーバー文字セットとサーバー照合順序があります。 デフォルトでは、これらは utf8mb4 および utf8mb4_0900_ai_ci ですが、サーバーの起動時にコマンドラインまたはオプションファイルで明示的に設定し、実行時に変更できます。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.3.2 サーバー文字セットおよび照合順序
characterSetResults
についても、「指定されたJavaのエンコーディングに対応するCharacter Setでエンコードされたデータを返すように
サーバーに指示します」と書かれています。
Instructs the server to return the data encoded with the default character set for the specified Java encoding.
指定しない、またはnull
の場合、サーバーは元のCharacter Setでデータを送信し、ドライバーは結果のメタデータに従ってデータを
デコードします。
If not set or set to "null", the server will send data in its original character set and the driver will decode it according to the result metadata.
connectionCollation
は、セッションシステム変数collation_connection
を指定されたCollationに設定し、character_set_client
と
character_set_connection
を対応するCharacter Setに設定するようにサーバーに指示します。
Instructs the server to set session system variable 'collation_connection' to the specified collation name and set 'character_set_client' and 'character_set_connection' to the corresponding character set.
この結果、connectionCollation
はcharacterEncoding
で指定した値を上書きする挙動になるようです。
This property overrides the value of 'characterEncoding' with the character set this collation belongs to.
そしてconnectionCollation
もcharacterEncoding
も指定されていない場合は、connectionCollation
のデフォルトのCollationになると
書かれています。
If neither this property nor the property 'characterEncoding' is set:
For Connector/J 8.0.25 and earlier, the driver will try to use the server default character set;
For Connector/J 8.0.26 and later, the driver will use "utf8mb4" default collation.
これは、どう指定するのが適切でしょうか?こうなると、各変数がアプリケーションの動作に与える影響を確認しておく必要が
ありそうですね。
現在はCharacter Encodingはutf8mb4
を指定するのが無難かと思いますので、主にCollationまわりに関する話がポイントかなとは
思いますが。
MySQLのCollationのドキュメントを読んでみる
次のドキュメントを見てみます。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.4 接続文字セットおよび照合順序
まずは、サーバーおよびデータベースレベルのCharacter SetとCollationについて。
・character_set_server および collation_server システム変数は、サーバーの文字セットと照合順序を示します。
・character_set_database および collation_database システム変数は、デフォルトデータベースの文字セットおよび照合順序を示します。
character_set_client
は、クライアントが送信するデータのエンコーディングに関わる話になります。
クライアントから離れるときのステートメントの文字セットは何ですか。
サーバーは、character_set_client システム変数値を、クライアントが送信するステートメントの文字セットにします。
character_set_connection
は、クライアントが送信したデータを変換する先のエンコーディングを指定するようです。
サーバーがステートメントを受信したあと、どの文字セットに変換するべきですか。
これを確認するために、サーバーは character_set_connection および collation_connection システム変数を使用します:
サーバーは、クライアントによって送信されたステートメントを character_set_client から character_set_connection に変換します。
一方で、collation_connection
はリテラル文字列の比較に使われるだけのようですね。
collation_connection は、リテラル文字列の比較に重要です。 カラム値と文字列を比較する場合、collation_connection は関係ありません。
ということは、collation_connection
を気にすることはほとんどなさそうですね。
character_set_results
は、サーバーから返すデータのエンコーディングに使用されるようです。
クエリー結果をクライアントに返送する前に、サーバーはどの文字セットに変換する必要がありますか。
character_set_results システム変数値は、サーバーがクライアントにクエリー結果を返信するときに使用する文字セットを示します。 これには、カラム値、結果メタデータ (カラム名など)、エラーメッセージなどの結果データが含まれます。
特に変換を行い場合は、設定しないかbinary
に指定する、と。
結果セットまたはエラーメッセージの変換を実行しないようにサーバーに指示するには、character_set_results を NULL または binary に設定します:
character_set_server
、character_set_database
、character_set_client
、character_set_connection
はutf8mb4
で統一していれば
問題なさそうですし、そうするとcharacter_set_results
は明示的に指定しなくてもいいのでは、という感じでしょうか。
character_set_results
を指定したとしても、utf8mb4
でしょうね。
それぞれのシステム変数のドキュメントと説明は、こちら。
- character_set_server … サーバーのデフォルトの文字セット
- character_set_database … デフォルトデータベースで使用される文字セット
- 現在は非推奨の設定
- character_set_client … クライアントから到達するステートメントの文字セット
- character_set_connection … 文字セットイントロデューサなしで指定されたリテラルおよび数値から文字列への変換に使用される文字セット
- character_set_results … クエリー結果をクライアントに返すために使用される文字セット。 これには、カラム値、結果メタデータ (カラム名など)、エラーメッセージなどの結果データが含まれます。
- collation_server … サーバーのデフォルトの照合順序
- collation_database … デフォルトデータベースで使用される照合
- 現在は非推奨の設定
- collation_connection … 接続文字セットの照合順序。collation_connection は、リテラル文字列の比較に重要です。 カラム値と文字列を比較する場合、collation_connection は関係ありません。これは、カラムには照合優先度の高い独自の照合があるためです
MySQL Connector/Jに話を戻すと
ここまでの話から、MySQL Connector/Jの設定に話を戻すと、characterEncoding
、characterSetResults
、connectionCollation
のそれぞれを
どう指定すればいいのか?ということなのですが。
MySQLサーバー側のCharacter Setをutf8mb4
に統一するのなら
characterEncoding
…UTF-8
characterSetResults
… 指定なしconnectionCollation
… 指定しなくても実害はなさそう(文字列リテラルの比較のみの話なので)だが、気になるならサーバーと同じCollationを指定
といったところでしょうか。
characterEncoding
についてはMySQL Connector/Jの説明(デフォルト値の部分)が気になるので、この後にテストコードを書いて
確認してみることにします。
環境
今回の環境はこちら。
$ java --version openjdk 17.0.2 2022-01-18 OpenJDK Runtime Environment (build 17.0.2+8-Ubuntu-120.04) OpenJDK 64-Bit Server VM (build 17.0.2+8-Ubuntu-120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-109-generic", arch: "amd64", family: "unix"
MySQLはこちらのバージョンで、172.17.0.2で動作しているものとします。
$ mysql --version mysql Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)
また、サーバーのCharacter SetおよびCollationは以下の設定としておきます。
character-set-server = utf8mb4 collation-server = utf8mb4_0900_bin
準備
作成したMavenプロジェクトの依存関係等は、こちら。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.22.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build>
次に、テストコードの雛形を作成します。
src/test/java/org/littlewings/mysql/ConnectorCharacterSetTest.java
package org.littlewings.mysql; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; public class ConnectorCharacterSetTest { List<String> characterSetVariables = List.of( "character_set_connection", "character_set_client", "character_set_database", "character_set_filesystem", "character_set_results", "character_set_server", "character_set_system" ); List<String> collationVariables = List.of( "collation_connection", "collation_database", "collation_server" ); private Map<String, String> collectCharacterSets(Connection conn) throws SQLException { Map<String, String> characterSets = new LinkedHashMap<>(); for (String characterSetVariable : characterSetVariables) { try (PreparedStatement ps = conn.prepareStatement("show variables where variable_name = ?")) { ps.setString(1, characterSetVariable); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { characterSets.put(rs.getString(1), rs.getString(2)); } } } } return characterSets; } private Map<String, String> collectCollations(Connection conn) throws SQLException { Map<String, String> collations = new LinkedHashMap<>(); for (String collationVariable : collationVariables) { try (PreparedStatement ps = conn.prepareStatement("show variables where variable_name = ?")) { ps.setString(1, collationVariable); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { collations.put(rs.getString(1), rs.getString(2)); } } } } return collations; } // ここに、テストを書く!! }
現在の接続内でのCharacter SetおよびCollationを収集するメソッドを用意して、以降に作成するテストで接続プロパティを変更するとともに、
これらのシステム変数がどのように変化していくかを見ていくことにします。
characterEncodingを確認してみる
とりあえず、なにも指定しない場合。
@Test public void nonSettings() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
character_set_connection
はutf8mb4
になっていますが、MySQL Connector/J 8.0.25以前の場合はサーバー側のデフォルトの
Character Setが使われることになりますがこれはutf8mb4
にしていますし、今回使用しているMySQL Connector/Jは8.0.28(8.0.26以降)
なのでどちらにしろutf8mb4
です。
collation_connection
はutf8mb4
のデフォルトのCollationである、utf8mb4_0900_ai_ci
ですね。
characterEncoding
にutf8mb4
を指定してみます。
@Test public void utf8mb4CharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=utf8mb4"; String username = "kazuhira"; String password = "password"; assertThatThrownBy(() -> DriverManager.getConnection(url, username, password)) .isInstanceOf(SQLException.class) .hasMessage("Unsupported character encoding 'utf8mb4'"); }
これは、例外がスローされます。JavaのCharset
としては指定できないからでしょうか。
UTF-8
を指定した場合は、utf8mb4
になっていますね。
@Test public void utf8CharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=UTF-8"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
今では使うことはないと思いますが、試しにWindows-31J
にしてみます。
@Test public void windows31jCharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=Windows-31J"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed entry("character_set_client", "cp932"), // changed entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_japanese_ci"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
すると、character_set_connection
とcharacter_set_client
はcp932
に、collation_connection
はcp932_japanese_ci
に変化しました。
というわけで、ドキュメントに書かれているとおり、characterEncoding
はJavaのエンコーディングで指定するのが正しいみたいですね。
characterEncoding
にutf8mb4
のようなJavaのCharset
としては無効な値を指定すると例外がスローされるのは、String#getBytes
で
確認しているからのようです。
また、UTF-8
がutf8mb4
のようになるのは、MySQL Connector/Jの中でJavaのCharset
として有効なエンコーディングと
MySQLのCharacter Setに対する変換表を持っているからみたいですね。
UTF-8
の場合は、こちら。対応するのが2つありますが、最終的に選択されるのはutf8mb4
になります。
new MysqlCharset(MYSQL_CHARSET_NAME_utf8, 3, 0, new String[] { "UTF-8" }), new MysqlCharset(MYSQL_CHARSET_NAME_utf8mb4, 4, 1, new String[] { "UTF-8" }), // "UTF-8 = *> 5.5.2 utf8mb4"
これで、characterEncoding
についての挙動はわかりました。
characterSetResultsを確認してみる
次は、characterSetResults
を確認してみます。
characterSetResults
を指定しない場合のcharacter_set_results
は、未設定でした。
@Test public void nonSettings() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
utf8mb4
を指定してみます。
@Test public void utf8mb4CharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=utf8mb4"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "utf8mb4"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
すると、こちらは通ります。character_set_results
がutf8mb4
になりました。
では、UTF-8
を指定してみましょう。
@Test public void utf8CharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=UTF-8"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "utf8mb4"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
これも通ります。そして、こちらもcharacter_set_results
がutf8mb4
になっています。
Windows-31J
を指定すると、cp932
になっています。
@Test public void windows31jCharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=Windows-31J"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "cp932"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
さて、どうなっているんでしょう?
こちらもやはり、JavaのCharset
をMySQLのCharacter Setに変換しようとするみたいです。
反対に、MySQLのCharacter Setとしか解釈できない値を指定した場合は、1度JavaのCharset
として有効なエンコーディングに
変換するようです。
この時に使う変換表も、characterEncoding
の時と同じですね。
connectionCollationを確認してみる
最後に、connectionCollation
を確認してみます。
なにも指定していない時は、collation_connection
はデフォルトのCharacter Setであるutf8mb4
のデフォルトのCollation、utf8mb4_0900_ai_ci
に
なっていました。
@Test public void nonSettings() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
connectionCollation
にutf8mb4_0900_bin
を指定してみます。
@Test public void utf8mb4_0900_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "connectionCollation=utf8mb4_0900_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
collation_connection
がutf8mb4_0900_bin
になりました。
cp932_bin
を指定してみます。
@Test public void cp932_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "connectionCollation=cp932_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed entry("character_set_client", "cp932"), // changed entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
こちらも反映されました。
最後に、characterEncoding
にはUTF-8
、connectionCollation
にはcp932_bin
と矛盾した内容を設定してみます。
@Test public void utf8CharacterEncoding_cp932_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=UTF-8&connectionCollation=cp932_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed & override entry("character_set_client", "cp932"), // changed & override entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } }
これは、ドキュメントに書かれている通り、characterEncoding
の値がconnectionCollation
で指定したCharacter Setで上書きされます。
今回は、character_set_connection
とcharacter_set_client
がcp932
になりましたね。
このようなケースは、以下の部分でCharacter Setの値が補正されます。
こんな感じで、実際の挙動が確認できました。
まとめ
今回は、MySQL Connector/Jの設定を見ていて、Character Set/Character Set Results/Connection Collationに関する項目と、
そもそもこれらの意味がちゃんとわかっていなかったなと思ってちょっと調べてみました。
ちゃんとドキュメントを見てみると、心配しすぎだったかな、という気がしないでもないですが。いつももやもやしていたので、
この機会に見ておいて意味はあったかなと思います。
ちなみに、このあたりを見ていると、これらの変数で指定した値は最終的にはSET NAMES
やSET character_set_results
として
実行されるようですね。
最後に、今回作成したテストコードの全体を載せておきます。
src/test/java/org/littlewings/mysql/ConnectorCharacterSetTest.java
package org.littlewings.mysql; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; public class ConnectorCharacterSetTest { List<String> characterSetVariables = List.of( "character_set_connection", "character_set_client", "character_set_database", "character_set_filesystem", "character_set_results", "character_set_server", "character_set_system" ); List<String> collationVariables = List.of( "collation_connection", "collation_database", "collation_server" ); private Map<String, String> collectCharacterSets(Connection conn) throws SQLException { Map<String, String> characterSets = new LinkedHashMap<>(); for (String characterSetVariable : characterSetVariables) { try (PreparedStatement ps = conn.prepareStatement("show variables where variable_name = ?")) { ps.setString(1, characterSetVariable); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { characterSets.put(rs.getString(1), rs.getString(2)); } } } } return characterSets; } private Map<String, String> collectCollations(Connection conn) throws SQLException { Map<String, String> collations = new LinkedHashMap<>(); for (String collationVariable : collationVariables) { try (PreparedStatement ps = conn.prepareStatement("show variables where variable_name = ?")) { ps.setString(1, collationVariable); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { collations.put(rs.getString(1), rs.getString(2)); } } } } return collations; } @Test public void nonSettings() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void utf8mb4CharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=utf8mb4"; String username = "kazuhira"; String password = "password"; assertThatThrownBy(() -> DriverManager.getConnection(url, username, password)) .isInstanceOf(SQLException.class) .hasMessage("Unsupported character encoding 'utf8mb4'"); } @Test public void utf8CharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=UTF-8"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void windows31jCharacterEncoding() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=Windows-31J"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed entry("character_set_client", "cp932"), // changed entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_japanese_ci"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void utf8mb4CharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=utf8mb4"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "utf8mb4"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void utf8CharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=UTF-8"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "utf8mb4"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void windows31jCharacterSetResults() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterSetResults=Windows-31J"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", "cp932"), // changed entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_ai_ci"), entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void utf8mb4_0900_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "connectionCollation=utf8mb4_0900_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "utf8mb4"), entry("character_set_client", "utf8mb4"), entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "utf8mb4_0900_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void cp932_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "connectionCollation=cp932_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed entry("character_set_client", "cp932"), // changed entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } @Test public void utf8CharacterEncoding_cp932_bin_connectionCollation() throws SQLException { String url = "jdbc:mysql://172.17.0.2:3306/practice?" + "characterEncoding=UTF-8&connectionCollation=cp932_bin"; String username = "kazuhira"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, username, password)) { Map<String, String> characterSets = collectCharacterSets(conn); Map<String, String> collations = collectCollations(conn); assertThat(characterSets).containsExactly( entry("character_set_connection", "cp932"), // changed & override entry("character_set_client", "cp932"), // changed & override entry("character_set_database", "utf8mb4"), entry("character_set_filesystem", "binary"), entry("character_set_results", ""), entry("character_set_server", "utf8mb4"), entry("character_set_system", "utf8mb3") ); assertThat(collations).containsExactly( entry("collation_connection", "cp932_bin"), // changed entry("collation_database", "utf8mb4_0900_bin"), entry("collation_server", "utf8mb4_0900_bin") ); } } }