1. 引言

在 Java 中,我们当然知道不能将一个 String 类型强转为 String[] 数组:

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

但这个错误在 JPA 中却很常见。

本篇教程会带你了解这个错误是怎么发生的,以及如何解决它。

2. JPA 中的常见错误场景

在使用 JPA 原生查询时,这个错误非常容易出现,特别是当我们使用 EntityManagercreateNativeQuery 方法时。

⚠️ 官方 Javadoc 明确指出:
该方法返回的是 Object[] 的列表,或者如果查询只返回一列,那结果就是 Object 本身。

来看个例子。

2.1 查询执行器

我们先创建一个通用的查询执行器,用于复用:

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

如上所示,我们使用了 createNativeQuery(),并默认返回的是 String[] 列表。

2.2 实体类

接着创建一个简单的实体类:

@Entity
public class Message {

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

    private String text;

    // getters and setters

}

2.3 初始化测试数据

public class SpringCastUnitTest {

    private static EntityManager em;
    private static EntityManagerFactory emFactory;

    @BeforeClass
    public static void setup() {
        emFactory = Persistence.createEntityManagerFactory("jpa-h2");
        em = emFactory.createEntityManager();

        // 插入测试数据
        Message message = new Message();
        message.setText("text");

        EntityTransaction tr = em.getTransaction();
        tr.begin();
        em.persist(message);
        tr.commit();
    }
}

2.4 执行查询并触发异常

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

    // 报错:java.lang.String cannot be cast to [Ljava.lang.String;
    for (String[] row : results) {
        // do nothing
    }
}

❌ **因为查询只返回一列,JPA 实际返回的是 List<String>,而不是 List<String[]>**,所以强转失败。

3. 手动类型检查修复

最简单的修复方式是:在处理结果前先判断类型。

我们在 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]);
}

⚠️ 这个方案虽然能解决问题,但每次都要手动转换,不够优雅

4. 使用 JPA 实体映射修复

更推荐的方式是:将查询结果映射到实体类中。

我们可以定义一个 SqlResultSetMapping,将结果映射到 Message 实体。

4.1 添加映射配置

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

4.2 添加构造函数

public class Message {

    // ... fields and default constructor

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

    // ... getters and setters

}

4.3 使用映射查询

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

4.4 测试映射结果

@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. 总结

在这篇文章中,我们展示了在使用 JPA 原生查询时常见的 ClassCastException 问题,并提供了两种解决方案:

  • 手动类型判断 + 转换
  • 使用 @SqlResultSetMapping 映射到实体类

虽然第一种方法简单粗暴,但第二种更符合 JPA 的设计思想,也更推荐使用。

📚 完整代码示例可在 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa


原始标题:Fixing the JPA error "java.lang.String cannot be cast to [Ljava.lang.String;" | Baeldung