我在我的项目中使用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类?


当前回答

在这种情况下,使用“数据库视图”这样的实体也就是不可变实体是非常容易的。

正常的实体

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

其他回答

使用DTO设计模式。它在EJB 2.0中使用。实体由容器管理。采用DTO设计模式来解决这一问题。 但是,当应用程序分别开发服务器端和客户端时,现在可能会使用它。DTO用于服务器端不想将带有注释的实体传递/返回给客户端。

DTO示例:

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

java <——可能需要

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}

我已经找到了一些解决方案。

使用映射实体(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();

但在这种情况下,绝地,必须是一个映射的实体职业。

避免此处未检查警告的另一种方法是使用命名本机查询。如果我们在实体中声明本机查询

@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);

JPA 2.1与@SqlResultSetMapping

随着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();

当然,在这种情况下,绝地不需要是一个映射实体。它可以是一个普通的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,最后两个是理想的方法。

首先声明以下注释:

@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);

如果您使用Spring-jpa,这是对答案和这个问题的补充。如有任何瑕疵,请改正。根据我的实际需求,我主要使用了三种方法来实现“map result Object[] to a pojo”:

JPA内置方法就足够了。 JPA内置的方法是不够的,但一个自定义的sql与它的实体是足够的。 前2失败,我必须使用一个nativeQuery。 下面是一些例子。 pojo期待着: 公共类 private String; private Integer successRate; // getter和setter public Antistealingdto(String secretKey, Integer successRate) { 这一点。secretKey = secretKey; 这一点。successRate =成功率; } }

方法1:将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: 在Edwin Dalorzo的回答中,以实体中的@SqlResultSetMapping和@NamedNativeQuery为例。

前两个方法将调用许多中间处理程序,如自定义转换器。例如,AntiStealing定义了一个secretKey,在它被持久化之前,会插入一个转换器来加密它。这将导致前2个方法返回一个转换后的返回secretKey,这不是我想要的。而方法3将克服转换器,返回的secretKey将与存储时相同(加密的)。

如果查询不是太复杂,您可以这样做。在我的情况下,我需要使用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());
  }

也许有更干净的方法。