Spring JPA自定义查询结果的接收方式
作者:晓风残月淡
这篇文章主要介绍了Spring JPA自定义查询结果的接收方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
一、标准使用方法
//构建实体类 @Getter @Setter @Entity @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private Integer age; private String address; } //继承JPA public interface UserRepository extends JpaRepository<User,Long> { } //调用测试 @RunWith(SpringRunner.class) @SpringBootTest class UserTest { @Autowired UserRepository userRepository; @Test void testUser() { User newUser = User.builder() .id(1L) .username("zhangsan") .age(18) .address("北京市").build(); User savedUser = userRepository.save(newUser); assertEquals("zhangsan",savedUser.getUsername()); } }
如图所示,插入成功。
二、自定义查询结果的接收
假设我们不想查询user表中的所有字段,而只需要其中的几个字段作为前端输出。
当然,JPA给我们提供了自定义SQL的功能进行个性化的查询。使用@Query就可以自定义SQL语句,编写自定义的查询语句了。
但是,它的使用方式分为两种:
- 一种是特定JPQL语言,这是通过实体对象来查询属性,而不用考虑对应的表名称和字段名称。
- 一种是SQL语言,还是像原来一样操作对应的表和字段。
1.那么在UserRepository 中应该怎么写自定义查询语句呢?
//创建个性化的DTO用于接收 @Value public class UserDTO { Long id; String username; } //写sql语句 public interface UserRepository extends JpaRepository<User,Long> { //@Query("select u from User u where u.id =?1") //是像这样,还是怎么写呢? UserDTO findUserDTOByID(Long id); } //调用 @RunWith(SpringRunner.class) @SpringBootTest class UserTest { @Autowired UserRepository userRepository; @Test void testUserDTO() { UserDTO dto = userRepository.findUserDTOByID(1L); assertEquals("zhangsan",dto.getUsername()); } }
可能的错误的查询方式:
//1.意图User实体自动映射某些属性到UserDTO public interface UserRepository extends JpaRepository<User,Long> { @Query("select u from User u where u.id =?1") UserDTO findUserDTOByID(Long id); //但是会报org.springframework.core.convert.ConverterNotFoundException错误 } //2.意图挑出User的某些字段会自动映射到UserDTO public interface UserRepository extends JpaRepository<User,Long> { @Query("select u.id,u.username from User u where u.id =?1") UserDTO findUserDTOByID(Long id); //但是会报org.springframework.core.convert.ConverterNotFoundException错误 } //3.意图挑出User的某些字段会自动映射到UserDTO public interface UserDTORepository extends JpaRepository<UserDTO,Long> { @Query("select u.id,u.username from User u where u.id =?1") UserDTO findByID(Long id); //但是这会生成一个新表 }
在上面虽然UserDTO只是User中的子集,只有它的两个属性,但是如果直接用UserDTO接收查询结构,就会报这种 类型转换错误。
这是因为UserRepository是实体对象User的仓库,必须用User来接收,不能用别的对象来接收。
那么我们能不能再创建一个UserDTORepository来接收查询结果呢?
这是不行的,因为UserDTORepository必须映射对应的表,才能查询UserDTO对象。而我们当然不希望又创建与User相似的表。
正确的查询方式:
//1.使用Object接收 public interface UserRepository extends JpaRepository<User,Long> { @Query("select u.id,u.username from User u where u.id =?1") Object findUserDTOByID(Long id); //倒是能接收到结果,但是丢失了属性名称,必须数组的索引访问,不方便 } //2.使用全限定名接收 public interface UserRepository extends JpaRepository<User,Long> { @Query("select new com.example.admin.ums.domain.user.UserDTO(id,username) from User u where u.id =?1") UserDTO findUserDTOByID(Long id); //能接收到结果,但是UserDTO必须有构造函数,带上所有参数,也不方便 } //3.定义接口来接收,使用的是projections接口投影机制 public interface IUser { //定义这些getter方法才能接收结果 Long getId(); String getUsername(); } //用接口接收查询结果 public interface UserRepository extends JpaRepository<User,Long> { @Query("select u.id as id ,u.username as username from User u where u.id =?1") IUser findUserDTOByID(Long id); //能查询到结果,但是必须用接口接收,可能不习惯 } //4.泛型动态查询投影 public interface UserRepository extends JpaRepository<User,Long> { <T> T findById(Long id, Class<T> type); //只需要输入ID和类类型就能查到结果,这个利用了方法名的查询生成器机制,不用专门写@Query //同时使用动态查询投影,所以不用输入很多参数,方便了很多,强烈推荐 } //5.用Map接收查询的结果 public interface UserRepository extends JpaRepository<User,Long> { @Query("select u.id as id ,u.username as username, u.address as address from User u where u.id =?1") Map<String,Object> findUserDTO(Long id); //需要写JPQL语句,必须用as取别名,否则就没有key值 //优点是不用构造DTO直接输出给前端 //缺点是查出来的不是对象,不方便再处理业务逻辑,若参数很多,就会很繁琐 }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。