JPA-Fehler behoben "java.lang.String kann nicht in[Ljava.lang.String;"

Behebung des JPA-Fehlers "java.lang.String kann nicht in Ljava.lang.String umgewandelt werden."

1. Einführung

Natürlich würden wir niemals annehmen, dass wir in Java einString in einString array umwandeln können:

java.lang.String cannot be cast to [Ljava.lang.String;

Dies stellt sich jedoch als häufiger JPA-Fehler heraus.

In diesem kurzen Tutorial zeigen wir, wie dies geschieht und wie es gelöst werden kann.

2. Häufiger Fehlerfall in JPA

In JPA ist es nicht ungewöhnlich, dass dieser Fehlerwhen we work with native queries and we use the createNativeQuery method of the EntityManager. angezeigt wird

SeineJavadoc warnen uns tatsächlich, dassthis method will return a list of Object[], or just an Object if only one column is returned by the query.

Sehen wir uns ein Beispiel an. Erstellen wir zunächst einen Abfrage-Executor, den wir wiederverwenden möchten, um alle unsere Abfragen auszuführen:

public class QueryExecutor {
    public static List executeNativeQueryNoCastCheck(String statement, EntityManager em) {
        Query query = em.createNativeQuery(statement);
        return query.getResultList();
    }
}

Wie oben gezeigt, verwenden wir diecreateNativeQuery()-Methode und erwarten immer eine Ergebnismenge, die einString-Array enthält.

Danach erstellen wir eine einfache Entität, die in unseren Beispielen verwendet werden kann:

@Entity
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String text;

    // getters and setters

}

Zuletzt erstellen wir eine Testklasse, die einMessage einfügt, bevor die Tests ausgeführt werden:

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();
    }
}

Jetzt können wir unsereQueryExecutor verwenden, um eine Abfrage auszuführen, die dastext-Feld unserer Entität abruft:

@Test(expected = ClassCastException.class)
public void givenExecutorNoCastCheck_whenQueryReturnsOneColumn_thenClassCastThrown() {
    List results = QueryExecutor.executeNativeQueryNoCastCheck("select text from message", em);

    // fails
    for (String[] row : results) {
        // do nothing
    }
}

Wie wir sehen können, erhaltenbecause there is only one column in the query, JPA will actually return a list of strings, not a list of string arrays. Wir erhalten einClassCastException, da die Abfrage eine einzelne Spalte zurückgibt und wir ein Array erwartet haben.

3. Manueller Casting Fix

The simplest way to fix this error is to check the type of the result set objects, umClassCastException. zu vermeiden Lassen Sie uns eine Methode implementieren, um dies in unserenQueryExecutor zu tun:

public static List 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) results)
          .stream()
          .map(s -> new String[] { s })
          .collect(Collectors.toList());
    } else {
        return (List) results;
    }
}

Dann können wir diese Methode verwenden, um unsere Abfrage auszuführen, ohne eine Ausnahme zu erhalten:

@Test
public void givenExecutorWithCastCheck_whenQueryReturnsOneColumn_thenNoClassCastThrown() {
    List results = QueryExecutor.executeNativeQueryWithCastCheck("select text from message", em);
    assertEquals("text", results.get(0)[0]);
}

Dies ist keine ideale Lösung, da wir das Ergebnis in ein Array konvertieren müssen, falls die Abfrage nur eine Spalte zurückgibt.

4. JPA Entity Mapping Fix

Another way to fix this error is by mapping the result set to an entity. Auf diese Weise können Siewe can decide how to map the results of our queries in advance und unnötige Gussteile vermeiden.

Fügen wir unserem Executor eine weitere Methode hinzu, um die Verwendung benutzerdefinierter Entitätszuordnungen zu unterstützen:

public static  List executeNativeQueryGeneric(String statement, String mapping, EntityManager em) {
    Query query = em.createNativeQuery(statement, mapping);
    return query.getResultList();
}

Anschließend erstellen wir ein benutzerdefiniertesSqlResultSetMapping , um die Ergebnismenge unserer vorherigen Abfrage einerMessage-Entität zuzuordnen:

@SqlResultSetMapping(
  name="textQueryMapping",
  classes={
    @ConstructorResult(
      targetClass=Message.class,
      columns={
        @ColumnResult(name="text")
      }
    )
  }
)
@Entity
public class Message {
    // ...
}

In diesem Fall müssen wir auch einen Konstruktor hinzufügen, der unseren neu erstelltenSqlResultSetMapping entspricht:

public class Message {

    // ... fields and default constructor

    public Message(String text) {
        this.text = text;
    }

    // ... getters and setters

}

Schließlich können wir unsere neue Executor-Methode verwenden, um unsere Testabfrage auszuführen und eine Liste vonMessage abzurufen:

@Test
public void givenExecutorGeneric_whenQueryReturnsOneColumn_thenNoClassCastThrown() {
    List results = QueryExecutor.executeNativeQueryGeneric(
      "select text from message", "textQueryMapping", em);
    assertEquals("text", results.get(0).getText());
}

Diese Lösung ist wesentlich übersichtlicher, da wir die Zuordnung der Ergebnismenge an JPA delegieren.

5. Fazit

In diesem Artikel haben wir gezeigt, dass native Abfragen ein häufiger Ort sind, um dieseClassCastException zu erhalten. Wir haben uns auch vorgenommen, die Typprüfung selbst durchzuführen und durch Zuordnen der Abfrageergebnisse zu einem Transportobjekt zu lösen.

Wie immer ist der vollständige Quellcode der Beispieleover on GitHub verfügbar.