JPAエラー「java.lang.Stringを[Ljava.lang.String;」にキャストできません. 」の修正
1前書き
もちろん、Javaで
String
を
__String
__配列にキャストできるとは限りません。
java.lang.String cannot be cast to[Ljava.lang.String;
しかし、これは一般的なJPAエラーです。
このクイックチュートリアルでは、これがどのように発生するのか、およびそれを解決する方法を説明します。
2 JPA
における一般的なエラーケース
JPAでは、ネイティブクエリを処理するときにこのエラーが発生することは珍しくありません。また、
EntityManager
の
createNativeQuery
メソッドを使用します。
そのhttps://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html#createNativeQuery-java.lang.String-[Javadoc]は、実際には** このメソッドは
Objectのリストを返すことを警告しています[]
、またはクエリによって返される列が1つだけの場合は
Object
だけです。
例を見てみましょう。まず、すべてのクエリを実行するために再利用したいクエリエグゼキュータを作成しましょう。
public class QueryExecutor {
public static List<String[]> executeNativeQueryNoCastCheck(String statement, EntityManager em) {
Query query = em.createNativeQuery(statement);
return query.getResultList();
}
}
上記のように、
createNativeQuery()
メソッドを使用しており、
String
配列を含む結果セットが常に必要です。
その後、例で使用する簡単なエンティティを作成しましょう。
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
//getters and setters
}
最後に、テストを実行する前に
Message
を挿入するテストクラスを作成しましょう。
public class SpringCastUnitTest {
private static EntityManager em;
private static EntityManagerFactory emFactory;
@BeforeClass
public static void setup() {
emFactory = Persistence.createEntityManagerFactory("jpa-h2");
em = emFactory.createEntityManager();
//insert an object into the db
Message message = new Message();
message.setText("text");
EntityTransaction tr = em.getTransaction();
tr.begin();
em.persist(message);
tr.commit();
}
}
これで、私たちの
QueryExecutor
を使用して、エンティティの
text
フィールドを取得するクエリを実行できます。
@Test(expected = ClassCastException.class)
public void givenExecutorNoCastCheck__whenQueryReturnsOneColumn__thenClassCastThrown() {
List<String[]> results = QueryExecutor.executeNativeQueryNoCastCheck("select text from message", em);
//fails
for (String[]row : results) {
//do nothing
}
}
ご覧のとおり、
クエリには列が1つしかないため、JPAは実際には文字列配列のリストではなく、文字列のリストを返します。
クエリが単一の列を返し、配列が必要なため、
ClassCastException
が発生します。
3.手動キャスティング修正
-
このエラーを修正する最も簡単な方法は、
ClassCastExceptionを回避するために、結果セットオブジェクトの型をチェックすることです。
そうするためのメソッドを
QueryExecutor
に実装しましょう。
public static List<String[]> executeNativeQueryWithCastCheck(String statement, EntityManager em) {
Query query = em.createNativeQuery(statement);
List results = query.getResultList();
if (results.isEmpty()) {
return new ArrayList<>();
}
if (results.get(0) instanceof String) {
return ((List<String>) results)
.stream()
.map(s -> new String[]{ s })
.collect(Collectors.toList());
} else {
return (List<String[]>) results;
}
}
それから、このメソッドを使用して、例外を受け取ることなくクエリを実行できます。
@Test
public void givenExecutorWithCastCheck__whenQueryReturnsOneColumn__thenNoClassCastThrown() {
List<String[]> results = QueryExecutor.executeNativeQueryWithCastCheck("select text from message", em);
assertEquals("text", results.get(0)[0]);
}
クエリが1列しか返さない場合は、結果を配列に変換する必要があるため、これは理想的な解決策ではありません。
4. JPAエンティティマッピングの修正
このエラーを修正するもう1つの方法は、結果セットをエンティティにマッピングすることです。
カスタムエンティティマッピングの使用をサポートするために別のメソッドをexecutorに追加しましょう。
public static <T> List<T> executeNativeQueryGeneric(String statement, String mapping, EntityManager em) {
Query query = em.createNativeQuery(statement, mapping);
return query.getResultList();
}
その後、カスタムの
__SqlResultSetMappingを作成して、前のクエリの結果セットを
Message__エンティティにマッピングします。
@SqlResultSetMapping(
name="textQueryMapping",
classes={
@ConstructorResult(
targetClass=Message.class,
columns={
@ColumnResult(name="text")
}
)
}
)
@Entity
public class Message {
//...
}
この場合、新しく作成した
SqlResultSetMapping
と一致するコンストラクターも追加する必要があります。
public class Message {
//... fields and default constructor
public Message(String text) {
this.text = text;
}
//... getters and setters
}
最後に、新しいexecutorメソッドを使ってテストクエリを実行し、
Message
のリストを取得します。
@Test
public void givenExecutorGeneric__whenQueryReturnsOneColumn__thenNoClassCastThrown() {
List<Message> results = QueryExecutor.executeNativeQueryGeneric(
"select text from message", "textQueryMapping", em);
assertEquals("text", results.get(0).getText());
}
このソリューションは、結果セットマッピングをJPAに委任しているため、はるかにクリーンです。
5結論
この記事では、ネイティブクエリがこの
ClassCastException
を取得する一般的な場所であることを示しました。また、クエリ結果をトランスポートオブジェクトにマッピングすることで解決するだけでなく、自分で型チェックを行うことも検討しました。
いつものように、例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa[GitHubで利用可能]です。