基本知识:
下面是一个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
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
关键字 | 例子 | 对应的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) |
SELECT a.* FROM address a JOIN user u ON a.user_id = u.id WHERE a.id = 1 AND u.id = 2
Address findTopByIdAndUser_Id(int addressId,int userId);
public interface PersonRepository extends JpaRepository{
//获得符合查询条件的前30条数据
ListfindTop30ByName(String name);
}
@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);
多表联合查询,动态查询可以用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
查询数据:
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;
}
};