Spring Data Jpa 的简单查询多表查询HQL,SQL ,动态查询, QueryDsl ,自定义查询笔记

 

 

基本知识:

  • Repository:仅仅只是一个标识,没有任何方法,方便Spring自动扫描识别
  • CrudRepository:继承Repository,实现一组CRUD相关方法
  • PagingAndStortingRepository:继承CrudRepository,实现一组分页排序相关方法
  • JpaRepository:继承PagingAndStortingRepository,QueryByExampleExecutor 实现一组JPA规范方法

下面是一个AppUser 的 Dao接口,除了简单的findBy 接口查询外,还有@Query的查询,查询返回一个自定义的结果Useinfo,UserInfo 从2个表中获取,从AppUser 中获取 phone ,name 等信息,从 HearUrl 表中获取userid 对应的头像地址;

@Repository
public interface AppUserRepository extends JpaRepository{
	
	AppUser findByUserid(String userid);

    //根据电话和客户名查询
	AppUser findByPhoneAndCustomName(String phone, String customName);

    //分页查询
	Page findByCustomName(String customName,Pageable pageable);


    //查询2个表的指定字段,组成新的对象UserInfo
	@Query(value = "select new com.cxx.beans.UserInfo(u.userid, u.phone,u.name, u.address, u.info,head.headUrl) "
			+ "from AppUser u, HeadUrl head where u.userid = head.userid and"
			+ " u.userid = :userid")
	UserInfo  findUserInfoByUserid(@Param("userid") String userid);
	

    //查询2个表具有同一个userid 的内容,并组成新的对象
	@Query(value = "select new com.cxx.beans.UserInfo(u.userid, u.phone,u.name, u.address, u.info,head.headUrl) "
			+ "from AppUser u, HeadUrl head where u.userid = head.userid")
	List  findUserInfo();
	

	@Query(value = "select u.userid, u.phone,u.name, u.address, u.info,head.headUrl from AppUser u, HeadUrl head"
			+ " where u.userid = head.userid and"
			+ " u.userid = :userid",nativeQuery = true)
	List  findUserInfos2(@Param("userid") String userid);


    //删除用户,根据name 和city 删除全部匹配的用户
	@Modifying
	@Transactional
	@Query(value="delete from AppUser where name =?1 and city= ?2")
	void deleteUser(String name,String city);
    


    //去重查询,查询city 列表
	@Query(value="select distinct city from AppUser")
    List getCityList();
    
}

 

public class UserInfo implements Serializable{
	
	private String userid;
	private String phone;
	private String name;
	private String address;
	private String info;
	private String headUrl;
	
	public UserInfo()
	{
	}
		
	public UserInfo(String userid, String phone, String name, String address, String info, String headUrl) {
		super();
		this.userid = userid;
		this.phone = phone;
		this.name = name;
		this.address = address;
		this.info = info;
		this.headUrl = headUrl;
	}
    
    ...
}
    findUserInfo 返回的数据结构
        {
            "userid": "e4d6f4eef48c4408985b65f6997891bb",
            "phone": "13760234541",
            "name": "张三",
            "address": null,
            "info": null,
            "headUrl": "http://cbc-huanan-image.oss-cn-shenzhen.aliyuncs.com/15337983334421.png"
        }

    findUserinfos2 返回的数据结构:
    [
        [
            "e4d6f4eef48c4408985b65f6997891bb",
            "13760234541",
            "张三",
            null,
            null,
            "http://cbc-huanan-image.oss-cn-shenzhen.aliyuncs.com/15337983334421.png"
        ],
        [ ... ]
    ]
  
    查询到多个去重后,比如我需要查包含密码信息的房间和包含指纹信息的房间,查找结果再去重,得到所有的房间号 
   //set去重

        Set set = new HashSet();
        List listNew=new ArrayList<>();
        set.addAll(list1);
        set.addAll(list2);
        listNew.addAll(set);
  

更正:

      我想从 A表(AppUser)中获取一些字段(name ,等), 再根据B表(HeadUrl)中获取一些字段(headUrl),组成查询结果,上面的这个方法,在有一个表中不存在相应的记录是无法得到想要的结果    
    @Query(value = "select new com.cxx.beans.UserInfo(u.userid, u.phone,u.name, u.address, u.info,head.headUrl) "
            + "from AppUser u, HeadUrl head where u.userid = head.userid and"
            + " u.userid = :userid")
    UserInfo  findUserInfoByUserid(@Param("userid") String userid);

正确的SQL 查询语句如下:需要用LeftJoin 方式,这样当HeadUrl 中查询内容为空时也能正确返回。

所以正确的查询方法如下,但这个返回的是Object数组,需要根据这个数组内容,再手动转成我们需要的对象

 @Query(select u.userid, u.phone,u.name, u.address, u.info, head.headUrl from AppUser u LEFT JOIN HeadUrl head    USING(userid) where u.userid = :userid",nativeQuery =true)
    List findFriendsByUserid(@Param("userid")String userid);

 

Spring JPA方法名解析步骤

  • 在进行方法名解析时会先去掉多余的前缀,比如find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析,并且如果方法最后一个参数时 Sort 或 Pageable 类型,也会提取相关信息
  • 比如:findByNameLikeAndAgeGreaterThan
    • 剔除findBy
    • 判断nameLikeAndAgeGreaterThan(根据POJO规范,首字母变小写)是否为返回对象 User 的一个属性,如果是,则根据该属性进行查询,如果没有该属性,则进行下一步
    • 从右往左截取第一个大写字母开头的字符串(此处为Than),然后检查剩下的字符串是否为 User 的一个属性,如果是,则根据该属性进行查询,否则重复第2步,直到找出 name 为 User 的属性
    • 从已截取的部分后面开始,重新第 1 步开始(剔除条件语句),循环,直到把方法名处理完毕
    • 通过获取的操作、条件和属性、带入参数值,生成查询
关键字 例子 对应的JPQL语句
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

关联外键查询

  • 假设有两张表 address与 user
  • address表中有一个 user_id 的外键列对应着 user 表的主键
  • 若查询条件为 id 为 1 的 address且同时对应 id 为 2 的用户,如下列 SQL 语句:
SELECT a.* FROM address a JOIN user u ON a.user_id = u.id WHERE a.id = 1 AND u.id = 2
  • 则使用 _ 来连接关联实体属性查询,上述 SQL 对应的 JPA 方法名为
Address findTopByIdAndUser_Id(int addressId,int userId);

限制查询结果数量

public interface PersonRepository extends JpaRepository{
//获得符合查询条件的前30条数据
ListfindTop30ByName(String name);
}

通过@query编写创建查询

  • 可以通过@Query(若使用nativesql属性,则使用原生的sql语句)注解来创建查询(参数也可用 ?1 ?2 代替,则不需@Param),如:
@Query("select * from User u where u.name like :first and u.age>:age")
List findByNameLikeAndAgeGreaterThan(@Param("first")String firstName,@Param("age")Integer age);
@Modifying 
@Transactional
@Query("update User u set u.name = ?1 where u.id = ?2") 
public int increaseSalary(String name, int id);

@Modifying
@Transactional
@Query(value="delete from UserShips where userid =?1 and houseNo= ?2")
void deleteByUseridAndHouseNo(String userid ,String houseNo);
  • 更新、删除操作需要加上@Transactional @Modifying

 

复杂查询

多表联合查询,动态查询可以用QueryDsl 方式,也可以用Specification 方式来做。以下是动态查询的一个实例:

  下面是人员Bean 的关键数据:

public class Person {	
	
	@Id
	@GeneratedValue(generator="system-uuid")
	private String id;

	
	private String name;
	
	....

	@ManyToOne
	private House house;
	
	@ManyToOne
	//@JsonIgnore
	private Building building;

	@ManyToMany(cascade = CascadeType.PERSIST,fetch=FetchType.EAGER)
	private Set gate_device;
		
	
	private String phone;

Dao:

@Repository
public interface PersonDao extends JpaRepository,JpaSpecificationExecutor,QuerydslPredicateExecutor 
{

	Person findByName(String name);
	
	List findByBuildingId(String id);
	List findByIdIn(Collection ids);
		
	//根据person ID 查询 person的概要信息
	@Query(value="SELECT p.id as pid," 
					+ "p.name as name, "
					+ "p.sex as sex,"  
					+ "p.card_id as card_id," 
					+ "file.url as url," 
					+ "p.enable as enable,"
					+ "p.permission_status,"
					+ "h.name AS room_name," 
					+ "b.name AS building_name " 
			        + "FROM Person p "
					+ "LEFT JOIN FileUrl file ON p.id=file.owner.id " 
					+ "LEFT JOIN House h ON p.house.id = h.id " 
					+ "LEFT JOIN Building b ON p.building.id = b.id " 
					+ "WHERE p.id= ?1 "
					)	
	List findAllInfo(String id);	

	
	/*
	 * personinfo 的构造函数 和QueryDsl 的自动构造函数有冲突(好像是) 
	 * 
	 * 
	 * 
	//根据person id 查询获取person 的概要信息 personinfo
	@Query(value="SELECT new com.cbest.cb025.entity.PersonInfo("
			+ "p.id," 
			+ "p.name,"
			+ "p.card_id,"
			+ "h.name," 
			+ "b.name, " 
			+ "p.sex," 
			+ "p.permission_status,"
			+ "p.enable"
			+ ") "

	        + "FROM Person p "
			+ "LEFT JOIN House h ON p.house.id = h.id " 
			+ "LEFT JOIN Building b ON p.building.id = b.id " 
			+ "WHERE p.id= ?1 GROUP BY p.id"
			)
	PersonInfo findOnePersonInfo(String id);
	

	@Query(value="SELECT new com.cbest.cb025.entity.PersonInfo("
			+ "p.id," 
			+ "p.name,"
			+ "p.card_id,"
			+ "h.name," 
			+ "b.name, " 
			+ "p.sex," 
			+ "p.permission_status,"
			+ "p.enable"
			+ ") "
	        + "FROM Person p "
			+ "LEFT JOIN House h ON p.house.id = h.id " 
			+ "LEFT JOIN Building b ON p.building.id = b.id " 
			+ "WHERE p.id in (?1)"
			)
	List findPersonInfoByPersonIds(String[] id);	
	*/

查询数据:

		Specification s = new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {

				Predicate p = cb.conjunction();
				
				
				 p.getExpressions()
					.add(cb.equal(root.get("building").get("address").get("owner").get("company"),company));
				
				if (!StringUtils.isEmpty(req.getProvince())) {
					p.getExpressions().add(cb.like(root.get("building").get("address").get("province"),
							"%" + req.getProvince() + "%"));
				}
				// 根据city 查询
				if (!StringUtils.isEmpty(req.getCity())) {
					p.getExpressions()
							.add(cb.like(root.get("building").get("address").get("city"), "%" + req.getCity() + "%"));
				}

				if (!StringUtils.isEmpty(req.getDistrict())) {
					p.getExpressions().add(cb.like(root.get("building").get("address").get("district"),
							"%" + req.getDistrict() + "%"));
				}
				if (!StringUtils.isEmpty(req.getStreet())) {
					p.getExpressions().add(
							cb.like(root.get("building").get("address").get("street"), "%" + req.getStreet() + "%"));
				}
				if (!StringUtils.isEmpty(req.getVillage())) {
					p.getExpressions().add(
							cb.like(root.get("building").get("address").get("name"), "%" + req.getVillage() + "%"));
				}

				if (!StringUtils.isEmpty(req.getApartment())) {
					p.getExpressions().add(cb.like(root.get("building").get("name"), "%" + req.getApartment() + "%"));
				}

。。。


				if (!StringUtils.isEmpty(req.getKeyword())) 
				{
					Join person_room = root.join("house", JoinType.LEFT); // 这里必须要用JOIN 的形式,否则在house 外键为空时查询不到数据
	/*				p.getExpressions().add(cb.and(cb.isNotNull(root.get("house")),cb.like(root.get("house").get("name"), "%" + req.getRoom() + "%")));*/
					
					p.getExpressions().add(
							cb.or(
								cb.like(root.get("phone"), "%" + req.getKeyword() + "%"),
								cb.like(root.get("name"), "%" + req.getKeyword() + "%"),
								cb.like(root.get("card_id"), "%" + req.getKeyword() + "%"),
								cb.like(root.get("building").get("address").get("name"), "%" + req.getKeyword() + "%"),
								cb.like(root.get("building").get("name"), "%" + req.getKeyword() + "%"),
								cb.like(person_room.get("name"),"%" + req.getKeyword() + "%")
								)
							);					
				}
				return p;
			}
		};
		if (req.getSize() == 0) {
			List devices = personDao.findAll(s);
			return ResponseFactory.getBaseResponse(devices);
		}

		Sort sort = new Sort(Sort.Direction.ASC, "name");
		Pageable pageable = PageRequest.of(req.getPage(), req.getSize(), sort);

		Page devices = personDao.findAll(s, pageable);

上面就是动态查询的人员列表的重点代码;特别注意的是,Person 中有个 外键 House 是可以为Null 的; 这种情况下就必须选用Join 的方式来创建查询表达式,而上面的查询例子中 Building 因为不为空,所以可以用 直接创建表达式的形式。

 

2.多表联合查询,返回自定义分页数据:

 

人员文件信息

@Entity
@EntityListeners(AuditingEntityListener.class)
@GenericGenerator(name="system-uuid",strategy = "uuid")
public class FileUrl {
	
	@Id
	@GeneratedValue(generator="system-uuid")
	private String id;
	
	private String url;
	
	@ManyToOne(optional = true,cascade = CascadeType.PERSIST)
	@JsonIgnore
	private Person owner;
....

查询结果Bean 定义:

@Data
@Builder
public class PersonInfo {
		

	private String pid;	
	private String name;
	private String card_id;	
	private String room_name;
	private String building_name;
	private String start_time;
	private String end_time;
	
	
	private int sex;
	private int permission_status;	
	private boolean enable;
	
	private Set urls = new HashSet(); // 怎么查询出这个数据? 一直没找到直接的方法。
	
	public String getPid() {
		return pid;
	}
	public void setPid(String pid) {
		this.pid = pid;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getCard_id() {
		return card_id;
	}
	public void setCard_id(String card_id) {
		this.card_id = card_id;
	}
	public String getRoom_name() {
		return room_name;
	}
	public void setRoom_name(String room_name) {
		this.room_name = room_name;
	}
	public String getBuilding_name() {
		return building_name;
	}
	public void setBuilding_name(String building_name) {
		this.building_name = building_name;
	}
	public int getSex() {
		return sex;
	}
	public void setSex(int sex) {
		this.sex = sex;
	}
	public int getPermission_status() {
		return permission_status;
	}
	public void setPermission_status(int permission_status) {
		this.permission_status = permission_status;
	}
	public boolean isEnable() {
		return enable;
	}
	public void setEnable(boolean enable) {
		this.enable = enable;
	}
	
   public PersonInfo(String pid, String name, String card_id, String room_name, String building_name,
			String start_time, String end_time, int sex, int permission_status, boolean enable,Set urls) {
		super();
		this.pid = pid;
		this.name = name;
		this.card_id = card_id;
		this.room_name = room_name;
		this.building_name = building_name;
		this.start_time = start_time;
		this.end_time = end_time;
		this.sex = sex;
		this.permission_status = permission_status;
		this.enable = enable;
        this.urls = urls;
	}
   
public static PersonInfo transform(Tuple tuple) {
        QPerson person = QPerson.person;
        QHouse house = QHouse.house;
        QBuilding building = QBuilding.building;
        
        PersonInfo pinfo = new PersonInfo(
        		tuple.get(person.id),
        		tuple.get(person.name),
        		tuple.get(person.card_id),
        		tuple.get(house.name),
        		tuple.get(building.name),
        		tuple.get(person.start_time),
        		tuple.get(person.end_time),
        		tuple.get(person.sex),
        		tuple.get(person.permission_status),
        		tuple.get(person.enable),
        		new HashSet()        		
        		);

        return pinfo;
    }
...

QueryDsl 方式分页查询获取数据: 没有一次性查找获得文件地址的数组,因为研究了好久还是不会,有人可以留言帮指导下吗?

 public PageResult findPageablePersonInfoByDeviceId(String device_id,Pageable pageable) {
        logger.info("findPageablePersonInfoByDeviceId id = " + device_id + " pageSize = " + pageable.getPageSize() + ",pageNumber = " + pageable.getPageNumber());
        long time = System.currentTimeMillis();
        QPerson person = QPerson.person;
        QHouse house = QHouse.house;
        QBuilding building = QBuilding.building;
        QFileUrl fileUrl = QFileUrl.fileUrl;
        
        String[] ids = personDao.findPersonIdsByDevicId(device_id);
        
        logger.info("findPageablePersonInfoByDeviceId: findPersonIdsByDevicId costTime = " + (System.currentTimeMillis() - time));
        QueryResults queryResults = jpaQueryFactory
                .select(
                        person.id,
                        person.name,
                        house.name,
                        building.name,
                        person.card_id,
                        person.start_time,
                        person.end_time,
                        person.sex,
                        person.permission_status,
                        person.enable
                )
                .from(person)
                .leftJoin(house).on(house.id.eq(person.house.id))
                .leftJoin(building).on(person.building.id.eq(building.id))
                .where(person.id.in(ids))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults()
                ;
        Set info = queryResults.getResults().stream().map(PersonInfo::transform
                
                )
                .collect(Collectors.toSet());
        
        Set pids = new HashSet();
        
        for(PersonInfo p : info)
        {
            pids.add(p.getPid());
        }
        
        List res = jpaQueryFactory
                .select(
                        person.id,
                        fileUrl.url                        
                        )
                .from(fileUrl)
                .leftJoin(person).on(fileUrl.owner.id.eq(person.id))
                .where(person.id.in(pids))
                .fetch();        
        
        for(PersonInfo p : info)
        {
            for (Tuple row : res) {
                if(row.get(person.id).equals(p.getPid()))
                        p.addUrl(row.get(fileUrl.url));      
            }
        }
        
        int totalPage = 0;
        if(queryResults.getTotal()%pageable.getPageSize() == 0)
         totalPage = (int) (queryResults.getTotal()/pageable.getPageSize());
        else
         totalPage = (int) (queryResults.getTotal()/pageable.getPageSize()) + 1;
        
        PageResult  result = new PageResult(info,
                totalPage, 
                (int)queryResults.getTotal(), 
                (pageable.getPageNumber() == totalPage -1) ? true :false,
                pageable.getPageNumber(),
                (pageable.getPageNumber() == 0) ? true :false,
                info.size());
        
        logger.info("findPageablePersonInfoByDeviceId: findTotal costTime = " + (System.currentTimeMillis() - time));

        return result;
    }

用到的复杂查询大概就是这些了。

还有一种情况,就是一对多的联合查询处理,可以用SetJoin 这样的方式

		Specification s = new Specification() {

			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {

				Predicate p = cb.conjunction();
				SetJoin join = root.joinSet("gate_device", JoinType.LEFT);
				p.getExpressions().add(cb.equal(join.get("sn"), req.getDevice_sn()));
				return p;
			}

		};

 

你可能感兴趣的:(SpringCloud笔记)