基于SpringDataJpa的mysql动态分页多表查询

来源:互联网 发布:安卓触屏修护软件 编辑:程序博客网 时间:2024/06/05 00:40


基于SpringDataJpa的mysql动态分页多表查询

    由于这篇文章预计篇幅会很长,关于Spring Data JPA的知识就简短的分享,更多的请自行度娘,JPA 封装了很多查询的接口,但今天要讲到的例子需涉及到的技术:①.jpa动态查询,即查询条件不定;②.分页查询,根据传入的页码,和每页的显示行进行分页;③.联表查询:根据需求获得联表查询的结果集;④.获得不相关结果集的笛卡尔积

 1.jpa动态分页查询

    框架封装的动态分页查询很简单,只需要Dao层继承接口,service层重写jpaSpecificationExecutor的findAll方法,判断查询条件即可,最后返回Page对象,由于后面会提及此对象,这里不做阐述。

@Entity
@Table(name="machine_user_info")
public class MachineGroupInfo extends IdEntity {
private String snCode;
private String userName;
private String telephone;
private String itemName;
private String prov;
private String city;
private String area;
private String address;

get、set方法

}

    这里我们写了一个统一的ID管理类继承了它,如果你只有一个类请在id字段使用

@Id

@GeneratedValue

注解标注

service层重写findAll方法:

public Page<MachineGroupInfo> getMachineGroupInfo(Integer page, Integer size, String prov, String city, String area, String itemname,

String username, String tel, String snCode){
Map<String,Object> map=new HashMap<>();
Sort sort=new Sort(Sort.Direction.ASC,"id");
Pageable pageable=new PageRequest(page,size,sort);
Page<MachineGroupInfo> page2=machineGroupInfoRepository.findAll(new Specification<MachineGroupInfo>() {
@Override
public Predicate toPredicate(Root<MachineGroupInfo> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
CriteriaQuery<MachineGroupInfo> query = cb.createQuery(MachineGroupInfo.class);
Predicate p1=null;
if (prov != null && !("".equals(prov))) {
Predicate p2 = cb.equal(root.get("prov").as(String.class), prov);
if (p1 != null) {
p1 = cb.and(p1, p2);
else {
p1 = p2;
}
~~~~~代码逻辑~~~~~~
}
query.where(p1);
return query.getRestriction();
}
},pageable);
return page2;

}

    我们判断条件参数是否非空,以此来判断是否将条件加入查询语句,话到这里,我们知道框架的模块化,高内聚性和低耦合性是的我们的开发过程得到了大大的简化,但随之而来的问题是当我们代码的逻辑出现问题时,问题的查找变得不再那么直观,这里介绍一个我们用的很少的数据库驱动--log4jdbc,通常数据库厂商会针对每种语言发布相对应的接口程序,如mysql的驱动com.mysql.jdbc.Driver。

     Log4JDBC的好处在于我们可以它会将框架产生的数据库执行计划或者sql以日志的形式输出到控制台,这使得我们的检工作更加的方便,框架使用效果更加直观,如hibernate的执行计划参数使用"?"占位符,当参数确定时由框架生成完整的sql:

 Hibernate: select machine0_.sn_code as sn_code14_0_, machine0_.id as id1_0_, 

 machine0_.air_direction as air_dir15_0_, machine0_.auto_antifreeze_protect as auto_an16_0_,

 machine0_.backwater_temp as backwat17_0_, machine0_.crankcase_status as crankca18_0_, 

 machine0_.cumulative_electricity as cumulat19_0_, machine0_.cur_operate_mode as cur_ope20_0_, 

 machine0_.device_type as device_21_0_, machine0_.effluent_temp as effluen22_0_, 

 machine0_.electric_current as electri23_0_, machine0_.exhaust_temp1 as exhaust24_0_, 

 machine0_.expansion_valve_step as expansi25_0_, machine0_.fault_effluent_temp as fault_ef2_0_,

 machine0_.fault_exhaust_temp as fault_ex3_0_, machine0_.fault_high_voltage as fault_hi4_0_, 

 machine0_.fault_indish_temp as fault_in5_0_, machine0_.fault_indoor_temp as fault_in6_0_, machine0_.fault_press_temp

 as fault_pr7_0_, machine0_.fault_room_temp as fault_ro8_0_, machine0_.fault_water_switch as fault_wa9_0_, 

 machine0_.fourway_valve_status1 as fourway26_0_, machine0_.indish_temp as indish_27_0_, 

 machine0_.indoor_fan_status as indoor_28_0_, machine0_.indoor_temp as indoor_29_0_, 

 machine0_.machine_power as machine30_0_, machine0_.machine_voltage as machine31_0_, 

 machine0_.outdoor_temp as outdoor32_0_, machine0_.permanent_fault_high_voltage as permane10_0_, 

 machine0_.permanent_fault_press_temp as permane11_0_, machine0_.permanent_fault_water_switch as 

 permane12_0_, machine0_.press_status1 as press_s33_0_, machine0_.report_time as report_13_0_,

 machine0_.set_temp as set_tem34_0_, machine0_.sleep_mode as sleep_m35_0_, machine0_.switch_status as switch_36_0_, 

 machine0_.water_pump_status as water_p37_0_, machine0_.water_supply_temp as water_s38_0_, 

 machine0_.water_tank_temp as water_t39_0_, machine0_.wind_status as wind_st40_0_ from machine_report_info 

 machine0_ where (machine0_.fault_room_temp=? or machine0_.fault_indoor_temp=? or machine0_.fault_indish_temp=? or 

 machine0_.fault_effluent_temp=? or machine0_.fault_exhaust_temp=? or machine0_.permanent_fault_press_temp=? or machine0_.fault_press_temp=? 

 or machine0_.fault_water_switch=? or machine0_.permanent_fault_water_switch=? or machine0_.fault_high_voltage=? 

 or machine0_.permanent_fault_high_voltage=?) and (cast(machine0_.report_time as datetime) between ? and ?) order by machine0_.id asc limit ?

MVC log4jdbc:配置信息如下所示:

spring.jpa.show-sql = true

logging.level.org.springframework.data=DEBUG

spring.jpa.hibernate.ddl-auto=

spring.datasource.validationQuery = SELECT 1

#######db##########

#spring.datasource.url=jdbc:mysql://172.17.123.193:8096/geothermy?useUnicode=true&characterEncoding=utf-8

spring.datasource.url = jdbc:log4jdbc:mysql://172.17.123.193:8096/geothermy?useUnicode=true&characterEncoding=utf-8

spring.datasource.username=geo

spring.datasource.password=******************

#spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.driverClassName = net.sf.log4jdbc.DriverSpy

spring.datasource.initialSize=5

spring.datasource.minIdle=5

spring.datasource.maxActive=20

# Naming strategy

spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy

    我们做代码测试的时候这个会很有帮助,通过这个方法我们也能学习框架中对于各种需求处理的方式,下面内容中我们会提到。

2.分页查询

    在web项目中分页查询是不可或缺的一项技术,一般有两种分页方式:①.前端分页:首先这是一种伪分页,我们将需要的数据一次性查询并发送给前端,由前端进行分页,显然这种方式的缺点不用细说,数据量巨大的时候从响应时间到硬件资源的占用都是非常大的。②.分页查询分页:我们只需在创建查询sql的时候确定分页参数,按指定页数,指定页行数进行查询并返回结果即可,优势不言而喻。

JPA分页查询:

Dao层接口继承如下接口:

public interface MachineGroupStatusRespository extends PagingAndSortingRepository<Machine,Long>,JpaSpecificationExecutor<Machine> {}

泛型是你需要查询的Entity类;

Service层:

public Page<Machine> getHistoriaclInfo(Integer page, Integer size, String sncode, Timestamp minTime, Timestamp curTime){
Sort sort=new Sort(Sort.Direction.DESC,"reportTime");
Pageable pageable=new PageRequest(page,size,sort);
Page<Machine> pageHistory=machineGroupStatusRespository.findAll(new Specification<Machine>() {
@Override
public Predicate toPredicate(Root<Machine> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
CriteriaQuery query=cb.createQuery(Machine.class);
Predicate p1=cb.equal(root.get("snCode").as(String.class),sncode);
Predicate p2=cb.and(p1,cb.between(root.get("reportTime").as(Timestamp.class),minTime,curTime));
query.where(p2);
return query.getRestriction();
}
},pageable);
return pageHistory;
}

返回Page对象,我们来看看Page对象的结构:

{"machineGroupInfo":{"content":[],"totalElements":0,"last":true,"totalPages":0,"size":10,"number":0,"sort":[{"direction":"ASC","property":"id","ignoreCase":false,"nullHandling":"NATIVE","ascending":true}],"first":true,"numberOfElements":0}} 包括了:元数据,排序方式,大小写敏感设置,查询记录的总条目数,当前页的数据条目数,总页数等信息;

3.联表查询和笛卡尔积

  mysql作为关系型数据库因为表与表之间的联系组成了庞大的数据组织,而联表查询使我们使用的较多的数据库技术,不做多的阐述,但当两个表格或者查询结果集没有直接的关联而我们又需要将其合并在一起时怎么做呢?这时我们就需要用到mysql的笛卡尔积以合并无关的结果集或者表格,基本语法参见:mysql笛卡尔积

4.Spring Date JPA 动态分页联表查询的实现

    在经过对JPA的一系列接口实验以实现上述需求之后,笔者发现它们似乎并不适合做这件事情,特别是当查询比较复杂时,使用封装的接方法会使得工作很难进行并且容易逻辑混乱,经过前辈推荐,我们在这里选择使用EntityManager 的nativeQuery方法进行查询,然后自己封装查询结果:

实体类:

public class UnionSearch extends IdEntity {

//(继承公共的Id管理类,如果你只有一个类请使用@Id @GeneratedValue)
private String sncode;
private String prov;
private String city;
private String area;
private String tel;
private String address;
private String itemname;
private String username;
private String switchstatus;
private String reporttime;
private String sum;

构造器

get、set方法

}

Dao层:


/**
* Created by BBSee rolltion.zhang@foxmail.com on 2017/7/20.
*/
@Transactional
@Repository
public class CommonDao {
@PersistenceContext
EntityManager entityManager;

/**
* * 查询数据集合
@param sql 查询sql sql中的参数用:name格式
@param params 查询参数map格式,key对应参数中的:name
@param clazz 实体类型为空则直接转换为map格式
@return
*/
@SuppressWarnings("unchecked")
public List<?> queryListEntity(String sql, Map<String, Object> params, Class<?> clazz){
Session session = entityManager.unwrap(org.hibernate.Session.class);
SQLQuery query = session.createSQLQuery(sql);
if (params != null) {
for (String key : params.keySet()) {
query.setParameter(key, params.get(key));
}
}
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List<Map<String, Object>> result = query.list();
if (clazz != null) {
List<Object> entityList = convert(clazz, result);
return entityList;
}
return result;
}

private List<Object> convert(Class<?> clazz, List<Map<String, Object>> list) {
List<Object> result;
if (CollectionUtils.isEmpty(list)) {
return null;
}
result = new ArrayList<Object>();
try {
PropertyDescriptor[] props = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
for (Map<String, Object> map : list) {
Object obj = clazz.newInstance();
for (String key:map.keySet()) {
String attrName = key.toLowerCase();
for (PropertyDescriptor prop : props) {
attrName = removeUnderLine(attrName);
if (!attrName.equals(prop.getName())) {
continue;
}
Method method = prop.getWriteMethod();
Object value = map.get(key);
if (value != null) {
value = ConvertUtils.convert(value,prop.getPropertyType());
}
method.invoke(obj,value);
}
}
result.add(obj);
}
catch (Exception e) {
throw new RuntimeException("数据转换错误");
}
return result;
}

private String removeUnderLine(String attrName) {
//去掉数据库字段的下划线
if(attrName.contains("_")) {
String[] names = attrName.split("_");
String firstPart = names[0];
String otherPart = "";
for (int i = 1; i < names.length; i++) {
String word = names[i].replaceFirst(names[i].substring(0, 1), names[i].substring(0, 1).toUpperCase());
otherPart += word;
}
attrName = firstPart + otherPart;
}
return attrName;
}

/**
* 获取记录条数
@param sql
@param params
@return
*/
public Integer getCountBy(String sql,Map<String, Object> params){
Query query = entityManager.createNativeQuery(sql);
if (params != null) {
for (String key : params.keySet()) {
query.setParameter(key, params.get(key));
}
}
BigInteger bigInteger = (BigInteger) query.getSingleResult();
return bigInteger.intValue();
}

/**
* 新增或者删除
@param sql
@param params
@return
*/
public Integer deleteOrUpDate(String sql,Map<String, Object> params){
Query query = entityManager.createNativeQuery(sql);
if (params != null) {
for (String key : params.keySet()) {
query.setParameter(key, params.get(key));
}
}
return query.executeUpdate();
}
}


* Created by BBSee rolltion.zhang@foxmail.com on 2017/7/19.

@SuppressWarnings({"JpaQlInspection""JpaQueryApiInspection"})
@Repository
public class UnionSearchDao extends CommonDao{

/**
* 按查询条件动态分页
@param queryCondition
@return
*/
public List<UnionSearch> getPageList(Integer currentPage,Integer size,Map<String,Object> queryCondition){
String sql="select * from (select \n" +
"t2.id as id, t2.sncode as sncode,\n" +
"t2.prov as prov, t2.city as city, \n" +
"t2.area as area, \n" +
"t2.tel as tel , \n" +
"t2.address as address , \n" +
"t2.itemname as itemname , \n" +
"t2.username as username ,\n" +
" case when t1.switchstatus is null then 'NO' end as switchstatus, \n" +
" case when t1.reporttime is null then 'NO' end as reporttime \n" +
" from (select id,sn_code as sncode,prov,city,area,telephone as tel,item_name as itemname, address,user_name as username from machine_user_info where ? )t2 \n" +
" left join (\n" +
" select t3.sncode as sncode,t3.switchstatus as switchstatus,max(t3.reporttime) as reporttime \n" +
" from (\n" +
" select sn_code as sncode,switch_status as switchstatus,report_time as reporttime from machine_report_info \n" +
" where report_time >= CURRENT_TIMESTAMP - INTERVAL 10 MINUTE)t3 group by t3.sncode)t1 on t2.sncode=t1.sncode group by t2.id limit "+currentPage*size+","+size+ ")s1,\n" +
" (select count(distinct m.id) as sum from (\n" +
" select id,sn_code as sncode,prov as prov,city as city,area as area,telephone as tel,item_name as itemname, address as address,user_name as username from machine_user_info where ?)m)s2\n" +
" order by s1.id,s2.sum";

String where="";
Set set=queryCondition.keySet();
Iterator iterator=set.iterator();
List<String> list=new ArrayList<>();
while(iterator.hasNext()){
list.add((String) iterator.next());
}
for(int i=0;i<list.size();i++){
if(i==0){
where+=list.get(i)+"= :"+list.get(i);
}else {
where+=" and "+list.get(i)+"= :"+list.get(i);
}
}
sql=sql.replace("?",where);
System.out.println(sql);
return (List<UnionSearch>) super.queryListEntity(sql,queryCondition,UnionSearch.class);
}
}

对象封装Bean


import java.util.List;

* Created by BBSee rolltion.zhang@foxmail.com on 2017/7/20.

public class UnionResponse {
private List result;
private String totalPage;
private String totalElements;
private String numberOfElements;
private String resultCode;
getset方法
}

看看我们的SQL查询结果:







service层计算分页参数:

List list= unionSearchDao.getPageList(page,size,queryCondition);

if(list==null){
response.setResultCode("00X");
response.setTotalElements("0");
}else {
response.setResultCode("0");
response.setResult(list);//设置元数据
JSONObject jsonObject=JSONObject.fromObject(list.get(0));
System.out.println(jsonObject.has("sum")+","+jsonObject.has("prov"));
Long sum= Long.parseLong(jsonObject.get("sum").toString());//与查询总数字段一致
response.setTotalElements(sum+"");
Long totalPage=(long)(Math.ceil(Double.parseDouble(sum/size+"")))+1;
response.setTotalPage(totalPage+"");
response.setNumberOfElements(list.size()+"");
}

最后对比一下Page对象与我们自己封装的对象:

Page对象:

{"machineGroupInfo":{"content":[],"totalElements":0,"last":true,"totalPages":0,"size":10,"number":0,"sort":[{"direction":"ASC","property":"id","ignoreCase":false,"nullHandling":"NATIVE","ascending":true}],"first":true,"numberOfElements":0},"resultCode":"00x"}

{"result":[{"id":87,"sncode":"D3A108167900000611110012","prov":"xx省","city":"xx市","area":"xx区","tel":"13xx015xx08x","address":"xxx地址","itemname":"xxx项目", "username":"BBSee","switchstatus":null,"reporttime":null,"sum":"27"}],"totalPage":"27","totalElements":"27","numberOfElements":"1","resultCode":"0"}

 嗯,基本一致,至于排序方式,大小写设置这些就比较简单了,如果需要酌情添加,以上就是jpa的动态条件,动态分页联表查询,通过动态拼接执行计划,没有sql注入的风险,但代码耦合度有点高,待他日我将这个思路封装好再与大家做更多的分享。​​​​


阅读全文
0 0