最近再看公司项目中有如下代码,看了后简直不能忍.缺点列出来的话,如下:
@Override
public List<Object[]> findByPcardCardOrder(
PcardCardOrder pcardCardOrder,String applyInstName2,Integer page, Integer rows) {
StringBuffer sql = new StringBuffer(
"SELECT p.*"
+",p2.vcard_make_des"
+",p3.cardnum_rule_id,p3.vtype_nm"
+",p4.cn_card_bin,p4.cn_nm"
+",p5.inst_id,p5.inst_name,p5.apply_range,p5.card_name,p5.card_type,p5.bin_card_material"
+",p6.inst_name AS apply_inst_name "
+",p7.inst_name AS apply_inst_name2"
+ ",p8.inst_name as receive_inst_name"
+ " FROM "
+" tbl_pcard_card_order p LEFT JOIN tbl_pcard_vcard_make p2 ON p.make_id = p2.vcard_make_id"
+" LEFT JOIN tbl_pcard_vtype p3 ON p2.vcard_make_vtype_id=p3.vtype_id"
+" LEFT JOIN tbl_pcard_cardnum_rule p4 ON p3.cardnum_rule_id=p4.cn_id"
+" LEFT JOIN tbl_pcard_cardbin p5 ON p4.cn_card_bin=p5.card_bin"
+" LEFT JOIN tbl_pcard_institution p6 ON p5.apply_range=p6.inst_id"
+" LEFT JOIN tbl_pcard_institution p7 ON p.apply_inst_id=p7.inst_id"
+" LEFT JOIN tbl_pcard_institution p8 ON p.receive_inst=p8.inst_id"
+" WHERE 1=1 ");
int i = 1;
Map<String, Object> map = new HashMap<String, Object>();
if (!StringUtils.isEmpty(pcardCardOrder.getCordId())) {
sql.append(" and p.cord_id=");
sql.append("?" + i);
map.put(i + "", pcardCardOrder.getCordId());
i++;
}
if (!StringUtils.isEmpty(pcardCardOrder.getAppointMchtcard())) {
sql.append(" and p.appoint_mchtcard=");
sql.append("?" + i);
map.put(i + "", pcardCardOrder.getAppointMchtcard());
i++;
}
if (!StringUtils.isEmpty(pcardCardOrder.getMakeId())) {
sql.append(" and p.make_id like ");
sql.append("?" + i);
map.put(i + "","%%"+ pcardCardOrder.getMakeId()+"%%");
i++;
}
if (!StringUtils.isEmpty(applyInstName2)) {
sql.append(" and p7.inst_name like ");
sql.append("?"+i);
map.put(i+"","%%"+applyInstName2+"%%");
i++;
}
sql.append(" order by p.ct_dm desc");
Query query = entityManager.createNativeQuery(sql.toString());
for (String key : map.keySet()) {
query.setParameter(key, map.get(key));
}
if (page != null && rows != null) {
query.setFirstResult(rows * (page - 1));
query.setMaxResults(rows);
}
return query.getResultList();
}
queryDSL就可以避免上面全部的问题,在解决问题之前先学习如何使用.
首先对于queryDSL有两个版本,com.mysema.querydsl
和com.querydsl
,前者是3.X系列后者是4.X系列,这里使用的是后者.
第一步:Maven引入依赖:
<dependency>
<groupId>com.querydslgroupId>
<artifactId>querydsl-jpaartifactId>
<version>${querydsl.version}version>
dependency>
<dependency>
<groupId>com.querydslgroupId>
<artifactId>querydsl-aptartifactId>
<version>${querydsl.version}version>
<scope>providedscope>
dependency>
第二步:加入插件,用于生成查询实例
<plugin>
<groupId>com.mysema.mavengroupId>
<artifactId>apt-maven-pluginartifactId>
<version>1.1.3version>
<executions>
<execution>
<goals>
<goal>processgoal>
goals>
<configuration>
<outputDirectory>target/generated-sources/javaoutputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessorprocessor>
configuration>
execution>
executions>
plugin>
执行mvn compile
之后,可以找到该target/generated-sources/java
,然后IDEA标示为源代码目录即可.
城市类:
@Entity
@Table(name = "t_city", schema = "test", catalog = "")
public class TCity {
//省略JPA注解标识
private int id;
private String name;
private String state;
private String country;
private String map;
}
旅馆类:
@Entity
@Table(name = "t_hotel", schema = "test", catalog = "")
public class THotel {
//省略JPA注解标识
private int id;
private String name;
private String address;
private Integer city;//保存着城市的id主键
}
Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作,这样的话单表动态查询就可以参考如下代码:
//查找出Id小于3,并且名称带有`shanghai`的记录.
//动态条件
QTCity qtCity = QTCity.tCity;
//该Predicate为querydsl下的类,支持嵌套组装复杂查询条件
Predicate predicate = qtCity.id.longValue().lt(3)
.and(qtCity.name.like("shanghai"));
//分页排序
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"id"));
PageRequest pageRequest = new PageRequest(0,10,sort);
//查找结果
Page tCityPage = tCityRepository.findAll(predicate,pageRequest);
QueryDSL对多表查询提供了一个很好地封装,看下面代码:
/**
* 关联查询示例,查询出城市和对应的旅店
* @param predicate 查询条件
* @return 查询实体
*/
@Override
public List findCityAndHotel(Predicate predicate) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
//添加查询条件
jpaQuery.where(predicate);
//拿到结果
return jpaQuery.fetch();
}
城市表左连接旅店表,当该旅店属于这个城市时查询出两者的详细字段,存放到一个Tuple的多元组中.相比原生sql,简单清晰了很多.
那么该怎么调用这个方法呢?
@Test
public void findByLeftJoin(){
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
//查询条件
Predicate predicate = qtCity.name.like("shanghai");
//调用
List result = tCityRepository.findCityAndHotel(predicate);
//对多元组取出数据,这个和select时的数据相匹配
for (Tuple row : result) {
System.out.println("qtCity:"+row.get(qtCity));
System.out.println("qtHotel:"+row.get(qtHotel));
System.out.println("--------------------");
}
System.out.println(result);
}
这样做的话避免了返回Object[]数组,下面是自动生成的sql语句:
select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!'
分页查询对于queryDSL无论什么样的sql只需要写一遍,会自动转换为相应的count查询,也就避免了文章开始的问题4,下面代码是对上面的查询加上分页功能:
@Override
public QueryResults findCityAndHotelPage(Predicate predicate,Pageable pageable) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery jpaQuery = queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
//拿到分页结果
return jpaQuery.fetchResults();
}
和上面不同之处在于这里使用了offset
和limit
限制查询结果.并且返回一个QueryResults,该类会自动实现count查询和结果查询,并进行封装.
调用形式如下:
@Test
public void findByLeftJoinPage(){
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
//条件
Predicate predicate = qtCity.name.like("shanghai");
//分页
PageRequest pageRequest = new PageRequest(0,10);
//调用查询
QueryResults result = tCityRepository.findCityAndHotelPage(predicate,pageRequest);
//结果取出
for (Tuple row : result.getResults()) {
System.out.println("qtCity:"+row.get(qtCity));
System.out.println("qtHotel:"+row.get(qtHotel));
System.out.println("--------------------");
}
//取出count查询总数
System.out.println(result.getTotal());
}
生成的原生count查询sql,当该count查询结果为0的话,则直接返回,并不会再进行具体数据查询:
select
count(tcity0_.id) as col_0_0_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!'
生成的原生查询sql:
select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!' limit ?
查看打印,可以发现对应的city也都是同一个对象,hotel是不同的对象.
有了上面的经验,改造就变得相当容易了.
首先前面的一堆sql可以写成如下形式,无非是多了一些select和left join
JPAQueryFactory factory = new JPAQueryFactory(entityManager);
factory.select($.pcardCardOrder)
.select($.pcardVcardMake.vcardMakeDes)
.select($.pcardVtype.cardnumRuleId,$.pcardVtype.vtypeNm)
.select($.pcardCardbin)
.leftJoin($.pcardVcardMake).on($.pcardCardOrder.makeId.eq($.pcardVcardMake.vcardMakeId))
//......省略
查询条件使用Predicate
代替,放在service拼接,或者写一个生产条件的工厂都可以.
jpaQuery.where(predicate);
最后的分页处理就和之前的一样了
jpaQuery.offset(pageable.getOffset())
.limit(pageable.getPageSize());
return jpaQuery.fetchResults();
个人感觉Query DSL和Spring Data JPA是绝配.更多请参考Demo代码: