我在我的项目中使用 JPA。
我遇到了一个查询,其中我需要对五个表进行连接操作。所以我创建了一个返回五个字段的本机查询。
现在我想将结果对象转换为包含相同五个字符串的 java POJO 类。
JPA 中有没有办法直接将该结果转换为 POJO 对象列表?
我来到以下解决方案..
@NamedNativeQueries({
@NamedNativeQuery(
name = "nativeSQL",
query = "SELECT * FROM Actors",
resultClass = db.Actor.class),
@NamedNativeQuery(
name = "nativeSQL2",
query = "SELECT COUNT(*) FROM Actors",
resultClass = XXXXX) // <--------------- problem
})
现在在 resultClass 中,我们是否需要提供一个实际 JPA 实体的类?或者我们可以将它转换为任何包含相同列名的 JAVA POJO 类?
我找到了几个解决方案。
使用映射实体 (JPA 2.0)
使用 JPA 2.0 无法将本机查询映射到 POJO,只能使用实体来完成。
例如:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
但在这种情况下,Jedi
必须是映射的实体类。
在这里避免未经检查的警告的另一种方法是使用命名的本机查询。因此,如果我们在实体中声明本机查询
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
然后,我们可以简单地做:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
这更安全,但我们仍然被限制使用映射实体。
手动映射
我尝试了一下(在 JPA 2.1 到来之前)的一个解决方案是使用一点反射对 POJO 构造函数进行映射。
public static <T> T map(Class<T> type, Object[] tuple){
List<Class<?>> tupleTypes = new ArrayList<>();
for(Object field : tuple){
tupleTypes.add(field.getClass());
}
try {
Constructor<T> ctor = type.getConstructor(tupleTypes.toArray(new Class<?>[tuple.length]));
return ctor.newInstance(tuple);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
此方法基本上采用元组数组(由本机查询返回)并通过查找具有相同数量的字段和相同类型的构造函数将其映射到提供的 POJO 类。
然后我们可以使用方便的方法,例如:
public static <T> List<T> map(Class<T> type, List<Object[]> records){
List<T> result = new LinkedList<>();
for(Object[] record : records){
result.add(map(type, record));
}
return result;
}
public static <T> List<T> getResultList(Query query, Class<T> type){
@SuppressWarnings("unchecked")
List<Object[]> records = query.getResultList();
return map(type, records);
}
我们可以简单地使用这种技术,如下所示:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
带有@SqlResultSetMapping 的 JPA 2.1
随着 JPA 2.1 的到来,我们可以使用 @SqlResultSetMapping 注解来解决这个问题。
我们需要在实体的某处声明一个结果集映射:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
然后我们简单地做:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
当然,在这种情况下,Jedi
不必是映射实体。它可以是一个普通的 POJO。
使用 XML 映射
我是发现在我的实体中添加所有这些 @SqlResultSetMapping
的人之一,我特别不喜欢实体中命名查询的定义,因此我可以在 META-INF/orm.xml
文件中执行所有这些操作:
<named-native-query name="GetAllJedi" result-set-mapping="JediMapping">
<query>SELECT name,age FROM jedi_table</query>
</named-native-query>
<sql-result-set-mapping name="JediMapping">
<constructor-result target-class="org.answer.model.Jedi">
<column name="name" class="java.lang.String"/>
<column name="age" class="java.lang.Integer"/>
</constructor-result>
</sql-result-set-mapping>
这些就是我所知道的所有解决方案。如果我们可以使用 JPA 2.1,最后两个是理想的方式。
JPA 提供了一个 SqlResultSetMapping
,允许您将本地查询的任何返回映射到实体 或自定义类。
EDIT JPA 1.0 不允许映射到非实体类。仅在 JPA 2.1 中添加了 ConstructorResult 以将返回值映射到 java 类。
此外,对于 OP 的计数问题,使用单个 ColumnResult
定义结果集映射就足够了
ConstructorResult
添加为 SqlResultSetMapping
的参数之一,允许使用在构造函数中设置的所有字段的 pojo。我会更新答案。
是的,使用 JPA 2.1 很容易。你有非常有用的注释。他们简化了你的生活。
首先声明您的本机查询,然后声明您的结果集映射(它定义了数据库返回的数据到您的 POJO 的映射)。编写您的 POJO 类以供参考(为简洁起见,此处不包括在内)。最后但同样重要的是:在 DAO(例如)中创建一个方法来调用查询。这在 dropwizard (1.0.0) 应用程序中对我有用。
首先在实体类中声明一个原生查询:
@NamedNativeQuery (
name = "domain.io.MyClass.myQuery",
query = "Select a.colA, a.colB from Table a",
resultSetMapping = "mappinMyNativeQuery") // must be the same name as in the SqlResultSetMapping declaration
您可以在下面添加结果集映射声明:
@SqlResultSetMapping(
name = "mapppinNativeQuery", // same as resultSetMapping above in NativeQuery
classes = {
@ConstructorResult(
targetClass = domain.io.MyMapping.class,
columns = {
@ColumnResult( name = "colA", type = Long.class),
@ColumnResult( name = "colB", type = String.class)
}
)
}
)
稍后在 DAO 中,您可以将查询称为
public List<domain.io.MyMapping> findAll() {
return (namedQuery("domain.io.MyClass.myQuery").list());
}
而已。
如果您使用 Spring-jpa
,这是对答案和此问题的补充。如有不足请指正。根据我实际满足的需求,我主要使用了三种方法来实现“将结果Object[]
映射到pojo”:
JPA 内置方法就足够了。 JPA 内置方法是不够的,但是一个带有实体的自定义 sql 就足够了。前2个失败了,我不得不使用nativeQuery。以下是示例。预期的pojo: public class Antistealingdto { private String secretKey;私人整数成功率; // GETTER 和 SETTER public Antistealingdto(String secretKey, Integer successRate) { this.secretKey = secretKey; this.successRate = 成功率; } }
方法一:将pojo改成接口:
public interface Antistealingdto {
String getSecretKey();
Integer getSuccessRate();
}
和存储库:
interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
Antistealingdto findById(Long id);
}
方法2:存储库:
@Query("select new AntistealingDTO(secretKey, successRate) from Antistealing where ....")
Antistealing whatevernamehere(conditions);
注意:POJO 构造函数的参数顺序在 POJO 定义和 sql 中必须一致。
方法 3:使用 Entity
中的 @SqlResultSetMapping
和 @NamedNativeQuery
作为 Edwin Dalorzo 回答中的示例。
前两种方法将调用许多中间处理程序,例如自定义转换器。例如,AntiStealing
定义了一个 secretKey
,在它被持久化之前,会插入一个转换器来对其进行加密。这将导致前 2 个方法返回转换后的 secretKey
,这不是我想要的。虽然方法 3 会克服转换器,但返回的 secretKey
将与存储的相同(加密的)。
可以执行展开过程以将结果分配给非实体(即 Beans/POJO)。程序如下。
List<JobDTO> dtoList = entityManager.createNativeQuery(sql)
.setParameter("userId", userId)
.unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(JobDTO.class)).list();
该用法用于 JPA-Hibernate 实现。
JobDTO
应该具有默认构造函数。或者您可以基于 AliasToBeanResultTransformer
实现来实现自己的转换器。
最简单的方法是使用 so 投影。它可以将查询结果直接映射到接口,比使用SqlResultSetMapping更容易实现。
一个例子如下所示:
@Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
@Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
List<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId);
@Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
Page<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId, Pageable pageable);
}
// Interface to which result is projected
public interface PeopleDTO {
String getName();
Long getCount();
}
投影接口中的字段必须与该实体中的字段匹配。否则字段映射可能会中断。
此外,如果您使用 SELECT table.column
表示法,请始终定义与实体名称匹配的别名,如示例所示。
在休眠中,您可以使用此代码轻松映射您的本机查询。
private List < Map < String, Object >> getNativeQueryResultInMap() {
String mapQueryStr = "SELECT * FROM AB_SERVICE three ";
Query query = em.createNativeQuery(mapQueryStr);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List < Map < String, Object >> result = query.getResultList();
for (Map map: result) {
System.out.println("after request ::: " + map);
}
return result;}
首先声明以下注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultEntity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultColumn {
int index();
}
然后注释您的 POJO,如下所示:
@NativeQueryResultEntity
public class ClassX {
@NativeQueryResultColumn(index=0)
private String a;
@NativeQueryResultColumn(index=1)
private String b;
}
然后编写注解处理器:
public class NativeQueryResultsMapper {
private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class);
public static <T> List<T> map(List<Object[]> objectArrayList, Class<T> genericType) {
List<T> ret = new ArrayList<T>();
List<Field> mappingFields = getNativeQueryResultColumnAnnotatedFields(genericType);
try {
for (Object[] objectArr : objectArrayList) {
T t = genericType.newInstance();
for (int i = 0; i < objectArr.length; i++) {
BeanUtils.setProperty(t, mappingFields.get(i).getName(), objectArr[i]);
}
ret.add(t);
}
} catch (InstantiationException ie) {
log.debug("Cannot instantiate: ", ie);
ret.clear();
} catch (IllegalAccessException iae) {
log.debug("Illegal access: ", iae);
ret.clear();
} catch (InvocationTargetException ite) {
log.debug("Cannot invoke method: ", ite);
ret.clear();
}
return ret;
}
// Get ordered list of fields
private static <T> List<Field> getNativeQueryResultColumnAnnotatedFields(Class<T> genericType) {
Field[] fields = genericType.getDeclaredFields();
List<Field> orderedFields = Arrays.asList(new Field[fields.length]);
for (int i = 0; i < fields.length; i++) {
if (fields[i].isAnnotationPresent(NativeQueryResultColumn.class)) {
NativeQueryResultColumn nqrc = fields[i].getAnnotation(NativeQueryResultColumn.class);
orderedFields.set(nqrc.index(), fields[i]);
}
}
return orderedFields;
}
}
使用上述框架如下:
String sql = "select a,b from x order by a";
Query q = entityManager.createNativeQuery(sql);
List<ClassX> results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class);
BeanUtils
在哪个包中?
使用休眠:
@Transactional(readOnly=true)
public void accessUser() {
EntityManager em = repo.getEntityManager();
org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u").addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE).addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE).addScalar("loginType", IntegerType.INSTANCE)
.setResultTransformer(Transformers.aliasToBean(User2DTO.class));
List<User2DTO> userList = q.list();
}
我们已使用以下方式解决了该问题:
//Add actual table name here in Query
final String sqlQuery = "Select a.* from ACTORS a"
// add your entity manager here
Query query = entityManager.createNativeQuery(sqlQuery,Actors.class);
//List contains the mapped entity data.
List<Actors> list = (List<Actors>) query.getResultList();
由于其他人已经提到了所有可能的解决方案,我正在分享我的解决方法。
在我使用 Postgres 9.4
的情况下,在使用 Jackson
时,
//Convert it to named native query.
List<String> list = em.createNativeQuery("select cast(array_to_json(array_agg(row_to_json(a))) as text) from myschema.actors a")
.getResultList();
List<ActorProxy> map = new ObjectMapper().readValue(list.get(0), new TypeReference<List<ActorProxy>>() {});
我相信你可以为其他数据库找到相同的。
仅供参考,JPA 2.0 native query results as map
使用 ResultSet 的旧样式
@Transactional(readOnly=true)
public void accessUser() {
EntityManager em = this.getEntityManager();
org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
session.doWork(new Work() {
@Override
public void execute(Connection con) throws SQLException {
try (PreparedStatement stmt = con.prepareStatement(
"SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
ResultSet rs = stmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
}
System.out.println("");
while (rs.next()) {
System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
}
}
}
});
}
不确定这是否适合这里,但我有类似的问题,并为我找到了以下简单的解决方案/示例:
private EntityManager entityManager;
...
final String sql = " SELECT * FROM STORE "; // select from the table STORE
final Query sqlQuery = entityManager.createNativeQuery(sql, Store.class);
@SuppressWarnings("unchecked")
List<Store> results = (List<Store>) sqlQuery.getResultList();
在我的情况下,我不得不在其他地方使用 Strings 中定义的 SQL 部分,所以我不能只使用 NamedNativeQuery。
请参阅下面的示例,了解使用 POJO 作为伪实体从本机查询中检索结果,而不使用复杂的 SqlResultSetMapping。 POJO 中只需要两个注释,一个裸露的@Enity 和一个虚拟的@Id。 @Id 可以在您选择的任何字段上使用,@Id 字段可以有重复的键但不能有空值。
由于@Enity 不映射到任何物理表,所以这个POJO 被称为伪实体。
环境:eclipselink 2.5.0-RC1、jpa-2.1.0、mysql-connector-java-5.1.14
您可以下载完整的 Maven 项目 here
本机查询基于 mysql 示例employees db http://dev.mysql.com/doc/employee/en/employees-installation.html
持久性.xml
<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="jpa-mysql" transaction-type="RESOURCE_LOCAL">
<class>org.moonwave.jpa.model.pojo.Employee</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/employees" />
<property name="javax.persistence.jdbc.user" value="user" />
<property name="javax.persistence.jdbc.password" value="***" />
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
</properties>
</persistence-unit>
雇员.java
package org.moonwave.jpa.model.pojo;
@Entity
public class Employee {
@Id
protected Long empNo;
protected String firstName;
protected String lastName;
protected String title;
public Long getEmpNo() {
return empNo;
}
public void setEmpNo(Long empNo) {
this.empNo = empNo;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("empNo: ").append(empNo);
sb.append(", firstName: ").append(firstName);
sb.append(", lastName: ").append(lastName);
sb.append(", title: ").append(title);
return sb.toString();
}
}
EmployeeNativeQuery.java
public class EmployeeNativeQuery {
private EntityManager em;
private EntityManagerFactory emf;
public void setUp() throws Exception {
emf=Persistence.createEntityManagerFactory("jpa-mysql");
em=emf.createEntityManager();
}
public void tearDown()throws Exception {
em.close();
emf.close();
}
@SuppressWarnings("unchecked")
public void query() {
Query query = em.createNativeQuery("select e.emp_no as empNo, e.first_name as firstName, e.last_name as lastName," +
"t.title from employees e join titles t on e.emp_no = t.emp_no", Employee.class);
query.setMaxResults(30);
List<Employee> list = (List<Employee>) query.getResultList();
int i = 0;
for (Object emp : list) {
System.out.println(++i + ": " + emp.toString());
}
}
public static void main( String[] args ) {
EmployeeNativeQuery test = new EmployeeNativeQuery();
try {
test.setUp();
test.query();
test.tearDown();
} catch (Exception e) {
System.out.println(e);
}
}
}
list
据称是 Employee
的列表,为什么您的 for-each 循环会遍历类型 Object
?如果您将 for-each 循环编写为 for(Employee emp : list)
,那么您会发现您的答案是错误的,并且您的列表内容不是员工,并且您取消的警告旨在提醒您注意这个潜在的错误。
List<Employee> list = (List<Employee>) query.getResultList();
的警告将 for (Object emp : list)
更改为 for (Employee emp : list)
更好,但如果保留为 Object emp
则不会出错,因为 list 是 List<Employee>
的实例。我更改了 git 项目中的代码,但不是在这里让您的评论与原始帖子相关
Query query = em.createNativeQuery("select * ...", Employee.class);
和 persistence.xml,本机查询确实返回了 Employee 列表。我刚刚签出并运行没有问题的项目。如果您在本地设置 mysql 示例员工数据库,您应该也可以运行该项目
Employee
,我认为它是一个实体。不是吗?
如果您使用的是 Spring,则可以使用 org.springframework.jdbc.core.RowMapper
这是一个例子:
public List query(String objectType, String namedQuery)
{
String rowMapper = objectType + "RowMapper";
// then by reflection you can instantiate and use. The RowMapper classes need to follow the naming specific convention to follow such implementation.
}
使用休眠:
@Transactional(readOnly=true)
public void accessUser() {
EntityManager em = repo.getEntityManager();
org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")
.addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE)
.addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE)
.addScalar("loginType", IntegerType.INSTANCE)
.setResultTransformer(Transformers.aliasToBean(User2DTO.class));
List<User2DTO> userList = q.list();
}
我尝试了上面答案中提到的很多事情。 SQLmapper 对放置它的位置非常困惑。只有非托管 POJO 是个问题。我尝试了各种方法,其中一种简单的方法就是照常工作。我正在使用hibernate-jpa-2.1。
List<TestInfo> testInfoList = factory.createNativeQuery(QueryConstants.RUNNING_TEST_INFO_QUERY)
.getResultList();
唯一需要注意的是 POJO 具有与查询相同的成员变量名称(全部小写)。显然,我什至不需要像我们在 JPQL 中使用 TypedQueries 那样告诉目标类和查询。
测试信息类
@Setter
@Getter
@NoArgsConstructor
@ToString
public class TestInfo {
private String emailid;
private Long testcount;
public TestInfo(String emailId, Long testCount) {
super();
this.emailid = emailId;
this.testcount = testCount;
}
}
在这种情况下,使用像实体(又名不可变实体)这样的“数据库视图”非常容易。
正常实体
@Entity
@Table(name = "people")
data class Person(
@Id
val id: Long = -1,
val firstName: String = "",
val lastName: String? = null
)
查看类似实体
@Entity
@Immutable
@Subselect("""
select
p.id,
concat(p.first_name, ' ', p.last_name) as full_name
from people p
""")
data class PersonMin(
@Id
val id: Long,
val fullName: String,
)
在任何存储库中,我们都可以创建查询函数/方法,就像:
@Query(value = "select p from PersonMin p")
fun findPeopleMinimum(pageable: Pageable): Page<PersonMin>
如果查询不是太复杂,你可以做这样的事情。在我的情况下,我需要使用 H2 FT_Search 结果查询来进行另一个查询。
var ftSearchQuery = "SELECT * FROM FT_SEARCH(\'something\', 0, 0)";
List<Object[]> results = query.getResultList();
List<Model> models = new ArrayList<>();
for (Object[] result : results) {
var newQuery = "SELECT * FROM " + (String) result[0];
models.addAll(entityManager.createNativeQuery(newQuery, Model.class).getResultList());
}
可能有更清洁的方法可以做到这一点。
将 SQL 查询转换为 POJO 类集合的简单方法,
Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class);
List<Actors> list = (List<Actors>) query.list();
return list;
使用 DTO Design Pattern
。它在 EJB 2.0
中使用。实体是容器管理的。 DTO Design Pattern
用于解决此问题。但是,现在可能会使用它,当应用程序分别开发 Server Side
和 Client Side
时。当 Server side
不想将带有注释的 Entity
传递/返回给 Client Side
时使用DTO
。
DTO 示例:
个人实体.java
@Entity
public class PersonEntity {
@Id
private String id;
private String address;
public PersonEntity(){
}
public PersonEntity(String id, String address) {
this.id = id;
this.address = address;
}
//getter and setter
}
PersonDTO.java
public class PersonDTO {
private String id;
private String address;
public PersonDTO() {
}
public PersonDTO(String id, String address) {
this.id = id;
this.address = address;
}
//getter and setter
}
DTOBuilder.java
public class DTOBuilder() {
public static PersonDTO buildPersonDTO(PersonEntity person) {
return new PersonDTO(person.getId(). person.getAddress());
}
}
EntityBuilder.java <-- 它是需要的
public class EntityBuilder() {
public static PersonEntity buildPersonEntity(PersonDTO person) {
return new PersonEntity(person.getId(). person.getAddress());
}
}
@SqlResultSetMapping
必须放在一个实体中,因为这就是 JPA 将从中读取元数据的地方。您不能指望 JPA 检查您的 POJO。您放置映射的实体是无关紧要的,也许是与您的 POJO 结果更相关的实体。或者,映射可以用 XML 表示,以避免与完全不相关的实体耦合。@SqlResultSetMapping
一起使用,可能值得注意的是Jedi
类将需要一个全参数构造函数,并且@ColumnResult
注释可能需要将type
属性添加到可能不是隐式的转换(我需要为某些列添加type = ZonedDateTime.class
)。