やりたいこと
JAX-RS(Jersey)× CDI × MyBatisの構成で開発するRESTful APIにおいて、SQLExceptionに対する例外処理を実施したい。
不具合
ExceptionMapperを用いてJAX-RSランタイムにて発生したSQLExceptionをキャッチできるような処理を追加したのですが、SQLExceptiontnがthrowされず処理できません。
原因調査
下記IBM様の記事サイトに以下のような記述がありました。
JPA プロバイダー実装は通常、SQL 状態またはエラー・コードがデータベース・サーバー問題またはネットワーク問題を示す可能性のある SQLException を伴う一般 PersistenceException をスローするだけです。(下記サイトから引用)
今回O/RマッパーはJPAではなくMyBatisを使っていますが、どうやらフレームワークによってはSQLExceptionではなくSQLExceptionをラップしたPersistenceExceptionがスローされるようです。
解決策
SQLExceptiponではなく、PersistenceExceptionをキャッチするように書き換えれば処理できそうです。
以下、ExceptionMapperを実装してPersistenceExceptionをキャッチするサンプルコードです。
例)PersistenceExceptionMapper.java
package com.example.exception.exceptionmapper;
import java.sql.SQLException;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import org.apache.ibatis.exceptions.PersistenceException;
import com.example.dto.ExceptionDto;
import com.example.exception.exceptionjson.ExceptionJson;
import com.example.util.Message;
@Provider
public class PersistenceExceptionMapper implements ExceptionMapper<PersistenceException> {
@Inject
private ExceptionJson json;
@Override
public Response toResponse(PersistenceException exception) {
SQLException e = (SQLException)exception.getCause();
ExceptionDto dto = new ExceptionDto();
dto.setCode(Message.CODE_500);
if("08001".equals(e.getSQLState())) {
dto.setMsg(Message.EXCEPTION_4);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(json.exceptionJson(dto))
.type(MediaType.APPLICATION_JSON)
.build();
}else {
dto.setMsg(Message.EXCEPTION_5);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(json.exceptionJson(dto))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
}
以下のように、PersistenceExceptionからSQLExceptionを取り出すことで、SQLExceptionのステータス等詳細に応じた処理を記述することができます。
//PersistenceExceptionからSQLExceptionを取り出す
//(exceptionはPersistenceException型)
SQLException e = (SQLException)exception.getCause();
ExceptionDto dto = new ExceptionDto();
dto.setCode(Message.CODE_500);
//SQLExceptionのステータスが08001かそれ以外かで処理分岐
if("08001".equals(e.getSQLState())) {
dto.setMsg(Message.EXCEPTION_4);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(json.exceptionJson(dto))
.type(MediaType.APPLICATION_JSON)
.build();
}else {
dto.setMsg(Message.EXCEPTION_5);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(json.exceptionJson(dto))
.type(MediaType.APPLICATION_JSON)
.build();
}
ソースコード
上記サンプルコードはGithubにコミットしています。