工作需要,接触到了QueryDSL,总结使用方法,参考了几个大佬的文章,对我帮助很大 参考文章1,参考文章2,参考文章3,参考文章4感谢大佬们!!!
使用场景一些概念
- QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。
- Querydsl可以通过一组通用的查询API为用户构建出适合不同类型ORM框架或者是SQL的查询语句,也就是说QueryDSL是基于各种ORM框架以及SQL之上的一个通用的查询框架。
- 借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以一种通用的API方式来构建查询。目前QueryDSL支持的平台包括
JPA,JDO,SQL,Java,Collections,RDF,Lucene,Hibernate Search。QueryDSL官网:http://querydsl.com/static/querydsl/4.1.3/reference/html_single/
使用方法
- EntityManager:
在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。
EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。- @PersistenceContext
Persistence context是由一组受托管的实体对象实例所构成的集合。它受entity manager 的管理。Entity manager追踪persistence context中所有对象的修改和更新情况,并根据指定的flush模式(本章稍后会做讨论)将这些修改保存到数据库中。一旦persistence context被关闭,所有实体对象实例都会脱离EntityManager而成为非托管对象。对象一旦从persistence context中脱离,就不再受entity manager管理了,任何对此对象的状态变更也将不会被同步到数据库。
pom文件的导入
com.querydsl querydsl-jpa com.querydsl querydsl-apt provided org.springframework.boot spring-boot-maven-plugin com.mysema.maven apt-maven-plugin 1.1.3 generate-sources process target/generated-sources com.querydsl.apt.jpa.JPAAnnotationProcessor
下面是两个大佬的文章里给出的例子,根据例子学习非常快,再次感谢大佬们!!!
例1@Entity @Getter @Setter @ToString @Accessors(fluent = true) @AllArgsConstructor @NoArgsConstructor @Table(name = "t_user") public class User extends IdEntity { private String username; private String password; private String sex; private String age; private String address; private Long roleId; }
@Repository public interface UserRepository extends JpaRepository, QuerydslPredicateExecutor {}
IDEA 的Maven面板,项目生命周期里重新编译项目,会自动生成Q实体类
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @PersistenceContext private EntityManager entityManager; @Override public User saveUser() { User address = new User().username("admin").password("123456").age("12").sex("男").address("山西省"); return userRepository.save(address); } @Override public Optional例2 一. 实体类findById(Long id) { QUser qUser = QUser.user; BooleanExpression eq = qUser.id.eq(id); return userRepository.findOne(eq); } @Override public List findAll() { JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); QUser qUser = QUser.user; QRole qRole = QRole.role; QBean bean = Projections.bean( RoUser.class,//返回自定义实体的类型 qUser.id, qUser.username, qUser.password, qUser.sex, qUser.age, qUser.address, qRole.roleName, qRole.remark ); // 返回分页参数的信息 queryFactory .select(bean) // 返回自定义实体 .from(qUser) // 主表 .leftJoin(qRole) // 从表 .on( qUser.roleId.eq(qRole.id) // on 条件 ).where(qUser.id.eq(2L)) // where 条件 .orderBy(qUser.createTime.desc()) // 排序 .offset(1).limit(10) // 分页 .fetchResults(); return queryFactory .select(bean) // 返回自定义实体 .from(qUser) // 主表 .leftJoin(qRole) // 从表 .on( qUser.roleId.eq(qRole.id) // on 条件 ).where(qUser.id.eq(2L)) // where 条件 .fetch(); } @Override public QueryResults findAll(String username, Long roleId) { JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); QUser qUser = QUser.user; QRole qRole = QRole.role; QBean bean = Projections.bean( RoUser.class,//返回自定义实体的类型 qUser.id, qUser.username, qUser.password, qUser.sex, qUser.age, qUser.address, qRole.roleName, qRole.remark ); // 定义返回类型 JPAQuery from = queryFactory .select(bean) // 返回自定义实体 .from(qUser);// 主表 // 模糊查询名字 if (StringUtils.isNotBlank(username)) { from.where(qUser.username.like("%" + username + "%")); } if (null != roleId) { from.where(qUser.roleId.eq(roleId)); } return from .leftJoin(qRole) .on(qUser.roleId.eq(qRole.id)) // on 子查询 .orderBy(qUser.createTime.desc()) // 排序 .offset(0) // 起始页 .limit(10) // 限制条数 .fetchResults(); } }
@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的查询操作
public interface tCityRepository extends JpaRepository, QueryDslPredicateExecutor {}
这样的话单表动态查询就可以参考如下代码:
//查找出Id小于3,并且名称带有`shanghai`的记录. //动态条件 QTCity qtCity = QTCity.tCity; //SDL实体类 //该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对多表查询提供了一个很好地封装,看下面代码:
@Override public ListfindCityAndHotel(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"); //调用 Listresult = 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查询,下面代码是对上面的查询加上分页功能:
@Override public QueryResultsfindCityAndHotelPage(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); //调用查询 QueryResultsresult = 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是不同的对象。