SchemaSpy HTMLレポートの日本語の文字化けを解決する方法

概要
データベースやDDLを基にテーブル定義やEntity Ralationship図(以下ER図)を出力したいモチベーションがあるとする。DDLに日本語が含まれているとき、HTMLドキュメントを生成すると、日本語部分が文字化けする。このドキュメントを読むことで、HTMLドキュメントの日本語の文字化けを解決することができます。
前提条件
SchemaSpyとは、データベースからスキーマ定義やER図をレポート形式で出力するソフトウェアです。レポートはHTML形式をサポートしており、インタラクティブで分かりやすくチームに共有することができます。

検証ではDocker Composeで環境を構築しています。
- Docker Compose version 2.4.1
- SchemaSpy version 6.1
- MySQL version 5.7
データベースはサンプルを作成しています。オンラインストアの会員 - 商品 - 注文を表現しています。
CREATE TABLE customers (
customer_id VARCHAR(36),
first_name VARCHAR(18) NOT NULL,
last_name VARCHAR(18) NOT NULL,
PRIMARY KEY (customer_id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT='会員';
CREATE TABLE products (
product_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
PRIMARY KEY (product_id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT='商品';
CREATE TABLE sales_orders (
sales_order_id INT AUTO_INCREMENT,
customer_id VARCHAR(36),
PRIMARY KEY (sales_order_id),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT='注文';
CREATE TABLE sales_order_items (
sales_order_item_id INT AUTO_INCREMENT,
parent_id INT,
product_id VARCHAR(36),
PRIMARY KEY (sales_order_item_id),
FOREIGN KEY (parent_id) REFERENCES sales_orders(sales_order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT='注文商品';
再現
docker composeコマンドからSchemaSpyのHTMLレポートを出力します。
$ docker compose up
HTMLレポートはoutputディレクトリに出力されます。HTMLファイルを確認すると日本語に対応した言葉が文字化けしています。

調査/原因
MySQLコンテナに接続し調査します。
テーブルのコメントは日本語です。
mysql> SHOW TABLE STATUS;
+-------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+-----------------+----------+----------------+--------------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment |
+-------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+-----------------+----------+----------------+--------------+
| customers | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 0 | 0 | NULL | 2022-07-31 06:22:03 | NULL | NULL | utf8_unicode_ci | NULL | | 会員 |
| products | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 0 | 0 | NULL | 2022-07-31 06:22:03 | NULL | NULL | utf8_unicode_ci | NULL | | 商品 |
| sales_order_items | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 32768 | 0 | 1 | 2022-07-31 06:22:03 | NULL | NULL | utf8_unicode_ci | NULL | | 注文商品 |
| sales_orders | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 16384 | 0 | 1 | 2022-07-31 06:22:03 | NULL | NULL | utf8_unicode_ci | NULL | | 注文 |
+-------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+-----------------+----------+----------------+--------------+
4 rows in set (0.00 sec)
文字セットについて確認します。システム変数のクライアントの文字セットが異なるため、文字化け発生していると仮定しました。
mysql> SHOW VARIABLES LIKE 'character_set%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
mysql> SHOW VARIABLES LIKE 'collation%';
+----------------------+-------------------+
| Variable_name | Value |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
| collation_database | latin1_swedish_ci |
| collation_server | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)
解決策
オプションファイルから文字セットを更新する
MySQLのオプションファイル(構成ファイル)から文字セットを更新します。
my.cnfファイルを追加してMySQLコンテナの構成ディレクトリにマウントします。
$ mkdir mysql/custom
$ touch mysql/custom/my.cnf
...
db:
image: mysql:5.7
volumes:
- ./mysql/custom:/etc/mysql/conf.d
...
[mysql]
default-charset-set=utf8
mysql> SHOW VARIABLES LIKE 'CHARACTER_%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
mysql> SHOW VARIABLES LIKE 'COLLATION_%';
+----------------------+-------------------+
| Variable_name | Value |
+----------------------+-------------------+
| collation_connection | utf8_general_ci |
| collation_database | latin1_swedish_ci |
| collation_server | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)
DDLに文字セットの更新を含める
セッションシステム変数を文字セットに更新することで対応します。SET NAMES ステートメントで実現できます。このステートメントは、character_set_clinet、character_set_connectionおよびcharacter_set_resultの3つのセッションシステム変数を特定の文字セットに設定できます。
SET NAMES 'utf8' COLLATE 'utf8_unicode_ci';
CREATE TABLE customers (
customer_id VARCHAR(36),
first_name VARCHAR(18) NOT NULL,
last_name VARCHAR(18) NOT NULL,
PRIMARY KEY (customer_id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT='会員';
...

まとめ
クライアント側の文字セットを更新することで、日本語文字化けに対応することができました。完全なコードは以下に公開しています。