Spring Data JPA 将本机查询结果映射到非实体 POJO

Spring Data JPA map the native query result to Non-Entity POJO

我有一个 Spring 带有本机查询的数据存储库方法

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

我想将结果映射到非实体 POJO GroupDetails

是否可能,如果可以,能否请您举个例子?

假设 GroupDetails 与 orid 的回答相同,您是否尝试过 JPA 2.1 @ConstructorResult

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID"),
                @ColumnResult(name="USER_ID")
            }
        )
    }
)

@NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

并在存储库界面中使用以下内容:

GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

根据 Spring Data JPA documentation,spring 将首先尝试查找与您的方法名称匹配的命名查询 - 因此通过使用 @NamedNativeQuery@SqlResultSetMapping@ConstructorResult 你应该能够实现这种行为

你可以这样做

@NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,

    query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName, 
               cat.issueCategory, idc.issueDescriptor, idc.description) 
            from Department dep 
            inner join dep.issues iss 
            inner join iss.category cat 
            inner join cat.issueDescriptor idc 
            where idc.id in(?1)")

并且必须有像

这样的构造函数
public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
            String description) {
        super();
        this.id = id;
        this.department = department;
        this.issueName = issueName;
        this.issueCategory = issueCategory;
        this.issueDescriptor = issueDescriptor;
        this.description = description;
    }

我认为最简单的方法是使用所谓的投影。它可以将查询结果映射到接口。使用 SqlResultSetMapping 很不方便,而且会让你的代码变得丑陋:).

来自 spring 数据 JPA 源代码的示例:

public interface UserRepository extends JpaRepository<User, Integer> {

   @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
   NameOnly findByNativeQuery(Integer id);

   public static interface NameOnly {

     String getFirstname();

     String getLastname();

  }
}

您也可以使用此方法获取投影列表。

Check out this spring data JPA docs entry for more info about projections.

注1:

请记住将您的 User 实体定义为正常 - 投影接口中的字段必须与该实体中的字段相匹配。否则字段映射可能会被破坏(getFirstname() 可能 return 姓氏等值)。

注二:

如果您使用 SELECT table.column ... 表示法,请始终定义与实体名称匹配的别名。例如,此代码将无法正常工作(每个 getter 的投影都会 return 为空):

@Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

但这很好用:

@Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

对于更复杂的查询,我宁愿使用 JdbcTemplate 和自定义存储库。

我认为 Michal 的方法更好。但是,还有另一种方法可以从本机查询中获取结果。

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

现在,您可以将这个二维字符串数组转换成您想要的实体。

您可以按照自己的方式编写本机或非本机查询,并且可以使用自定义结果实例包装 JPQL 查询结果 类。 创建一个与查询中返回的列具有相同名称的 DTO,并创建一个具有与查询返回的相同序列和名称的全参数构造函数。 然后使用下面的方式查询数据库。

@Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")

创建 DTO:

package example;

public class CountryAndCapital {
    public String countryName;
    public String capitalName;

    public CountryAndCapital(String countryName, String capitalName) {
        this.countryName = countryName;
        this.capitalName = capitalName;
    }
}

使用接口中的默认方法,获取EntityManager获得设置ResultTransformer的机会,然后就可以return纯POJO了,像这样:

final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
    return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}

而 BaseRepository.java 是这样的:

@PersistenceContext
public EntityManager em;

public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
    Session session = em.unwrap(Session.class);
    NativeQuery q = session.createSQLQuery(sql);
    if(params!=null){
        for(int i=0,len=params.length;i<len;i++){
            Object param=params[i];
            q.setParameter(i+1, param);
        }
    }
    q.setResultTransformer(Transformers.aliasToBean(dto));
    return (T) q.uniqueResult();
}

此解决方案不会影响存储库接口文件中的任何其他方法。

使用 JPA 投影 在您的情况下,可能需要将数据检索为自定义类型的对象。这些类型反映了根 class 的部分视图,仅包含我们关心的属性。这就是预测派上用场的地方。 首先将实体声明为@immutable

@Entity
@Immutable

public class Address {

@Id
private Long id;

设置您的存储库

public interface AddressView {
    String getZipCode();
}

然后在存储库界面中使用它:

public interface AddressRepository extends Repository<Address, Long> {
      @Query("EXEC SP_GETCODE ?1")
    List<AddressView> getAddressByState(String state);
}

这是我转换为地图然后再转换为自定义对象的解决方案

private ObjectMapper objectMapper;

public static List<Map<String, Object>> convertTuplesToMap(List<?> tuples) {
    List<Map<String, Object>> result = new ArrayList<>();

    tuples.forEach(object->{
        if(object instanceof Tuple single) {
            Map<String, Object> tempMap = new HashMap<>();
            for (TupleElement<?> key : single.getElements()) {
                tempMap.put(key.getAlias(), single.get(key));
            }
            result.add(tempMap);
        }else{
            throw new RuntimeException("Query should return instance of Tuple");
        }
    });

    return result;
}

public <T> List<T> parseResult(List<?> list, Class<T> clz){
    List<T> result = new ArrayList<>();
    convertTuplesToMap(list).forEach(map->{
        result.add(objectMapper.convertValue(map, clz));
    });
    return result;
}

public static class CustomDTO{
    private String param1;
    private Integer param2;
    private OffsetDateTime param3;
}

public List<CustomDTO> doSomeQuery(){
    Query query = entityManager.createNativeQuery("SELECT param1, param2 param3 ... ", Tuple.class);
    return parseResult(query.getResultList(), CustomDTO.class);
}

如果您正在寻找 运行 在 spring 中使用 @repository 和 @service 结构启动的自定义 SQL 查询。请看一下