こんにちは、エキサイト株式会社の岩藤です。
JavaのSpring Bootを使用して、PostgreSQLデータベースのinet型やcidr型にIPアドレスを"124.24.32.2"の形式でINSERTすると、データ型の不一致によるエラーが発生することがあります。
本記事では、このエラーを回避するための解決策について解説します。
- 動作確認環境
- PostgreSQLでIPアドレスを扱う型
- 作成したテーブル
- 手動INSERTした場合
- MyBatis Generatorで自動生成したクラスでINSERTした場合
- Freemarkerテンプレート(ftlファイル)を作成してINSERT文を作成した場合
- データ型不一致のエラー解決策 Freemarkerテンプレート(ftlファイル)を利用した場合
- データ型不一致のエラー解決策 Mybatisの場合(断念)
動作確認環境
openjdk 21.0.2 2024-01-16 LTS OpenJDK Runtime Environment Corretto-21.0.2.13.1 (build 21.0.2+13-LTS) OpenJDK 64-Bit Server VM Corretto-21.0.2.13.1 (build 21.0.2+13-LTS, mixed mode, sharing)
Spring Boot
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.2.1)
Gradle
Build time: 2023-11-29 14:08:57 UTC Revision: 28aca86a7180baa17117e0e5ba01d8ea9feca598 Kotlin: 1.9.20 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 21.0.2 (Amazon.com Inc. 21.0.2+13-LTS) OS: Mac OS X 14.7 aarch64
PostgreSQL 15.4 (Debian 15.4-2.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
PostgreSQLでIPアドレスを扱う型
PostgreSQLでIPアドレスを扱う型は下記の3種類があります。
データ型 | 説明 | 例 |
---|---|---|
cidr型 | ネットワークアドレスを表現するデータ型。IPアドレスとサブネットマスクを含む。 | 192.168.100.0/24 |
inet型 | ホストやネットワークのIPアドレスを格納するデータ型。IPv4やIPv6に対応している。 | 192.168.100.15 または 192.168.100.15/24 |
macaddr型 | MACアドレス(ネットワーク機器の物理アドレス)を格納するデータ型。 | 08:00:2b:01:02:03 |
今回は、ネットワークのIPアドレスを格納したく、サブネットマスクなしで242.168.100.15のように登録したいので、inet型を採用しました。
作成したテーブル
CREATE文
CREATE TABLE ip_address ( id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, ip_address INET NOT NULL, created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(), PRIMARY KEY (id) ); COMMENT ON TABLE ip_address IS 'IPアドレス管理'; COMMENT ON COLUMN ip_address.id IS 'ID'; COMMENT ON COLUMN ip_address.ip_address IS 'IPアドレス'; COMMENT ON COLUMN ip_address.created_at IS '作成日時'; CREATE INDEX ip_address_index ON ip_address (ip_address);
手動INSERTした場合
作成したテーブルに手動でINSERTしてみます。
INSERT INTO ip_address (id, ip_address) VALUES (1, '18.177.66.153'); Query 1 OK: INSERT 0 1, 1 row affected
手動でINSERTした場合は、エラーにならずにINSERTされました。
なお、CIDR型の場合は、同様のINSERT文を実行した際に自動で /32 のサブネットマスクが付与されます。
18.177.66.153/32
MyBatis Generatorで自動生成したクラスでINSERTした場合
id = 1L, ip = "18.177.66.153"
@Override public void insertIpAddress(final Long id, final String ip) { IpAddress ipAddress = new IpAddress(); ipAddress.setIpAddress(ip); ipAddressMapper.insertSelective(ipAddress); }
実行結果
Error updating database. Cause: org.postgresql.util.PSQLException: ERROR: column "ip_address" is of type inet but expression is of type character varying ヒント: You will need to rewrite or cast the expression.
挿入しようとしている値が文字列型(character varying)であるため、型の不一致によるエラーが発生しました。
Freemarkerテンプレート(ftlファイル)を作成してINSERT文を作成した場合
ip_address_insert.ftl id = 1L, ip = "18.177.66.153"
/* IPアドレスを登録 */ INSERT INTO ip_address (id, ip_address) VALUES (<@p name="id" />, <@p name="ipAddress" />);
実行結果
Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: column "ip_address" is of type inet but expression is of type character varying ヒント: You will need to rewrite or cast the expression.
こちらでも同様のエラーが発生しました。
CIDR型の場合も同様のエラーが確認できました。
INET型とCIDR型の両方において、プログラムを通じて実行した際に、文字列型でIPアドレスを指定すると型の不一致によりINSERTが失敗する事がわかりました。
データ型不一致のエラー解決策 Freemarkerテンプレート(ftlファイル)を利用した場合
INSERT文を下記の内容に変更します。
変更箇所は、VALUESのipAddress後ろに::inetが指定されている部分です。 これは型キャスト演算子と言います。
/* IPアドレスを登録 */ INSERT INTO ip_address (id, ip_address) VALUES (<@p name="id" />, <@p name="ipAddress" />::inet);
実行結果 inet型にキャストした事で型が一致してINSERTが成功しました!
CIDR型の場合は::cidrという指定になります。
データ型不一致のエラー解決策 Mybatisの場合(断念)
MyBatisでは以下のように、あらかじめ定義された型エイリアスを使用することが一般的です。
しかし、PostgreSQLのinet型に直接マッピングする場合、MyBatisはデフォルトでこのデータ型をサポートしていないため、独自に型エイリアスを定義する必要があります。
また、PostgreSQLのinet型に関連するJDBCドライバも、inet型をその他のデータ型として扱っています。
データ型 | 対応するJavaクラス | SQL型 |
---|---|---|
inet, cidr, macaddr | org.postgresql.util.PGobject | java.sql.Types.OTHER |
下記参照 JDBCドライバのドキュメント
つまり、MyBatisの型マッピングと、JDBCドライバ固有の処理を組み合わせて対応する必要があり、少し複雑な対応が必要そうで断念しました。
以上となります、同様のエラーでお困りの方は参考にいただけますと幸いです。