SpringDataJPA 将本机查询结果映射到非实体 POJO

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

@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

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

210627 次浏览

假设 GroupDetails 和 orid 的答案一样,你试过 JPA 2.1 @ ArchittorResult吗?

@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 文件,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();


}
}

您还可以使用此方法获取一个投影列表。

查看这个春季数据 JPA 文档条目以获得更多关于预测的信息。

注一:

请记住将 User实体定义为正常-来自投影接口的字段必须与该实体中的字段匹配。否则字段映射可能中断(getFirstname()可能返回姓氏等值)。

注二:

如果使用 SELECT table.column ...表示法,则始终定义与实体名称匹配的别名。例如,这段代码不能正常工作(投影将为每个 getter 返回空值) :

@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

我觉得米哈尔的方法更好。但是,还有一种从本机查询中获取结果的方法。

@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 的机会,然后您可以返回纯 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 投影 在您的情况下,最好将数据作为自定义类型的对象检索。这些类型反映根类的部分视图,只包含我们关心的属性。这就是投影派上用场的地方。 首先将 Entity 声明为@immutable

@Entity
@Immutable

公共班级地址{

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

这是我转换为 Map 然后转换为自定义 Object 的解决方案

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

如果您正在寻找在春季启动中运行一个自定义 SQL 查询,其中包含@Repository 和@service 结构。请看。

Https://stackoverflow.com/a/71501509/4735043