Spring BootからMySQLに繋ごうとして、application.ymlに何を書けばいいのか迷っていませんか?
接続自体はできたのに、保存した日時が9時間ずれる、日本語が「???」になる、起動した瞬間に Public Key Retrieval is not allowed で落ちる、という相談もよく受けます。
この記事では、Docker ComposeでMySQL 8を起動するところから、依存追加、application.ymlのdatasource設定、疎通確認までを一気通貫でやります。そのうえでJDBC URLパラメータの意味と推奨値、定番エラー、タイムゾーンと文字コードのずれの直し方を整理します。
エラーメッセージで検索してたどり着いた方は、「定番エラーと対処法」の節から読んでもらって大丈夫です。
この記事のゴールと前提環境
前提は次のとおりです。
- Spring Boot 3.x(Java 21)
- MySQL 8.0 または 8.4
- JDBCドライバは
com.mysql:mysql-connector-j
ドライバの座標は、昔の mysql:mysql-connector-java から com.mysql:mysql-connector-j に変わっています。Spring Boot 3.x は旧座標をバージョン管理していないので、バージョン指定なしで書くと依存解決に失敗します。古い記事からコピーするときは com.mysql:mysql-connector-j に読み替えてください。
ゴールは「MySQL起動 → 依存追加 → application.yml → 疎通確認」の4ステップです。datasource設定はJPAでもMyBatisでも共通なので、どちらを使う予定でもそのまま読み進められます。
Docker ComposeでMySQL 8を起動する
MySQLのインストール方法はいろいろありますが、ここではDocker Composeに絞ります。文字コードとタイムゾーンを最初から固定しておくのがポイントです。
# compose.yaml
services:
mysql:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: appdb
MYSQL_USER: app
MYSQL_PASSWORD: secret
TZ: Asia/Tokyo
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
interval: 5s
timeout: 3s
retries: 10
volumes:
mysql-data:
MYSQL_DATABASE と MYSQL_USER を指定しておくと、初回起動時にデータベースと、そのDBへの権限を持つユーザーが自動で作られます。named volumeを付けているので、コンテナを消してもデータは残ります。TZ が効くのは公式の mysql イメージに tzdata が入っているためで、軽量ビルドのイメージでは効かないことがあります。
docker compose up -d のあと、一度だけ中身を確認しておきましょう。
docker compose exec mysql mysql -uroot -proot \
-e "SHOW VARIABLES LIKE 'character_set_server'; SELECT @@global.time_zone, @@system_time_zone;"
utf8mb4 と JST が出ていればOKです。ここを最初に見ておくと、あとで「化けた」「ずれた」となったときにサーバ側を疑わなくて済みます。
Spring Bootアプリ自体もコンテナで動かしたい場合は Spring BootアプリをDockerコンテナで動かす方法 が参考になります。
依存関係を追加する(Gradle / Maven)
ドライバは runtimeOnly で十分です。アプリのコードはJDBC APIにしか依存しないので、ドライバの実装クラスをコンパイル時に見せる必要がありません。バージョンはSpring Bootの依存管理に任せます。
// build.gradle
dependencies {
// JPAを使うなら
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// MyBatisを使うならこちら(どちらか一方でOK)
// Boot の依存管理外なので、Maven Central で最新の 3.0.x を確認して指定する
// implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
runtimeOnly 'com.mysql:mysql-connector-j'
}
Mavenならこうです。
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
JPAとMyBatisのどちらを選んでも、次に書くdatasource設定は同じです。
application.ymlの最小構成
まず接続できる最小構成はこの3項目です。
spring:
datasource:
url: jdbc:mysql://localhost:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8
username: app
password: ${DB_PASSWORD:secret}
driver-class-name は書いていません。Spring BootがURLの jdbc:mysql: から com.mysql.cj.jdbc.Driver を自動判別してくれるためです。明示しても害はありませんが、書かなくて動くものは書かない方が設定は減ります。
パスワードは ${DB_PASSWORD} の形で環境変数から読みます。${DB_PASSWORD:secret} のようにデフォルト値を付ける書き方は便利ですが、本番のYAMLに残すと事故のもとなので開発用に限定しましょう。
URLの後ろに付けたパラメータは、次の節で説明します。
JDBC URLパラメータの意味と推奨値
Connector/J 8.x でよく使うパラメータをまとめます。古い記事に出てくる useUnicode=true や serverTimezone は、今は不要か別名扱いです。
| パラメータ | 意味 | 推奨値 | 用途 |
|---|---|---|---|
connectionTimeZone | JDBCが日時を変換する基準のタイムゾーン | Asia/Tokyo か UTC | 共通 |
forceConnectionTimeZoneToSession | セッションの time_zone も同じ値に揃える | true(TIMESTAMP型を使うなら) | 共通 |
characterEncoding | 接続の文字コード。UTF-8 は utf8mb4 として扱われる | UTF-8 | 共通 |
sslMode | SSLの扱い。useSSL の後継 | 開発 DISABLED / 本番 REQUIRED 以上 | 両方 |
allowPublicKeyRetrieval | 非SSL時にサーバ公開鍵の取得を許可する | 開発のみ true | 開発 |
createDatabaseIfNotExist | DBがなければ作成する(CREATE権限が必要) | 開発のみ true | 開発 |
serverTimezone は Connector/J 8.0.23 以降 connectionTimeZone の別名として残っているだけなので、新しく書くなら connectionTimeZone にしましょう。useSSL も同様に sslMode へ置き換わっていて、両方書くと sslMode が優先されます。
createDatabaseIfNotExist は接続ユーザーにグローバルな CREATE 権限が必要です。MYSQL_USER で作った app ユーザーは MYSQL_DATABASE に対する権限しか持たないので、別名のDBを作ろうとすると Access denied ... to database になります。
開発用と本番用の完成形を並べるとこうなります。
spring:
datasource:
# 開発用
url: jdbc:mysql://localhost:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8&sslMode=DISABLED&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
# 本番用(コメントを外して差し替える)
# url: jdbc:mysql://db.example.internal:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8&sslMode=REQUIRED
本番では allowPublicKeyRetrieval と createDatabaseIfNotExist を外す、と覚えておけば十分です。
疎通確認(SELECT 1とテーブル作成)
まずはJPAにもMyBatisにも依存しない方法で確認しましょう。JdbcTemplate はどちらのスターターにも含まれているので、そのまま使えます。
@Component
public class ConnectionCheckRunner implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(ConnectionCheckRunner.class);
private final JdbcTemplate jdbcTemplate;
public ConnectionCheckRunner(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void run(String... args) {
Integer one = jdbcTemplate.queryForObject("SELECT 1", Integer.class);
String tz = jdbcTemplate.queryForObject("SELECT @@session.time_zone", String.class);
log.info("SELECT 1 = {}, session time_zone = {}", one, tz);
}
}
起動ログに HikariPool-1 - Start completed. が出ていれば、接続そのものは成功しています。そのあとに SELECT 1 = 1 が出れば疎通完了です。JdbcTemplate の使い方は Spring BootのJdbcTemplateで素のSQLを実行する方法 にまとめています。
JPAとMyBatisそれぞれの最小追加設定はこれだけです。
spring:
jpa:
hibernate:
ddl-auto: update # 開発用。本番では validate か none
show-sql: true
# MyBatisの場合はこちら
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
ddl-auto: update にしておけば、@Entity を1つ書いて起動するだけでテーブルが作られます。本番で update のままにしておくと、エンティティの変更が勝手にスキーマへ反映されて怖いので、validate か none にして後述のFlywayに任せましょう。Mapperの書き方は Spring BootでMyBatisを使う方法 を参照してください。
定番エラーと対処法
エラー文で検索してきた方のために、原因と修正を短くまとめます。
Public Key Retrieval is not allowed
原因は、MySQL 8 の既定認証 caching_sha2_password と非SSL接続(sslMode=DISABLED)の組み合わせです。開発なら allowPublicKeyRetrieval=true を付けるか、SSLを有効にすれば直ります。
The server time zone value 'JST' is unrecognized or represents more than one time zone.
原因は、サーバの system_time_zone が JST のような略称で、Connector/Jが解釈できないことです。主に 8.0.22 以前のドライバか serverTimezone 未指定の旧構成で出ます。8.0.23 以降は connectionTimeZone の既定が LOCAL(JVMのタイムゾーン)で、サーバ側を参照するのは connectionTimeZone=SERVER を指定したときだけなので、既定では出ません。古い記事の依存をコピーしていないか確認したうえで、connectionTimeZone=Asia/Tokyo(旧ドライバなら serverTimezone)を明示しておくのが確実です。
Communications link failure
原因は、ホスト名・ポートの誤りか、MySQLコンテナが起動しきっていないことです。Compose内のアプリからはサービス名 mysql で繋ぎ、depends_on に condition: service_healthy を付けて起動を待たせましょう。
Access denied for user 'app'@'172.18.0.1'
原因は、パスワード違いか、接続元ホストが許可されていないことです。MYSQL_USER のユーザーは 'app'@'%' ですが、手動で 'app'@'localhost' と作るとコンテナ外からは弾かれます。
Unknown database 'appdb'
原因は、DBが未作成なことです。MYSQL_DATABASE で作るか、CREATE権限のあるユーザーで createDatabaseIfNotExist=true を付けてください。
日時がずれる原因と対処
接続できたのに保存した日時が9時間ずれる、という相談は本当に多いです。関係する層が3つあるのを押さえましょう。
- JVMのデフォルトタイムゾーン(
user.timezone) - MySQLサーバとセッションの
time_zone - Connector/Jの変換基準(
connectionTimeZone)
さらに、MySQLの TIMESTAMP 型はUTCで保存されてセッションの time_zone で変換されるのに対し、DATETIME 型は変換されずそのまま保存されます。「同じ値を入れたのに列の型で結果が違う」のはこれが原因です。
Java側の型は、DATETIME 列には変換のない LocalDateTime を、絶対時刻として扱いたい値は Instant を TIMESTAMP 列に対応させるのが分かりやすいです。OffsetDateTime はオフセット付きで受け渡ししたいAPI境界向けで、MySQLにはオフセットを保持する型がないので保存時は TIMESTAMP 相当に変換される点に注意してください。JPAなら spring.jpa.properties.hibernate.jdbc.time_zone でHibernateがJDBCに渡すときの基準も固定できます。
JVM層は起動時の java -Duser.timezone=Asia/Tokyo -jar app.jar、コンテナなら環境変数 TZ=Asia/Tokyo で固定できます。迷ったらサーバ・JVM・接続の3つを同じタイムゾーンに揃えるか、すべてUTCに揃えるかのどちらかにしてください。ずれているときは SELECT @@session.time_zone, NOW() で現状を確認できます。確認クエリは次の節にまとめて載せておきます。
日本語が文字化けする原因と対処
文字化けも同じく、サーバ側と接続側の2か所を見ます。
サーバ側は character_set_server と、データベース・テーブルの CHARACTER SET / COLLATION です。Docker Composeの command で utf8mb4 を固定していれば、あとから作るDBとテーブルは自動でそれを引き継ぎます。接続側は characterEncoding=UTF-8 で、Connector/Jはこれを utf8mb4 にマッピングします。
注意したいのは、MySQLの utf8 が実は3バイトまでしか扱えない utf8mb3 の別名だということです。古いテーブルが utf8 で作られていると、絵文字を入れた瞬間に Incorrect string value で落ちます。既存テーブルは SHOW CREATE TABLE で確認し、utf8mb3 なら utf8mb4 に変換しておきましょう。
-- タイムゾーンの確認
SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone, NOW();
-- 文字コードの確認と変換
SHOW VARIABLES LIKE 'character_set%';
SHOW CREATE TABLE users;
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
HikariCPとの関係とmaxLifetime設定
ここまで一度もプールの設定をしていませんが、Spring Bootはデフォルトで HikariCP を使うので、すでにコネクションプール経由で接続しています。起動ログの HikariPool-1 がその証拠です。
MySQLで1つだけ気にしてほしいのが max-lifetime です。MySQLは wait_timeout(デフォルト28800秒)を過ぎたアイドル接続をサーバ側で切ります。HikariCP側の寿命がそれより長いと、貸し出し時の isValid() 検証に失敗して Failed to validate connection ... Possibly consider using a shorter maxLifetime value. という WARN ログが出続けます。クエリの実行中にサーバ側で切られた場合は Communications link failure になります。HikariCPのデフォルトは30分なので普段は問題ありませんが、サーバ側の wait_timeout を短くしている環境では必ず下回るように設定してください。
spring:
datasource:
hikari:
max-lifetime: 570000 # wait_timeout=600 なら数十秒短くする
maximum-pool-size: 10
connection-test-query: SELECT 1 を書いている設定をたまに見ますが、Connector/Jは JDBC4 の isValid() に対応しているので不要です。書くとむしろ検証が遅くなります。プールサイズの算出やActuatorでの監視は Spring BootのHikariCPコネクションプールを正しく設定・チューニングする方法 で詳しく扱っています。
Flywayでスキーマを管理する場合
ddl-auto: update は手軽ですが、本番のスキーマ管理には向きません。Flywayに任せる場合、MySQLでは flyway-core だけでは足りず flyway-mysql が必要です。これがないと起動時に Unsupported Database: MySQL で落ちます。
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
あわせて spring.jpa.hibernate.ddl-auto を validate にして、スキーマはFlywayが管理しHibernateは検証だけ、という役割分担にします。あとは src/main/resources/db/migration/V1__create_users.sql を1本置けば、起動時に自動で適用されます。MySQL 8.4 に対して Spring Boot が管理する Flyway が古いと Flyway upgrade recommended: MySQL 8.4 is newer than this version of Flyway と出ますが、失敗ではなく警告なので慌てなくて大丈夫です。バージョニングの運用や本番適用の流れは Spring BootでFlywayを使ったデータベースマイグレーション管理 にまとめてあります。
本番向けの設定(SSLとプロファイルの切り替え)
開発用のURLをそのまま本番に持ち込まないよう、プロファイルで分けておきましょう。
# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8&sslMode=DISABLED&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
jpa:
hibernate:
ddl-auto: update
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8&sslMode=REQUIRED
# 証明書まで検証する場合。CA証明書を入れたトラストストアが必要
# url: jdbc:mysql://${DB_HOST}:3306/appdb?connectionTimeZone=Asia/Tokyo&characterEncoding=UTF-8&sslMode=VERIFY_CA&trustCertificateKeyStoreUrl=file:/etc/app/ca-truststore.p12&trustCertificateKeyStorePassword=${TRUSTSTORE_PASSWORD}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
sslMode=REQUIRED は通信を暗号化しますが、サーバ証明書の検証まではしません。VERIFY_CA や VERIFY_IDENTITY に上げるなら、サーバ証明書を発行したCA証明書をトラストストアに登録しておく必要があります。方法は trustCertificateKeyStoreUrl でPKCS12のトラストストアを指定するか、JVMの cacerts にCAを取り込んでおくかのどちらかです(fallbackToSystemTrustStore が既定で true なので、後者はURLに何も足さなくても参照されます)。RDSやCloud SQLのような私設CAで、登録なしに VERIFY_CA にすると PKIX path building failed で接続に失敗します。
本番のポイントは3つです。sslMode を REQUIRED 以上にすること、allowPublicKeyRetrieval と createDatabaseIfNotExist を外すこと、パスワードを環境変数やシークレット管理から注入してYAMLに書かないことです。プロファイルの切り替え方は Spring BootのProfileを使って環境によって違う設定を安全に切り替える方法 を参照してください。
まとめ
Spring BootとMySQLの接続でつまずくポイントは、だいたい「繋がらない」「ずれる」「化ける」の3つに集約されます。繋がらないのはURLパラメータ(allowPublicKeyRetrieval と sslMode)、ずれるのはJVM・サーバ・接続の3層のタイムゾーン、化けるのは utf8mb4 の統一で解決できます。
開発用URLには sslMode=DISABLED&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true を付けて楽をし、本番では sslMode=REQUIRED 以上にしてそれらを外す。この切り替えさえ守れば、接続まわりで困ることはほぼなくなるはずです。
接続ができたら、次は REST APIのCRUD実装 や、初期データを入れるための data.sql・schema.sqlの使い方 に進んでみてください。