• JPA+QueryDSL


    工作需要,接触到了QueryDSL,总结使用方法,参考了几个大佬的文章,对我帮助很大 参考文章1参考文章2参考文章3参考文章4感谢大佬们!!!

    使用场景

    1. QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。
    2. Querydsl可以通过一组通用的查询API为用户构建出适合不同类型ORM框架或者是SQL的查询语句,也就是说QueryDSL是基于各种ORM框架以及SQL之上的一个通用的查询框架。
    3. 借助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/

    一些概念

    1. EntityManager:
      在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。
      EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
    2. @PersistenceContext
      Persistence context是由一组受托管的实体对象实例所构成的集合。它受entity manager 的管理。Entity manager追踪persistence context中所有对象的修改和更新情况,并根据指定的flush模式(本章稍后会做讨论)将这些修改保存到数据库中。一旦persistence context被关闭,所有实体对象实例都会脱离EntityManager而成为非托管对象。对象一旦从persistence context中脱离,就不再受entity manager管理了,任何对此对象的状态变更也将不会被同步到数据库。

    使用方法

    pom文件的导入

    	
        <dependency>
            <groupId>com.querydslgroupId>
            <artifactId>querydsl-jpaartifactId>
        dependency>
        
        <dependency>
            <groupId>com.querydslgroupId>
            <artifactId>querydsl-aptartifactId>
            <scope>providedscope>
        dependency>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
    
            
            <plugin>
                <groupId>com.mysema.mavengroupId>
                <artifactId>apt-maven-pluginartifactId>
                <version>1.1.3version>
                <executions>
                    <execution>
                        <phase>generate-sourcesphase>
                        <goals>
                            <goal>processgoal>
                        goals>
                        <configuration>
                            <outputDirectory>target/generated-sourcesoutputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessorprocessor>
                        configuration>
                    execution>
                executions>
            plugin>
    
        plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    下面是两个大佬的文章里给出的例子,根据例子学习非常快,再次感谢大佬们!!!

    例1
    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;
    
        /** 角色id */
        private Long roleId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. 创建repository接口
    @Repository
    public interface UserRepository extends JpaRepository<User,Long>, QuerydslPredicateExecutor<User> {}
    
    • 1
    • 2
    1. 生成Q类 (举例: user实体生成实体后会有一个QUser的类,该框架操作的是QUser类,而不是直接的是实体类)

    IDEA 的Maven面板,项目生命周期里重新编译项目,会自动生成Q实体类

    1. 创建service接口实现类
    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserRepository userRepository;
    
        @PersistenceContext
        private EntityManager entityManager;
    
        /**
         * 添加用户
         *
         * @return user
         */
        @Override
        public User saveUser() {
            User address = new User().username("admin").password("123456").age("12").sex("男").address("山西省");
            return userRepository.save(address);
        }
    
        /**
         * 通过id查询
         *
         * @param id 数据id
         * @return Optional
         */
        @Override
        public Optional<User> findById(Long id) {
            QUser qUser = QUser.user;
            BooleanExpression eq = qUser.id.eq(id);
            return userRepository.findOne(eq);
        }
    
        /**
         * 查询所有
         *
         * @return List
         */
        @Override
        public List<RoUser> findAll() {
            JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
            QUser qUser = QUser.user;
            QRole qRole = QRole.role;
    
            QBean<RoUser> 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();
        }
    
        /**
         * 动态查询 + 子查询 + 分页 + 排序
         *
         * @return List
         */
        @Override
        public QueryResults<RoUser> findAll(String username, Long roleId) {
            JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
            QUser qUser = QUser.user;
            QRole qRole = QRole.role;
    
            QBean<RoUser> bean = Projections.bean(
                    RoUser.class,//返回自定义实体的类型
                    qUser.id,
                    qUser.username,
                    qUser.password,
                    qUser.sex,
                    qUser.age,
                    qUser.address,
                    qRole.roleName,
                    qRole.remark
            );
            // 定义返回类型
            JPAQuery<RoUser> 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    例2
    一. 实体类
    1. 城市类
    @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;  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 旅馆类
    @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主键  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    二. 单表动态分页查询

    Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作

    public interface tCityRepository extends JpaRepository<TCity, Integer>, QueryDslPredicateExecutor<TCity> {}  
    
    • 1

    这样的话单表动态查询就可以参考如下代码:

    //查找出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<TCity> tCityPage = tCityRepository.findAll(predicate,pageRequest);  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    三. 多表动态查询

    QueryDSL对多表查询提供了一个很好地封装,看下面代码:

    /** 
    * 关联查询示例,查询出城市和对应的旅店 
    * @param predicate 查询条件 
    * @return 查询实体 
    */  
    @Override  
    public List<Tuple> findCityAndHotel(Predicate predicate) {  
    	JPAQueryFactory queryFactory = new JPAQueryFactory(em);  
    	JPAQuery<Tuple> 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();  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    城市表左连接旅店表,当该旅店属于这个城市时查询出两者的详细字段,存放到一个Tuple的多元组中.相比原生sql,简单清晰了很多.
    那么该怎么调用这个方法呢?

    @Test  
        public void findByLeftJoin(){  
            QTCity qtCity = QTCity.tCity;  
            QTHotel qtHotel = QTHotel.tHotel;  
            //查询条件  
            Predicate predicate = qtCity.name.like("shanghai");  
            //调用  
            List<Tuple> 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);  
        }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这样做的话避免了返回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 '!'  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    四 多表动态分页查询

    分页查询对于queryDSL无论什么样的sql只需要写一遍,会自动转换为相应的count查询,下面代码是对上面的查询加上分页功能:

    @Override  
    public QueryResults<Tuple> findCityAndHotelPage(Predicate predicate,Pageable pageable) {  
       JPAQueryFactory queryFactory = new JPAQueryFactory(em);  
       JPAQuery<Tuple> 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();  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    和上面不同之处在于这里使用了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<Tuple> 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());  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    生成的原生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 '!'  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    生成的原生查询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 ?  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    查看打印,可以发现对应的city也都是同一个对象,hotel是不同的对象。

  • 相关阅读:
    Haproxy集群
    高等数学(第七版)同济大学 习题1-10 个人解答
    # CSP-J 2023 第一轮试题
    Winform和WPF数据绑定区别小结
    [2023.09.27]: Yew SSR开发中的服务器端与客户端共同维护同一状态的实践
    1583 - Digit Generator (UVA)
    25 WEB漏洞-XSS跨站之原理分类及攻击手法
    111个Python数据分析实战项目,代码已跑通,数据可下载
    计算机网络期末复习(知识点)
    代码随想录58——单调栈:739每日温度、 496下一个更大元素I
  • 原文地址:https://blog.csdn.net/weixin_44449518/article/details/127440399