Spring Data JPA 自定义对象接收查询结果集
Spring Data JPA
简介
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦
自定义对象接收
在实际工作中,我们经常遇到的场景是查询数据表中的某几个字段,但是使用 Spring Data Jpa框架一般查询结果只返回对应的Entity实体。我们是无法直接操作 Spring Data JPA提供的一些方法获得自定义实体类。
例如。我们如果想拿用户表中的姓名和生日字段,我们可能写出如下的操作。
// entity 实体类
public class User {
private Stirng name;
private String birthday;
/**
* 其他字段
*/
}
public class UserDTO {
private Stirng name;
private String birthday;
/**
* 省略get set方法
*/
}
// dao层
public interface UserDao extends JpaSpecificationExecutor<User>,
JpaRepository<User, Integer> {
@Query("select name, birthday from user where name = ?1", nativeQuery=true)
List<UserDTO> findByName(String name);
}
实际测试可以测到返回的是Object[]类型,并非我们想要得到的UserDTO对象。那么我们应该如何操作呢
当然,我们也可以这样操作,用Object[]接收,然后对数组进行解析。
//转换实体类
public static <T> List<T> castEntity(List<Object[]> list, Class<T> clazz) throws Exception {
List<T> returnList = new ArrayList<T>();
if(CollectionUtils.isEmpty(list)){
return returnList;
}
Object[] co = list.get(0);
Class[] c2 = new Class[co.length];
//确定构造方法
for (int i = 0; i < co.length; i++) {
if(co[i]!=null){
c2[i] = co[i].getClass();
}else {
c2[i]=String.class;
}
}
for (Object[] o : list) {
Constructor<T> constructor = clazz.getConstructor(c2);
returnList.add(constructor.newInstance(o));
}
return returnList;
}
实体类
@Entity
@Table(name = "user")
@Data
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 姓名
@Column(name = "name")
private String name;
// 昵称
@Column(name = "nickname")
private String nickname;
// 出生日期
@Column(name = "birthday")
private LocalDate birthday;
// 性别
@Column(name = "sex")
private Boolean sex;
//密码
@Column(name = "password")
private String password;
}
我们想要查询数据表中的某几个字段,有以下三种方法。
Spring Data Jpa支持的关键字搜索
自定义一个返回对象
@Data
@ToString
//@NoArgsConstructor 不可写,否则会出现类型转换错误
@AllArgsConstructor // 必须写,转换时会调用
public class UserDTO implements Serializable { private String name; private String nickname; private LocalDate birthday; }
Dao层定义 repository类
注意
- 这种方法存在局限性,只能使用JPA提供的一些方法。如果业务复杂,或者查询条件为空等,则无法处理
public interface UserDao extends JpaSpecificationExecutor<User>,
JpaRepository<User, Integer> {
/**
* 根据id查询
* @param id
* @param type
* @param <T>
* @return
*/
<T> Optional<T> findById(int id, Class<T> type);
}
测试
测试
@Autowired
private UserDao dao;@Test
public void findByIdTest() {
Optional<UserDTO> userDTO = dao.findById(1, UserDTO.class);
if (userDTO.isPresent()) {
System.out.println(userDTO);
}
}
自定义一个返回对象
需求:查询用户姓名,昵称,出生日期、
自定义一个返回对象
@Data
@ToString
//@NoArgsConstructor 不可写,否则会出现类型转换错误
@AllArgsConstructor // 必须写,转换时会调用
public class UserDTO implements Serializable { private String name; private String nickname; private LocalDate birthday; }
Dao层定义 repository类 ,使用@Query注解,JPQL
- 注意:
- 当返回对象为实体类中的某几个属性时,字段名和实体类必须保持一致。否则无法映射
- 如果实体类中的属性名为驼峰例如:userName,映射到数据库则为user_name
- 使用全限定类名com.kxj.jpa.dto.UserDTO,必须有new关键字
public interface UserDao extends JpaSpecificationExecutor<User>,JpaRepository<User, Integer> {
/**
* 根据用户名查找
* @param name
* @return
*/
@Query(value = "select new com.kxj.jpa.dto.UserDTO(u.name, u.nickname, u.birthday) from User u where u.name = :name")
List<UserDTO> findByUserName(@Param("name") String name);
}
测试
测试
@Test
public void findByUserNamesTest() {
List<UserDTO> userDTOS = dao.findByUserName("tom");
for (UserDTO userDTO : userDTOS) {
System.out.println(userDTO);
}
}
自定义接口
自定义一个接口
public interface UserDTO2 {
String getName();/**
* 当别名与该getXXX名称不一致时,可以使用该注解调整
* @return
*/
@Value("#{target.nickName}")
String getNickname(); LocalDate getBirthday(); // 主要起打印作用
default String toStringInfo() {
return getName() + " " + getNickname() + " " + getBirthday();
}
}
Dao层定义 repository类 ,使用@Query注解,JPQL
注意:
当返回对象为接口时,SQL语句中查询的字段为 类.字段 ,eg: u.name . 则必须写别名,和接口中getXXX保持一致,否则无法转换,为null。
如果别名与该getXXX名称不一致时,也可在接口中使用@Value("#{target.XXX}")和SQL中别名保持一致
public interface UserDao extends JpaSpecificationExecutor<User>,
JpaRepository<User, Integer> {
/**
* 根据用户名查找
* 别名必须写 在HQL方式中 否则无法转换 为null,在原生SQL中别名可写可不写
* @param name
* @return
*/
@Query(value = "select u.name as name, u.nickname as nickName, u.birthday as birthday from User u where u.name = :name")
List<UserDTO2> findByUserName(@Param("name") String name);
}
测试
@Test
public void findByNameTest() {
List<UserDTO2> list = dao.findByUserName("tom");
list.forEach(user -> System.out.println(user.toStringInfo()));
}
如果我们想获取Page对象,则查询的时候加上Pageable对象
注意
传入Pageable对象,JPA会自动帮我们解析。查询的时候回多出一条查询条数的SQL
/**
* 分页 根据用户名查找
* @param name
* @param pageable
* @return
*/
@Query(value = "select u.name as name, u.nickname as nickName, u.birthday as birthday from User u where u.name = :name") Page<UserDTO2> findByUserName(@Param("name") String name, Pageable pageable);
测试
注意
当数据表中的查询总条数大于传入的size(每页显示的条数时,才会触发查询总条数
@Test
public void findByNamePageTest() {
Pageable pageable = PageRequest.of(0, 1);
Page<UserDTO2> page = dao.findByUserName("tom", pageable);
page.getContent().forEach(user -> System.out.println(user.toStringInfo()));
System.out.println("条数:" + page.getTotalElements());
}
通过配置输出SQL,可以在控制台看到输出的SQL语句是两条
6. 使用原生SQL
我们除了使用JPQL查询外,也可以使用原生SQL进行查询
/**
* 分页 根据用户名查找 原生SQL
* @param name
* @param pageable
* @return
*/
@Query(value = "select name, nickName, birthday from user where name = :name", nativeQuery = true)
Page<UserDTO2> findByUserNameUseNativeSQL(@Param("name") String name, Pageable pageable);
- 测试同上例
处理实体类中未有的属性
需求:查询用户重名的个数及姓名
自定义对象
- 用法大致和上述无区别,知识多加属性和查询的字段保持一致
- 注意
- 注意:count()查询返回的数据类型是Long类型,如果用其他类型接收,会报错
- 注意
- 用法大致和上述无区别,知识多加属性和查询的字段保持一致
@Data
@ToString
@AllArgsConstructor // 必须写,转换时会调用
public class UserDTO3 { private String name; /**
* 数量
*/
private Long count;
}
/**
* 查找名字相同的数量 类
* @return
*/
@Query(value = "select new com.kxj.jpa.dto.UserDTO3(u.name, count(u.id)) from User u group by u.name")
List<UserDTO3> findUserCount();
自定义接口
public interface UserDTO4 { String getName(); Long getCount(); default String toStringInfo() {
return getName() + " " + getCount();
}
}
/**
* 查找名字相同的数量 接口
* @return
*/
@Query(value = "select name, count(id) as count from user group by name", nativeQuery = true)
List<UserDTO4> findUserNameCount();
上面也可以使用原生SQL查询
以上测试省略
源码
https://github.com/kong0827/Spring-Data-JPA/tree/master/Spring-Data-Jpa-Enhance
总结
- 采用自定义对象接收时,可以分场景使用不同方式
- 自定义对象和自定义接口的区别,例如自定义对象,属性要和实体类保持一致,要有全参构造等
- 如果遇到聚集函数等实体类没有定义的属性应该添加字段相对应的属性到DTO类中
Spring Data JPA 自定义对象接收查询结果集的更多相关文章
- Spring Data JPA中的动态查询 时间日期
功能:Spring Data JPA中的动态查询 实现日期查询 页面对应的dto类private String modifiedDate; //实体类 @LastModifiedDate protec ...
- spring data jpa使用原生sql查询
spring data jpa使用原生sql查询 @Repository public interface AjDao extends JpaRepository<Aj,String> { ...
- 【Spring Data 系列学习】Spring Data JPA 自定义查询,分页,排序,条件查询
Spring Boot Jpa 默认提供 CURD 的方法等方法,在日常中往往时无法满足我们业务的要求,本章节通过自定义简单查询案例进行讲解. 快速上手 项目中的pom.xml.application ...
- Spring Boot 入门系列(二十七)使用Spring Data JPA 自定义查询如此简单,完全不需要写SQL!
前面讲了Spring Boot 整合Spring Boot JPA,实现JPA 的增.删.改.查的功能.JPA使用非常简单,只需继承JpaRepository ,无需任何数据访问层和sql语句即可实现 ...
- Spring Data Jpa (四)注解式查询方法
详细讲解声明式的查询方法 1 @Query详解 使用命名查询为实体声明查询是一种有效的方法,对于少量查询很有效.一般只需要关心@Query里面的value和nativeQuery的值.使用声明式JPQ ...
- spring data jpa自定义bean字段映射
当遇到复杂多表查询时,并且同时还需要确保查询性能,此时则需要使用自定义sql查询,然而spring data jpa对于自定义sql则需使用查询需要在对应的bean中做太多的配置映射,我尝试了一下,最 ...
- Spring MVC和Spring Data JPA之按条件查询和分页(kkpaper分页组件)
推荐视频:尚硅谷Spring Data JPA视频教程,一学就会,百度一下就有, 后台代码:在DAO层继承Spring Data JPA的PagingAndSortingRepository接口实现的 ...
- Spring Data Jpa的四种查询方式
一.调用接口的方式 1.基本介绍 通过调用接口里的方法查询,需要我们自定义的接口继承Spring Data Jpa规定的接口 public interface UserDao extends JpaR ...
- spring data jpa实现多条件查询(分页和不分页)
目前的spring data jpa已经帮我们干了CRUD的大部分活了,但如果有些活它干不了(CrudRepository接口中没定义),那么只能由我们自己干了.这里要说的就是在它的框架里,如何实现自 ...
随机推荐
- Java 关于线程的面试题及答案
一.职场可能碰到的关于线程的面试题: 1. 什么是线程? 线程是程序中一个单一的顺序控制流程.进程内有一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位. ...
- HashMap的四种遍历!
HashMap的四种遍历 import java.util.Collection; import java.util.HashMap; import java.util.Map; import jav ...
- kubernetes集群中的pause容器
昨天晚上搭建好了k8s多主集群,启动了一个nginx的pod,然而每启动一个pod就伴随这一个pause容器,考虑到之前在做kubelet的systemd unit文件时有见到: 1 2 3 4 5 ...
- GDB调试指南-单步调试
前言 前面通过<启动调试>,<断点设置>,<变量查看>,我们已经了解了GDB基本的启动,设置断点,查看变量等,如果这些内容你还不知道,建议先回顾一下前面的内容.在启 ...
- module in JavaScript
JavaScript 在ES6之前没有给出官方模块的定义,因此社区自己搞了两个模块加载方案: CommonJS (node) AMD (browser) 本文略 CommonJS规范 module定义 ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛
传送门:2018 ACM 国际大学生程序设计竞赛上海大都会赛 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛2018-08-05 12:00:00 至 2018-08-05 17:00:0 ...
- 某某项目SDV软件测试报告范例
说明:本范例为符合CMMI 5级要求的范例 Prepared by 拟制 小张 Date 日期 2008-04-09 Reviewed by 评审人 小丽.小王.小李.小莉.小三.小四.小猪.小猫.小 ...
- vuejs 踩坑及经验总结
问题描述 在使用 v-for repeat 组件时控制台会出现警告: 解决方法 在组件标签上使用 v-for : 加 :key 使用 template 标签包裹该组件,再在 template 标签 上 ...
- 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API
https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ...
- 《高性能javascript》阅读摘要
最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践.性能优化类的书.记录下主要知识. 加载和执行 脚本位置 放在<head>中的javasc ...