【エラー解決方法】GenericJDBCException Could not open connection エラー発生!? を解決する方法

こんにちは、飯塚です。

業務中に「GenericJDBCException」という普段あまり見ないエラーが起こりました。発見当初はいまいち原因がよくわからなかったので、振り返りのためにまとめてみました。

目次

  1. GenericJDBCException – too many clients already
  2. 原因と解決法 (max_connections/EntityManagerFactory)
  3. 1. hibernateとプロセスの対応について調べてみた
  4. 2. hibernateとプロセスの対応 まとめ
  5. まとめ

GenericJDBCException – too many clients already

エラーログ

Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Could not open connection
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1387)
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
...(省略)

Caused by: org.postgresql.util.PSQLException: FATAL: sorry, too many clients already
	at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:291)
	at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:108)
	...(省略)

「sorry, too many clients already」と書いてあることから、DBへの接続数が最大になってしまったので新規のDB接続が確立されない、そのためSQLの発行に失敗しているようです。

ちなみにエラーログが流れて見失った場合は、下記のファイルを漁れば見つかるはずです。
※Windowsの場合

C:\Program Files\PostgreSQL\9.6\data\pg_log\postgresql-yyyy-MM-dd_HHmmss.log

原因と解決法 (max_connections/EntityManagerFactory)

結論から先に書くと、解決策は以下の2つです。

(1) DBの最大接続数を増やす。
(2) EntityManagerFactoryを適宜close()する。

(1) DBの最大接続数を増やす

postgresql.confを開いて、max_connectionsの値を変更します。パスは以下です。
※Windowsの場合

C:\Program Files\PostgreSQL\9.6\data\postgresql.conf

postgresql.conf (ファイルの64行目)

#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------

# - Connection Settings -

listen_addresses = '*'		# what IP address(es) to listen on;
					# comma-separated list of addresses;
					# defaults to 'localhost'; use '*' for all
					# (change requires restart)
port = 5432				# (change requires restart)
max_connections = 100			# (change requires restart)
#superuser_reserved_connections = 3	# (change requires restart)

変更したあとは、Postgreの再起動をお忘れなく!

(2) EntityManagerFactoryを適宜close()する。

例えば以下のようにEntityManagerFactoryを作成したとします。

	protected static EntityManagerFactory factory =
			Persistence.createEntityManagerFactory("persistenceUnit");

どこかでクローズの処理が洩れているはずなので、下記を追記して接続を閉じます。
factory.close();

ここで覚えておきたいのが、EntityManagerFactoryをclose()しない場合、factoryがクラス変数/ローカル変数、static変数/非static変数に関係なく、アプリ終了まで接続が閉じられないという点です。

1. hibernateとSQLの処理の対応について調べてみた

もう少し、エラーが起こるまでの流れをわかっておきたかったので、下記のようなSQLとDaoを使ってJava側とDB側の処理の対応を調べてみました。

TestDao.java

public class TestDao {

	protected static EntityManagerFactory factory =
			Persistence.createEntityManagerFactory("persistenceUnit"); // (1)

	/**
	 * 登録処理
	 * @param data
	 */
	public void add(TestEntity data) {

		EntityManager manager = factory.createEntityManager(); // (2)

		EntityTransaction transaction = manager.getTransaction(); // (3)
		transaction.begin(); // (4)
		manager.persist(data); // (5)
		transaction.commit(); // (6)

		manager.close(); // (7)
	}
}

DB側のプロセス確認用のSQL

select * from pg_stat_activity psa;

pg_stat_activityはDBに接続中のプロセス情報を管理するテーブルです。さらっと書けるといつか友だちに自慢できるかもしれません。

処理を順番に追うと以下のようになります。

①プロセスの開始

(1) protected static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");
(2) EntityManager manager = factory.createEntityManager();
(3) EntityTransaction transaction = manager.getTransaction();
(4) transaction.begin();

(1)でプロセスが開始されてから、(4)までは変更がありません。トランザクション開始でも変化がないのは意外です。

②INSERT

(5) manager.persist(data);

③COMMIT

(6) transaction.commit();
(7)vmanager.close();


manager.close()でも変更がないので要注意です。

④プロセスの終了

※ manager.close(); // もしくはアプリの終了

2. hibernateとプロセスの対応 まとめ

DB登録までの流れをまとめると以下のようになりました。

Java hibernateのログ SQL ステータス
(1) Instantiated session factory SELECT typname FROM pg_catalog.pg_type WHERE oid = $1 idle
(2)
(3)
(4) AbstractTransactionImpl – begin Obtaining JDBC connection
(5) executing identity-insert immediately insert into test (created_at, name, updated_at) values ($1, $2, $3) RETURNING * idle in transaction
(6) AbstractTransactionImpl – committing COMMIT idle
(7) Releasing JDBC connection
factory.close() Cleaning up connection pool プロセスが死ぬ (接続が閉じる)

(7)でEntityManagerをcloseしたときに、セッションは閉じられますが接続は閉じられないことに注意しましょう。

意外と紛らわしいのが、コミット直後のログに「Releasing JDBC connection」と書かれていても、プロセスは死んでいない(=接続は閉じていない)ことです。ログだけ読むと、接続はここで閉じているんだなと誤解しそうですよね。

まとめ

今回のエラーから学んだのは、テーブルからデータを取ってくるだけではなく、インフラ的な知識もわかってこそSQLを知っていると言えるのだということです。勉強不足を痛感しました。
pg_stat_activityのapplication_nameがわかるとエラー原因調査も捗るはずなので、設定方法がわかったら別記事でまとめたいと思います。

記事をシェア
MOST VIEWED ARTICLES