springDataJpa入门教程
-
springDataJpa入门教程(1)-基于springBoot的基本增删改查
-
springDataJpa入门教程(2)-Specification动态条件查询+排序+分页
-
springDataJpa入门教程(3-1)-基于EntityManager原生sql多表联合查询+动态条件查询+分页
-
springDataJpa入门教程(3-2)-基于EntityManager原生sql多表联合查询+动态条件查询+分页返回自定义实体类对象
-
springDataJpa入门教程(4)-Example单表动态条件查询+分页
-
springDataJpa入门教程(5)-单表动态条件查询+分页
-
springDataJpa入门教程(6)-多表动态条件查询+分页
-
springDataJpa入门教程(7)-基于springDataJpa投影(Projection)返回自定义实体类对象
-
springDataJpa入门教程(8)-JPA EnableJpaAuditing 审计功能
-
springDataJpa入门教程(9)-spring jpa实体属性类型转换器AttributeConverter的用法
-
springDataJpa入门教程(10)-JPA使用过程中遇到的坑及解决方法
springDataJpa入门教程(7)-基于springDataJpa投影(Projection)返回自定义实体类对象
在使用springDataJpa做查询操作时,如果查询的结果集和实体类不对应,通常的做法有三种:
- 返回Map对象,然后再手动将Map对象里的值取出来,一个一个set到自定义对象里面。
- 返回Object数组,然后按照数组下标,将值一个一个set到自定义对象里面。
- 在自定义对象类中,添加构造方法,然后通过类似于"@Query(value = “select new 包路径.类名(args1,args2…) from tb_user”)"的方式,将查询结果封装到自定义实体类对象中。
首先说第三种方法,这种方法要创建构造方法,通常情况下构造方法的参数会比较多,构造方法显示很臃肿,而且构造方法的参数顺序要和查询结果集的参数顺序完全对应,否则就会报错,这样的话参数一多很容易出错。再有,如果自定义实体类在多个查询里面使用,结果就是构造方法泛滥,难以维护。
第二种方法,Object数组方式,需要强转才能set到对象里面,有的情况还要做null值判断,不是很方便。
第一种方法,个人认为比第二种和第三种要理想一些,但是还是要自己手动从Map对象里面get值,然后再set,也避免不了要进行类型强转。
今天来给大家介绍另一种方法,springDataJpa的投影Projection,可以很容易实现将结果集封装到自定义对象里面,并且代码也比较简洁。由于本人水平有限,教程中难免出现错误,敬请谅解,欢迎大家批评指正。源码地址:源码下载地址。java学习交流群:184998348,欢迎大家一起交流学习。下面来举个例子。
下面是需要用到的实体类
package com.thizgroup.jpa.study.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "tb_user")
@Data//使用lombok生成getter、setter
@NoArgsConstructor//生成无参构造方法
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name",columnDefinition = "varchar(64)")
private String name;
@Column(name = "mobile",columnDefinition = "varchar(64)")
private String mobile;
@Column(name = "email",columnDefinition = "varchar(64)")
private String email;
@Column(name = "age",columnDefinition = "smallint(64)")
private Integer age;
@Column(name = "birthday",columnDefinition = "timestamp")
private Date birthday;
//地址
@Column(name = "address_id",columnDefinition = "bigint(20)")
private Long addressId;
@Column(name = "create_date",columnDefinition = "timestamp")
private Date createDate;
@Column(name = "modify_date",columnDefinition = "timestamp")
private Date modifyDate;
@Builder(toBuilder = true)
public User(Long id,String name, String mobile, String email, Integer age, Date birthday,
Long addressId) {
this.id = id;
this.name = name;
this.mobile = mobile;
this.email = email;
this.age = age;
this.birthday = birthday;
this.addressId = addressId;
}
}
package com.thizgroup.jpa.study.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
@Entity
@Table(name="tb_address")
@Data//使用lombok生成getter、setter
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "country",columnDefinition = "varchar(64)")
private String country;
@Column(name = "province",columnDefinition = "varchar(64)")
private String province;
@Column(name = "city",columnDefinition = "varchar(64)")
private String city;
}
需求: 根据用户id查询用户信息,用户信息包括姓名、年龄、手机号、国家、城市、地址(地址=国家+城市)。
下面我们来自定义一个接口用于封装查询的结果集,注意:这里定义的是interface,而不是Class,不需要给接口定义实现类。
package com.thizgroup.jpa.study.dto;
import org.springframework.beans.factory.annotation.Value;
/**
* 用户信息 jpa 投影
*/
public interface UserProjection {
public Long getId();
public String getName();//姓名
public Integer getAge();//年龄
public String getMobile();//手机号
public String getCountry();//国家
public String getCity();//城市
@Value("#{target.country+target.city}")
public String getAddress();//地址
}
address这个字段比较特殊,在结果集中并没有一个名为address的字段,这个address是由country字段和city字段聚合得到的。
在IUserService接口中添加一个查询方法,
/**
* 使用jpa投影查询用户信息
* @param id
* @return
*/
UserProjection findUserInfoById(Long id);
然后在UserServiceImpl实现类中实现该方法,
package com.thizgroup.jpa.study.service.impl;
import com.thizgroup.jpa.study.dao.UserRepository;
import com.thizgroup.jpa.study.dto.UserProjection;
import com.thizgroup.jpa.study.model.User;
import com.thizgroup.jpa.study.service.IUserService;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@Service
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
public class UserServiceImpl implements IUserService {
@Autowired
private UserRepository userRepository;
@Override
public UserProjection findUserInfoById(Long id) {
return userRepository.findUserInfoById(id);
}
}
接下来在UserRepository中添加findUserInfoById查询方法,
package com.thizgroup.jpa.study.dao;
import com.thizgroup.jpa.study.dto.UserProjection;
import com.thizgroup.jpa.study.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
@Query("select u.id as id,u.name as name,u.age as age,u.mobile as mobile,a.country as country,"
+ " a.city as city from User u "
+ " left join Address a on u.addressId=a.id where u.id =:id")
UserProjection findUserInfoById(@Param("id") Long id);
}
注意:写sql语句一定要给字段起别名,并且别名必须和自定义接口中的属性名保持一致。
完成以上几步,就能直接拿到一个UserProjection 对象,整个代码看上去十分简洁。
下面写个单元测试来验证一下,
package com.thizgroup.jpa.study.service;
import com.thizgroup.jpa.study.JpaApplication;
import com.thizgroup.jpa.study.dto.UserProjection;
import com.thizgroup.jpa.study.model.User;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest(classes={JpaApplication.class})
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
public class UserServiceImplTest {
@Autowired
private IUserService userService;
@Test
public void findUserInfoByIdTest(){
UserProjection userProjection = userService.findUserInfoById(1L);
System.out.println("id:"+userProjection.getId()+",age:"
+userProjection.getAge()+",country:"+userProjection.getCountry()
+",address:"+userProjection.getAddress());
}
}
运行一下单元测试,结果如下:
id:1,age:35,country:中国,address:中国上海
至此,springDataJpa使用Projection返回自定义实体类对象就介绍完了,有需要源码的朋友,请到git上下载源码,源码地址:源码下载地址。java学习交流群:184998348,欢迎大家一起交流学习。