Correction de l’erreur JPA "java.lang.String ne peut pas être converti en[Ljava.lang.String;"

Correction de l'erreur JPA "java.lang.String ne peut pas être converti en Ljava.lang.String;"

1. introduction

Bien sûr, nous ne supposerons jamais que nous pouvons convertir unString en un tableauString en Java:

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

Mais cela s'avère être une erreur JPA courante.

Dans ce rapide didacticiel, nous allons montrer comment cela se produit et comment le résoudre.

2. Cas d'erreur courant dans JPA

Dans JPA, il n’est pas rare d’obtenir cette erreurwhen we work with native queries and we use the createNativeQuery method of the EntityManager.

SonJavadoc nous avertit en fait quethis method will return a list of Object[], or just an Object if only one column is returned by the query.

Voyons un exemple. Commençons par créer un exécuteur de requêtes que nous souhaitons réutiliser pour exécuter toutes nos requêtes:

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

Comme vu ci-dessus, nous utilisons la méthodecreateNativeQuery() et nous attendons toujours un jeu de résultats contenant un tableauString.

Ensuite, créons une entité simple à utiliser dans nos exemples:

@Entity
public class Message {

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

    private String text;

    // getters and setters

}

Et enfin, créons une classe de test qui insère unMessage avant d'exécuter les tests:

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

Maintenant, nous pouvons utiliser nosQueryExecutor pour exécuter une requête qui récupère le champtext de notre entité:

@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
    }
}

Comme nous pouvons le voir,because there is only one column in the query, JPA will actually return a list of strings, not a list of string arrays. Nous obtenons unClassCastException car la requête renvoie une seule colonne et nous attendions un tableau.

3. Correction de coulée manuelle

The simplest way to fix this error is to check the type of the result set objects pour éviter lesClassCastException. Implémentons une méthode pour le faire dans nosQueryExecutor:

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

Ensuite, nous pouvons utiliser cette méthode pour exécuter notre requête sans obtenir d’exception:

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

Ce n'est pas une solution idéale car nous devons convertir le résultat en un tableau au cas où la requête ne renvoie qu'une colonne.

4. Correctif de mappage d'entité JPA

Another way to fix this error is by mapping the result set to an entity. De cette façon,we can decide how to map the results of our queries in advance et évite les castings inutiles.

Ajoutons une autre méthode à notre exécuteur pour prendre en charge l'utilisation des mappages d'entités personnalisés:

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

Après cela, créons unSqlResultSetMapping personnalisé pour mapper l'ensemble de résultats de notre requête précédente à une entitéMessage:

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

Dans ce cas, nous devons également ajouter un constructeur qui correspond à nosSqlResultSetMapping nouvellement créés:

public class Message {

    // ... fields and default constructor

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

    // ... getters and setters

}

Enfin, nous pouvons utiliser notre nouvelle méthode d'exécuteur pour exécuter notre requête de test et obtenir une liste deMessage:

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

Cette solution est beaucoup plus propre puisque nous déléguons le mappage du jeu de résultats à JPA.

5. Conclusion

Dans cet article, nous avons montré que les requêtes natives sont un endroit courant pour obtenir cesClassCastException. Nous avons également envisagé de vérifier nous-mêmes le type et de le résoudre en mappant les résultats de la requête sur un objet de transport.

Comme toujours, le code source complet des exemples est disponibleover on GitHub.