Spring Data JPA 将原生查询结果映射到非实体 POJO
Posted
技术标签:
【中文标题】Spring Data JPA 将原生查询结果映射到非实体 POJO【英文标题】:Spring Data JPA map the native query result to Non-Entity POJO 【发布时间】:2015-05-18 22:00:28 【问题描述】:我有一个带有本机查询的 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
。
有可能吗,如果可以,能否举个例子?
【问题讨论】:
【参考方案1】:我认为最简单的方法是使用所谓的投影。它可以将查询结果映射到接口。使用SqlResultSetMapping
很不方便,而且会让你的代码变得丑陋:)。
来自 Spring Data 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()
可能会返回姓氏等值)。
注2:
如果您使用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
和自定义存储库。
【讨论】:
这是一个更清洁的解决方案。我已经检查过,但性能比使用 SqlResultSetMapping 差得多(它慢了大约 30-40% :( ) 效果很好!如果您想在其他地方使用该界面,请将其公开 如果要提取 XML 类型 (clob) 字段,则不起作用。有什么建议吗? @Ashish 我宁愿使用JdbcTemplate
(docs.spring.io/spring-framework/docs/current/javadoc-api/org/…)。您可以在 resultSet
上使用 getClob
方法来获取 clob InputStream
。例如:rs.getClob("xml_column").getCharacterStream()
.
@SalmanKazmi 我什至不会考虑这样做,因为如果数据库中的表发生更改,您的视图对象也必须更改,因此维护此投影将是一个地狱。但是如果投影中的字段与接口/dto中的字段相同,它应该可以工作。【参考方案2】:
假设 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
,您应该能够实现该行为
【讨论】:
为了让spring数据能够匹配到NamedNativeQuery,域实体的Class名后跟一个点,需要在NamedNativeQuery的名字前面加上前缀。所以名字应该是(假设域实体是Group)'Group.getGroupDetails'。 如何返回此类对象的列表? 要让它工作,GroupDetails
应该用@Entity
标记吗?如果可能的话,你能告诉哪个类注释@NamedNativeQuery
必须应用?
@SqlResultSetMapping
和 @NamedNativeQuery
注释必须存在于 Spring Data 存储库中使用的实体上(例如,对于 public interface CustomRepository extends CrudRepository<CustomEntity, Long>
,它是 CustomEntity
类)
别忘了把@Query(nativeQuery = true) 放在GroupDetails getGroupDetails(@Param("userId")...【参考方案3】:
我认为 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);
现在,您可以将此二维字符串数组转换为您想要的实体。
【讨论】:
简约优雅 如何将二维数组转换为类? 这太冒险了。如果表中的列顺序发生更改(例如 - 添加了新列,或修改了现有列),则结果值将能够混合。例如。如果您的实体有ClientId
字段,但列从 client_id
更改为 user_id
则很难检测到此更改,因为缺少列名验证。【参考方案4】:
您可以按照自己的方式编写本机或非本机查询,并且可以使用自定义结果类的实例来包装 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;
【讨论】:
更正:相同的名称不是强制性的...只是构造函数中的参数序列和返回的结果集相同。 仅当 Country 是您的 java 实体类时才有效。如果 Country 不是您的 java 实体类,则不会。 你说这也适用于原生查询?你能举个例子吗? OP 要求本地查询,但给出的示例是非本地查询【参考方案5】:使用接口中的默认方法,获取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();
此解决方案不会影响存储库接口文件中的任何其他方法。
【讨论】:
【参考方案6】:使用 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);
【讨论】:
【参考方案7】:你可以这样做
@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;
【讨论】:
问题是关于原生查询,而不是用 HQL 编写的查询。以上是关于Spring Data JPA 将原生查询结果映射到非实体 POJO的主要内容,如果未能解决你的问题,请参考以下文章
使用 Spring Data JPA 将 sql 查询的结果映射到 pojo