SpringDataJPA

2022/2/24 SpringdataJpamysql

# ORM思想

传统JDBC操作

  1. 实体类

    image-20210604084315687

  2. 数据表

    image-20210604084338714

  3. 对表增删改查

    image-20210604084423691

操作繁琐、占位符赋值麻烦

ORM

主要目的;操作实体类就相当于操作数据库表

建立两个映射关系:

  1. 实体类和表的映射关系
  2. 实体类中属性和表中字段的映射关系

不再重点关注:sql语句

实现了ORM思想的框架:mybatis,hibernate

# Hibernate与JPA概述

JAP规范,实现JPA,内部是由接口和抽象类组成

JPA(Java持久化API)是一种Java应用程序接口规范,描述java应用中关系数据的管理,充当面向对象的领域模型和关系数据库系统之间的桥梁。

Application code ---> JPA ---->实现JPA规范的框架(比如hibernate、spring data jpa) ----> 数据库

graph LR

A[Application code] --> |jpa规范| B(hibernate)
B -->|jdbc| C(mysql数据库)
1
2
3
4
  1. 容器及特性的支持
  2. 简单方便
  3. 查询能力
  4. 高级特性

JDBC规范

image-20210604091048241

JPA规范

image-20210604091238226

JPA规范提供接口,具体实现由具体框架来实现

Hibernate框架

Hibernate是一个开源的对象关系映射框架,

​ 它对JDBC进行了非常轻量级的对象封装,

​ 它将POJO与数据库表建立映射关系,是一个全自动的ORM框架

Hibernate与JPA关系

image-20210604091846231

# JPA的基本操作

对客户表的基本增删改查

搭建环境

  1. 创建表

     /*创建客户表*/
        CREATE TABLE cst_customer (
          cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
          cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
          cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
          cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
          cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
          cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
          cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
          PRIMARY KEY (`cust_id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  2. 创建maven项目导入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>Jpa-study</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <!-- junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <!-- hibernate对jpa的支持包 -->
            <!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core -->
            <dependency>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>6.0.0.Alpha7</version>
            </dependency>
    
    
            <!-- c3p0 -->
            <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.2</version>
            </dependency>
    
    
            <!-- log日志 -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.12.1</version>
            </dependency>
    
            <!--mysql连接驱动-->
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.49</version>
            </dependency>
    
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
    </project>
    
    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
  3. 创建persistence.xml配置

    在java工程的src路径下创建一个名为META-INF的文件夹,在此文件夹下创建一个名为persistence.xml的配置文件

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <persistence xmlns="https://jakarta.ee/xml/ns/persistence"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
                 version="3.0">
        <!--需要配置persistence-unit节点
            持久化单元:
                name:持久化单元名称
                transaction-type:事务管理的方式
                    JTA:分布式事务管理
                    RESOURCE_LOCAL:本地事务管理
        -->
        <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
            <!--jpa的实现方式-->
            <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
            <!--可选配置:配置jpa实现方式的配置信息-->
            <properties>
                <!-- 数据库信息
                    用户名,javax.persistence.jdbc.user
                    密码,  javax.persistence.jdbc.password
                    驱动,  javax.persistence.jdbc.driver
                    数据库地址   javax.persistence.jdbc.url
                -->
                <property name="javax.persistence.jdbc.user" value="root"/>
                <property name="javax.persistence.jdbc.password" value="DYJ0129.."/>
                <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa_study?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
    
                <!--配置jpa实现方(hibernate)的配置信息
                    显示sql           :   false|true
                    自动创建数据库表    :  hibernate.hbm2ddl.auto
                            create      : 程序运行时创建数据库表(如果有表,先删除表再创建)
                            update      :程序运行时创建表(如果有表,不会创建表)
                            none        :不会创建表
                -->
                <property name="hibernate.show_sql" value="true" />
                <property name="hibernate.hbm2ddl.auto" value="update" />
            </properties>
        </persistence-unit>
    </persistence>
    
    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
  4. 创建实体类

    package com.du;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.*;
    
    /**
     * 客户的实体类
     *      配置映射关系
     *
     *
     *   1.实体类和表的映射关系
     *      @Entity:声明实体类
     *      @Table : 配置实体类和表的映射关系
     *          name : 配置数据库表的名称
     *   2.实体类中属性和表中字段的映射关系
     *
     *
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    
    @Entity
    @Table(name = "cst_customer")
    public class Customer {
    
        /**
         * @Id:声明主键的配置
         * @GeneratedValue:配置主键的生成策略
         *      strategy
         *          GenerationType.IDENTITY :自增,mysql
         *                 * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
         *          GenerationType.SEQUENCE : 序列,oracle
         *                  * 底层数据库必须支持序列
         *          GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
         *          GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
         * @Column:配置属性和字段的映射关系
         *      name:数据库表中字段的名称
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "cust_id")
        private Long custId; //客户的主键
    
        @Column(name = "cust_name")
        private String custName;//客户名称
    
        @Column(name="cust_source")
        private String custSource;//客户来源
    
        @Column(name="cust_level")
        private String custLevel;//客户级别
    
        @Column(name="cust_industry")
        private String custIndustry;//客户所属行业
    
        @Column(name="cust_phone")
        private String custPhone;//客户的联系方式
    
        @Column(name="cust_address")
        private String custAddress;//客户地址
    }
    
    
    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
  5. 测试增删改查

    import com.du.entity.Customer;
    import com.du.utils.JpaUtils;
    import org.junit.Test;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityTransaction;
    
    public class TestCRUD {
    
        @Test
        public void testSave() {
            /*// 1.加载配置文件创建工厂(实体管理器工厂)对象
            EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
            // 2.通过实体管理器工厂获取实体管理器
            EntityManager em = factory.createEntityManager();
            // 3.获取事务对象,开启事务*/
            EntityManager em = JpaUtils.getEntityManager();
            EntityTransaction tx = em.getTransaction();
            tx.begin();// 开启事务
            // 4.完成增删改查操作
            Customer customer = new Customer();
            customer.setCustName("字节跳动");
            customer.setCustIndustry("互联网");
            // 保存
            em.persist(customer);
            // 5.提交事务
            tx.commit();
            // 6.释放资源
            em.close();
            //factory.close();
        }
    
        /**
         * 根据id查询客户
         * 使用find方法查询
         * 1、查询的对象就是当前客户对象本身
         * 2、在调用find方法的时候,就会发送sql语句查询数据库
         * 立即加载
         */
        @Test
        public void findById() {
            EntityManager manager = JpaUtils.getEntityManager();
            EntityTransaction transaction = manager.getTransaction();
            transaction.begin();
            Customer customer = manager.find(Customer.class, 1l);
            System.out.println(customer);
            transaction.commit();
            manager.close();
        }
    
        /**
         * 根据id查询客户
         * getReference方法
         * 1、获取的对象是一个动态对象
         * 2、调用getReference方法不会立即发送sql语句查询数据库
         * 当调用查询结果对象的时候,才会发送查询的sql语句,什么时候调用什么时候查询
         * 延迟加载(懒加载)
         * 得到的是一个动态代理对象
         * 什么时候用,什么时候才会查询
         */
        @Test
        public void testReference() {
            EntityManager manager = JpaUtils.getEntityManager();
            EntityTransaction transaction = manager.getTransaction();
            transaction.begin();
            Customer customer = manager.getReference(Customer.class, 1l);
            System.out.println(customer);
            transaction.commit();
            manager.close();
        }
    
        /**
         * 删除客户
         *   remove(object)
         */
        @Test
        public void testRemove() {
            EntityManager manager = JpaUtils.getEntityManager();
            EntityTransaction transaction = manager.getTransaction();
            transaction.begin();
    
            //根据id查询客户
            Customer customer = manager.find(Customer.class, 2l);
            //ii 调用remove方法完成删除操作
            manager.remove(customer);
            transaction.commit();
            manager.close();
        }
    
        /**
         * 更新客户
         *  merge(object)
         */
        @Test
        public void testUpdate() {
            EntityManager manager = JpaUtils.getEntityManager();
            EntityTransaction transaction = manager.getTransaction();
            transaction.begin();
            // 根据id查询客户
            Customer customer = manager.find(Customer.class, 3l);
            customer.setCustName("腾讯");
            //ii 更新客户
            manager.merge(customer);
            transaction.commit();
            manager.close();
        }
    }
    
    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

JPA主键策略

JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。

  • IDENTITY : 主键由数据库自动生成(主要是自动增长型)
  • SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
  • AUTO:主键由程序控制
  • TABLE:使用一个特定的数据库表格来保存主键

JPA的API介绍

JPA的操作步骤

  1. 加载配置文件创建实体管理工厂

    Persistence:静态方法(根据持久化单元名称创建管理器工厂)

    ​ createEntityManagerFactory(持久化单元名称)

    作用:创建实体管理器工厂

  2. 根据实体管理工厂,创建实体管理器

    EntityManagerFactory:获取EntityManager对象

    方法:createEntityManager

    内部维护了很多内容

    • 内部维护了数据库信息
    • 维护了缓存信息
    • 维护了所有的实体管理器对象
    • 在创建EntityManagerFactory的过程中会根据配置创建数据库表

    EntityManagerFactory的创建工程比较浪费资源

    特点:线程安全的对象

    ​ 多个线程访问同一个EntityManagerFactory不会有线程安全问题

    如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题

    ==创建一个公共的EntityManagerFactory对象==

    静态代码块的形式创建EntityManagerFactory

    package com.du.utils;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    /**
     * 解决实体管理器工厂的浪费浪费资源和耗时问题
     *      通过静态代码块的形式,当程序第一次访问就创建factory
     *      第一次访问getEntityManager,通过静态代码块创建EntityManagerFactory对象,再调用方法创建一个EntityManager对象
     *      第二次访问直接通过一个创建好的EntityManagerFactory对象,创建EntityManager对象
     */
    public class JpaUtils {
    
        private static EntityManagerFactory factory;
    
        static {
            factory = Persistence.createEntityManagerFactory("myJpa");
        }
    
        /**
         *  获取EntityManager对象
         */
        public static EntityManager getEntityManager(){
            return factory.createEntityManager();
        }
    
    }
    
    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
  3. 创建事务对象,开启事务

    EntityManager对象:实体类管理器

    ​ beginTransaction:创建事务对象,开启事务

    ​ presist:保存

    ​ merge:更新

    ​ remove:删除

    ​ find/getRefrence:根据id查询

    Transaction对象:事务

    ​ begin:开启事务管理

    ​ commit:提交事务

    ​ rollback:回滚

  4. 增删改查操作

  5. 提交事务

  6. 释放资源

jpa复杂查询

import com.du.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;

public class JpqlTest {

    /**
     * 查询全部
     * jqpl:from com.du.entity.Customer
     * sql:SELECT * FROM cst_customer
     */
    @Test
    public void findAll(){
        // 1、获取EntityManager对象
        EntityManager manager = JpaUtils.getEntityManager();
        // 2、开启事务
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        // 3、查询全部
        String jpql = "from com.du.entity.Customer";
        // 发送查询,创建查询对象
        Query query = manager.createQuery(jpql);
        // 4、返回结果集
        List list = query.getResultList();
        list.forEach(System.out::println);
        // 5、提交事务
        transaction.commit();
        // 6、关闭资源
        manager.close();
    }

    /**
     * 排序查询 ,asc正序查询,desc倒序查询
     * jqpl:from com.du.entity.Customer order by custId desc
     * sql:SELECT * FROM cst_customer order by custId desc
     */
    @Test
    public void testOrder(){
        // 1、获取EntityManager对象
        EntityManager manager = JpaUtils.getEntityManager();
        // 2、开启事务
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        // 3、排序查询,asc正序(默认),desc倒序
        String jpql = "from com.du.entity.Customer order by custId desc";
        // 发送查询,创建查询对象
        Query query = manager.createQuery(jpql);
        // 4、返回结果集
        List list = query.getResultList();
        list.forEach(System.out::println);
        // 5、提交事务
        transaction.commit();
        // 6、关闭资源
        manager.close();
    }

    /**
     * 统计总数
     * jqpl:SELECT count(custId) com.du.entity.Customer
     * sql:SELECT count(custId) FROM cst_customer
     */
    @Test
    public void testCount(){
        // 1、获取EntityManager对象
        EntityManager manager = JpaUtils.getEntityManager();
        // 2、开启事务
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        // 3、查询全部
        String jpql = "select count(custId) from com.du.entity.Customer";
        // 发送查询,创建查询对象
        Query query = manager.createQuery(jpql);
        // 4、返回结果集
        List list = query.getResultList();
        list.forEach(System.out::println);
        // 5、提交事务
        transaction.commit();
        // 6、关闭资源
        manager.close();
    }

    /**
     * 分页查询
     *      sql:select * from cst_customer limit 0,2
     *      jqpl : from Customer
     */
    @Test
    public void testPaged() {
        //1.获取entityManager对象
        EntityManager em = JpaUtils.getEntityManager();
        //2.开启事务
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //3.查询全部
        //i.根据jpql语句创建Query查询对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        //ii.对参数赋值 -- 分页参数
        //起始索引
        query.setFirstResult(0);
        //每页查询的条数
        query.setMaxResults(2);

        //iii.发送查询,并封装结果
        /**
         * getResultList : 直接将查询结果封装为list集合
         * getSingleResult : 得到唯一的结果集
         */
        List list = query.getResultList();

        list.forEach(System.out::println);

        //4.提交事务
        tx.commit();
        //5.释放资源
        em.close();
    }

    /**
     * 模糊查询
     *     案例:查询客户名称以‘传智播客’开头的客户
     *          sql:SELECT * FROM cst_customer WHERE cust_name LIKE  ?
     *          jpql : from Customer where custName like ?
     */
    @Test
    public void fuzzyQuery(){
        EntityManager manager = JpaUtils.getEntityManager();
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();

        // 查询
        String jpql = "from Customer where custName like ?";
        Query query = manager.createQuery(jpql);
        // 第一个参数占位符索引位置,第二个参数你要填入的参数
       // query.setParameter(1,"阿里");
        // 获得返回结果集
        List list = query.getResultList();
        list.forEach(System.out::println);

        transaction.commit();
        manager.close();
    }
}
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

# SpringData JPA

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!

Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。

实体映射关系

==实体:==

@Entity 对实体注释。任何Hibernate映射对象都要有这个注释

@Table 声明此对象映射到数据库的数据表,通过它可以为实体指定表(talbe),目录(Catalog)和schema的名字。该注释不是必须的,如果没有则系统使用默认值(实体的短类名)。

​ 属性:

  • name,用于指明数据库的表名
  • catalogschema 用于设置表所属的数据库目录或模式,通常为数据库名

@Version 该注释可用于在实体Bean中添加乐观锁支持

==字段:==

@ID 声明该属性是主键,一个实体类中必须存在

@GeneratedValue 在主键上使用,设置主键的增加策略

​ 属性:strategy有四个可选

  • TABLE,使用表保存ID值
  • IDENTITY,ID顺序增加,需要数据库支持自增(mysql)
  • SEQUENCR,ID按序列增加,需要数据库支持序列(Oracle)
  • AUTO,根据使用不同的数据库自动选择策略

@GenericGenerator,实现自定义主键生成策略

​ 属性:

  • name 属性指定生成器名称。

  • strategy 属性指定具体生成器的类名( 使用非默认策略的时候,需要使用全类名 )。

  • parameters 得到strategy指定的具体生成器所用到的参数。

    //hibernate-5.0.12.Final版本的默认工厂中有14种生成策略,具体可见org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory
    public DefaultIdentifierGeneratorFactory() {
        register( "uuid2", UUIDGenerator.class );
        register( "guid", GUIDGenerator.class );            // can be done with UUIDGenerator + strategy
        register( "uuid", UUIDHexGenerator.class );         // "deprecated" for new use
        register( "uuid.hex", UUIDHexGenerator.class );     // uuid.hex is deprecated
        register( "assigned", Assigned.class );
        register( "identity", IdentityGenerator.class );
        register( "select", SelectGenerator.class );
        register( "sequence", SequenceStyleGenerator.class );
        register( "seqhilo", SequenceHiLoGenerator.class );
        register( "increment", IncrementGenerator.class );
        register( "foreign", ForeignGenerator.class );
        register( "sequence-identity", SequenceIdentityGenerator.class );
        register( "enhanced-sequence", SequenceStyleGenerator.class );
        register( "enhanced-table", TableGenerator.class );
    }
    
    public void register(String strategy, Class generatorClass) {
        LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() );
        final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass );
        if ( previous != null ) {
            LOG.debugf( "    - overriding [%s]", previous.getName() );
        }
    }
    
    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

    使用自带的ID生成策略

    @Entity
    @Table(name = "test_person")
    public class PersonPO {
    
        @Id
        @GenericGenerator(name="personGenerator", strategy="uuid")
        @GeneratedValue(generator="personGenerator")
        private String pid;
    
        private String name;
    
    	// setter and getters...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

@Column

​ 属性:

  • name:映射的列名。如:映射tbl_user表的name列,可以在name属性的上面或getName方法上面加入;
  • unique:是否唯一;
  • nullable:是否允许为空;
  • length:对于字符型列,length属性指定列的最大字符长度
  • insertable:是否允许插入;
  • updatetable:是否允许更新
  • columnDefinition:定义建表时创建此列的DDL
  • secondaryTable:从表名,如果此列不建在主表上(默认是主表),该属性定义该列所在从表的名字

Repository接口

image-20210604202034319

Spring Data Jpa中一共提供了:

Repository

  • 支持方法命名查询 (提供了findBy + 属性方法 )

  • @Query

    HQL: nativeQuery 默认false

    SQL: nativeQuery 默认true

    更新的时候,需要配合@Modifying使用

CurdRepository

继承了Repository 主要提供了对数据的增删改查

PagingAndSortRepository

继承了CrudRepository 提供了对数据的分页和排序,缺点是只能对所有的数据进行分页或者排序,不能做条件判断

JpaRepository

继承了PagingAndSortRepository,开发中经常使用的接口,主要继承了PagingAndSortRepository,对返回值类型做了适配

JpaSpecificationExecutor

提供多条件查询

Specification

多表动态查询

Springboot整合

  1. 导入依赖

    <!--mysql 连接驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    
    <!--jpa支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  2. 编写实体类

    package com.du.entity;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    import java.io.Serializable;
    
    @Getter
    @Setter
    @Entity
    @Table(name = "User")
    @JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"})
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
        @Column(name = "name", length = 10)
        private String name;
    
        @Column(name = "age", length = 3)
        private Integer age;
    }
    
    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
  3. 编写配置类

    spring:
        datasource:
            username: root
            password: root
            driver-class-name: com.mysql.jdbc.Driver
            url: jdbc:mysql://localhost:3306/jpa_study?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai
        jpa:
            generate-ddl: true
            hibernate:
                ddl-auto: update
            #配置方言
            database-platform: org.hibernate.dialect.MySQLDialect
        transaction:
            rollback-on-commit-failure: true
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  4. 编写接口

    package com.du.pepository;
    
    import com.du.entity.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface UserPepository extends JpaRepository<User, Integer> {
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
  5. service类

    package com.du.controller;
    
    import com.du.entity.User;
    import com.du.pepository.UserPepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController()
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        UserPepository userPepository;
    
        @GetMapping("/{id}")
        public User getUser(@PathVariable("id") Integer id){
            User user = userPepository.getById(id);
            return user;
        }
    
        @GetMapping("/add")
        public User insertUser(User user){
            User save = userPepository.save(user);
            return save;
        }
    
        @GetMapping("/all")
        public List<User> queryAll(){
            return userPepository.findAll();
        }
    
        @GetMapping("/delete/{id}")
        public String delete(@PathVariable("id") Integer id){
            userPepository.deleteById(id);
            return "删除成功";
        }
    
        @GetMapping("/count")
        public Long count(){
            return userPepository.count();
        }
    
        @GetMapping("/exists/{id}")
        public boolean exists(@PathVariable("id") Integer id){
            return userPepository.existsById(id);
        }
    
        @GetMapping("/page/{currPage}")
        public Page<User> page(@PathVariable("currPage") int currPage){
            return userPepository.findAll(PageRequest.of(currPage,2));
        }
    }
    
    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

# SpringData JPA源码分析

  1. 客户接口开始

  2. SpringData JPA创建动态代理对象 simpleJpaRepository

    image-20210605213631577

SpringData Jpa的运行过程和原理剖析

通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象(SimpleJpaRepository) SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CURD) 通过hibernate完成数据库操作(封装了jdbc)

# Spring Data JPA的查询方式

# 接口查询

  • 继承JpaRepository的方法列表

    image-20210615164042463

  • 继承JpaSpecificationExecutor方法列表

    image-20210615164131580

JPQL查询 jpql:jpa query language (jpa查询语言)

特点:

语法或关键字和sql语句类似

查询的是类和类中的属性

需要将JPQL语句配置到接口方法上

特有的查询:需要在dao接口上配置的方法 在新添加的方法上,使用注解的形式配置jpql查询语句 注解:@Query

查询

public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> {    
    //@Query 使用jpql的方式查询。
    @Query(value="from Customer")
    public List<Customer> findAllCustomer();
    
    //@Query 使用jpql的方式查询。?1代表参数的占位符,其中1对应方法中的参数索引
    @Query(value="from Customer where custName = ?1")
    public Customer findCustomer(String custName);
}
1
2
3
4
5
6
7
8
9

更新

此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。

@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName,Long custId); //测试方法需要增加事务支持:
1
2
3
/*
* springDataJpa中使用jpql完成 更新/删除操作
*   需要手动添加事务的支持
*   默认会执行结束之后,回滚事务
* */
@Test
@Transactional
@Rollback(false)
public void testUpdate(){
    customerDao.updateCustNameById(5L,"尚硅谷");
}
1
2
3
4
5
6
7
8
9
10
11

# SQL语句查询

  1. 特有的查询:需要在dao接口上配置方法

  2. 在新添加的方法上,使用注解的形式配置sql查询语句

  3. 注解 :@Query

    value : jpql语句 | sql语句

    nativeQuery : false(使用jpql查询) | true(使用本地查询 :sql查询)

/**
 * 使用sql的形式查询:
 *     查询全部的客户
 *  sql : select * from cst_customer;
 *  Query : 配置sql查询
 *      value : sql语句
 *      nativeQuery :查询方式
 *          true : sql查询
 *          false:jpql查询
 *
 */
//@Query(value = " select * from cst_customer" ,nativeQuery = true)
@Query(value="select * from cst_customer where cust_name like ?1",nativeQuery = true)
public List<Object [] > findSql(String name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法命名规则查询

  • 是对jpql查询,更加深入的一层封装
  • 我们只需要按照SpringDataJpa提供的方法名称规则来定义方法,不需要再去配置jpql语句,就能完成查询

规则

findBy开头: 代表查询,对象中属性的名称(首字母大写) 含义:根据属性名称进行查询

# 精准匹配

测试

/**
 * 方法名的约定:
 *      findBy : 查询
 *            对象中的属性名(首字母大写) : 查询的条件
 *            CustName
 *            * 默认情况 : 使用 等于的方式查询
 *                  特殊的查询方式
 *
 *  findByCustName   --   根据客户名称查询
 *
 *  再springdataJpa的运行阶段
 *          会根据方法名称进行解析  findBy    from  xxx(实体类)
 *                                      属性名称      where  custName =
 *
 *      1.findBy  + 属性名称 (根据属性名称进行完成匹配的查询=)
 *      2.findBy  + 属性名称 + “查询方式(Like | isnull)”
 *          findByCustNameLike
 *      3.多条件查询
 *          findBy + 属性名 + “查询方式”   + “多条件的连接符(and|or)”  + 属性名 + “查询方式”
 */
public Customer findByCustName(String custName);
  //测试方法命名规则的查询
  @Test
  public void testNaming(){
    Customer customer = customerDao.findByCustName("博学谷");
    System.out.println(customer);
  }
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

# 模糊查询

findBy + 属性名称 + “查询方式(Like | isnull)”

测试

public List<Customer> findByCustNameLike(String custName);
1

# 多条件查询

findBy + 属性名 + “查询方式” + “多条件的连接符

测试

//使用客户名称模糊匹配和客户所属行业精准匹配的查询
public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
1
2

# Specification动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

JpaSpecificationExecutor方法列表

T findOne(Specification spec); //查询单个对象

List findAll(Specification spec); //查询列表

查询全部,分页

pageable:分页参数

返回值:分页pageBean(page:是springdatajpa提供的)

Page findAll(Specification spec, Pageable pageable);

查询列表

Sort:排序参数

List findAll(Specification spec, Sort sort);	1
    

long count(Specification spec);//统计查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Specification:查询条件

自定义Specification类

//root:查询的根对象(查询的任何属性都可以从根对象中获取)
//CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用)
//CriteriaBuilder:查询的构造器,封装了很多的查询条件
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件
1
2
3
4

# 单个条件查询

/**
     * 根据条件,查询单个对象
     *
     */
@Test
public void testSpec() {
    //匿名内部类
    /**
         * 自定义查询条件
         *      1.实现Specification接口(提供泛型:查询的对象类型)
         *      2.实现toPredicate方法(构造查询条件)
         *      3.需要借助方法参数中的两个参数(
         *          root:获取需要查询的对象属性
         *          CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
         *       )
         *  案例:根据客户名称查询,查询客户名为传智播客的客户
         *          查询条件
         *              1.查询方式
         *                  cb对象
         *              2.比较的属性名称
         *                  root对象
         *
         */
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //1.获取比较的属性
            Path<Object> custName = root.get("custId");
            //2.构造查询条件  :    select * from cst_customer where cust_name = '传智播客'
            /**
                 * 第一个参数:需要比较的属性(path对象)
                 * 第二个参数:当前需要比较的取值
                 */
            Predicate predicate = cb.equal(custName, "传智播客");//进行精准的匹配  (比较的属性,比较的属性的取值)
            return predicate;
        }
    };
    Customer customer = customerDao.findOne(spec);
    System.out.println(customer);
}
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

# 多个条件查询

/**
     * 多条件查询
     *      案例:根据客户名(传智播客)和客户所属行业查询(it教育)
     *
     */
@Test
public void testSpec1() {
    /**
         *  root:获取属性
         *      客户名
         *      所属行业
         *  cb:构造查询
         *      1.构造客户名的精准匹配查询
         *      2.构造所属行业的精准匹配查询
         *      3.将以上两个查询联系起来
         */
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Path<Object> custName = root.get("custName");//客户名
            Path<Object> custIndustry = root.get("custIndustry");//所属行业

            //构造查询
            //1.构造客户名的精准匹配查询
            Predicate p1 = cb.equal(custName, "传智播客");//第一个参数,path(属性),第二个参数,属性的取值
            //2..构造所属行业的精准匹配查询
            Predicate p2 = cb.equal(custIndustry, "it教育");
            //3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系)
            Predicate and = cb.and(p1, p2);//以与的形式拼接多个查询条件
            // cb.or();//以或的形式拼接多个查询条件
            return and;
        }
    };
    Customer customer = customerDao.findOne(spec);
    System.out.println(customer);
}
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

# 模糊查询

/**
     * 案例:完成根据客户名称的模糊匹配,返回客户列表
     *      客户名称以 ’传智播客‘ 开头
     *
     * equal :直接的到path对象(属性),然后进行比较即可
     * gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较
     *      指定参数类型:path.as(类型的字节码对象)
     */
@Test
public void testSpec3() {
    //构造查询条件
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊匹配
            Predicate like = cb.like(custName.as(String.class), "传智播客%");
            return like;
        }
    };
    //        List<Customer> list = customerDao.findAll(spec);
    //        for (Customer customer : list) {
    //            System.out.println(customer);
    //        }
    //添加排序
    //创建排序对象,需要调用构造方法实例化sort对象
    //第一个参数:排序的顺序(倒序,正序)
    //   Sort.Direction.DESC:倒序
    //   Sort.Direction.ASC : 升序
    //第二个参数:排序的属性名称
    Sort sort = new Sort(Sort.Direction.DESC,"custId");
    List<Customer> list = customerDao.findAll(spec, sort);
    for (Customer customer : list) {
        System.out.println(customer);
    }
}
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

# 分页查询

/**
     * 分页查询
     *      Specification: 查询条件
     *      Pageable:分页参数
     *          分页参数:查询的页码,每页查询的条数
     *          findAll(Specification,Pageable):带有条件的分页
     *          findAll(Pageable):没有条件的分页
     *  返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数)
     */
@Test
public void testSpec4() {

    Specification spec = null;
    //PageRequest对象是Pageable接口的实现类
    /**
         * 创建PageRequest的过程中,需要调用他的构造方法传入两个参数
         *      第一个参数:当前查询的页数(从0开始)
         *      第二个参数:每页查询的数量
         */
    Pageable pageable = new PageRequest(0,2);
    //分页查询
    Page<Customer> page = customerDao.findAll(null, pageable);
    System.out.println(page.getContent()); //得到数据集合列表
    System.out.println(page.getTotalElements());//得到总条数
    System.out.println(page.getTotalPages());//得到总页数
}
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

# 多表之间的关系和操作

表关系

  • 一对一

  • 一对多:

    ​ 一的一方:主表

    ​ 多的一放:从表

    ​ 外键:需要在从表上新建一列作为外键,他的取值来源于主表的主键

多对多

​ 中间表:中间表中最少应该由两个字段组成,这两个字段作为外键指向两张表的主键,又组成了联合主键。

实体类中的关系

  • 包含关系:可以通过实体类中的包含关系描述表关系

  • 继承关系

分析步骤

  1. 明确表关系
  2. 确定表关系(描述 外键 | 中间表)
  3. 编写实体类,在实体类中描述表关系(包含关系)
  4. 配置映射关系

# 多表操作

# 一对多操作

	案例:客户和联系人的案例(一对多关系)
		客户:一家公司
		联系人:这家公司的员工
	
		一个客户可以具有多个联系人
		一个联系人从属于一家公司
		
	分析步骤
		1.明确表关系
			一对多关系
		2.确定表关系(描述 外键|中间表)
			主表:客户表
			从表:联系人表
				* 再从表上添加外键
		3.编写实体类,再实体类中描述表关系(包含关系)
			客户:再客户的实体类中包含一个联系人的集合
			联系人:在联系人的实体类中包含一个客户的对象
		4.配置映射关系
			* 使用jpa注解配置一对多映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

实体类

Customer.java

/*
* 1.实体类和表的映射关系
*   @Entity
*   @Table
* 2.类中属性和表中字段的映射关系
*   @Id
*   @GeneratedValue
*   @Column
*
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "cst_customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="cust_id")
    private Long custId;
    @Column(name="cust_address")
    private String custAddress;
    @Column(name="cust_industry")
    private String custIndustry;
    @Column(name="cust_level")
    private String custLevel;
    @Column(name="cust_name")
    private String custName;
    @Column(name="cust_phone")
    private String custPhone;
    @Column(name="cust_source")
    private String custSource;

    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     *
     */

//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称\
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         : 所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载

      */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>();
}
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

Linkman.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "cst_linkman")
public class LinkMan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId; //联系人编号(主键)
    @Column(name = "lkm_name")
    private String lkmName;//联系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender;//联系人性别
    @Column(name = "lkm_phone")
    private String lkmPhone;//联系人办公电话
    @Column(name = "lkm_mobile")
    private String lkmMobile;//联系人手机
    @Column(name = "lkm_email")
    private String lkmEmail;//联系人邮箱
    @Column(name = "lkm_position")
    private String lkmPosition;//联系人职位
    @Column(name = "lkm_memo")
    private String lkmMemo;//联系人备注

    /**
     * 配置联系人到客户的多对一关系
     *     使用注解的形式配置多对一关系
     *      1.配置表关系
     *          @ManyToOne : 配置多对一关系
     *              targetEntity:对方的实体类字节码
     *      2.配置外键(中间表)
     *
     * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     *
     */
    @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;
}
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

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     *  效果:客户和联系人作为独立的数据保存到数据库中
     *      联系人的外键为空
     *  原因?
     *      实体类中没有配置关系
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");

        /**
         * 配置了客户到联系人的关系
         *      从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
         * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
         */
        customer.getLinkMans().add(linkMan);


        customerDao.save(customer);
        linkManDao.save(linkMan);
    }


    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd1() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");

        /**
         * 配置联系人到客户的关系(多对一)
         *    只发送了两条insert语句
         * 由于配置了联系人到客户的映射关系(多对一)
         *
         *
         */
        linkMan.setCustomer(customer);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

    /**
     * 会有一条多余的update语句
     *      * 由于一的一方可以维护外键:会发送update语句
     *      * 解决此问题:只需要在一的一方放弃维护权即可
     *
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd2() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");


        linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
        customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

    /**
     * 级联添加:保存一个客户的同时,保存客户的所有联系人
     *      需要在操作主体的实体类上,配置casacde属性
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeAdd() {
        Customer customer = new Customer();
        customer.setCustName("百度1");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李1");

        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);

        customerDao.save(customer);
    }


    /**
     * 级联删除:
     *      删除1号客户的同时,删除1号客户的所有联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeRemove() {
        //1.查询1号客户
        Customer customer = customerDao.findOne(1l);
        //2.删除1号客户
        customerDao.delete(customer);
    }
}
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
122
123
124
125

级联

操作一个对象的同时操作他的关联对象

级联操作:

  1. 需要区分操作主体

  2. 需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)

  3. cascade级联

级联添加

案例:当我保存一个客户的同时保存联系人

级联删除

案例:但我删除一个客户的同时删除此客户所有的联系人

# 多对多操作

案例:用户和角色

1.明确表关系
	多对多关系
2.确定表关系(描述 外键|中间表)
	中间间表
3.编写实体类,再实体类中描述表关系(包含关系)
	用户:包含角色的集合
	角色:包含用户的集合
4.配置映射关系
1
2
3
4
5
6
7
8

实体类

User.java

@Entity
@Table(name = "sys_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="user_id")
    private Long userId;
    @Column(name="user_name")
    private String userName;
    @Column(name="age")
    private Integer age;

    /**
     * 配置用户到角色的多对多关系
     *      配置多对多的映射关系
     *          1.声明表关系的配置
     *              @ManyToMany(targetEntity = Role.class)  //多对多
     *                  targetEntity:代表对方的实体类字节码
     *          2.配置中间表(包含两个外键)
     *                @JoinTable
     *                  name : 中间表的名称
     *                  joinColumns:配置当前对象在中间表的外键
     *                      @JoinColumn的数组
     *                          name:外键名
     *                          referencedColumnName:参照的主表的主键名
     *                  inverseJoinColumns:配置对方对象在中间表的外键
     */
    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role",
            //joinColumns,当前对象在中间表中的外键
            joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
            //inverseJoinColumns,对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<>();

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}
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

Role.java

@Entity
@Table(name = "sys_role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;

    //配置多对多
    @ManyToMany(mappedBy = "roles")  //配置多表关系(对方配置映射关系的属性)
    private Set<User> users = new HashSet<>();

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}
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

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {

    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    /*
    * 保存一个用户,保存一个角色
    *
    * 多对多放弃维护权,被动的一方放弃
    * */
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd(){
        User user = new User();
        user.setUserName("小刘");

        Role role = new Role();
        role.setRoleName("程序员");

        //配置用户到角色关系,可以对中间表中的数据进行维护
        user.getRoles().add(role);

        userDao.save(user);
        roleDao.save(role);
    }
}
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

# 多表的查询

  1. 对象导航查询

    查询一个对象的同时,通过此对象查询他的关联对象

    案例:一个客户对应多个联系人

    • 从一方查询多方

    默认:使用延迟加载

    • 从多方查询一方

      默认:使用立即加载

实体类

Customer.java

/**
 * 1.实体类和表的映射关系
 *      @Eitity
 *      @Table
 * 2.类中属性和表中字段的映射关系
 *      @Id
 *      @GeneratedValue
 *      @Column
 */
@Entity
@Table(name="cst_customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="cust_id")
    private Long custId;
    @Column(name="cust_address")
    private String custAddress;
    @Column(name="cust_industry")
    private String custIndustry;
    @Column(name="cust_level")
    private String custLevel;
    @Column(name="cust_name")
    private String custName;
    @Column(name="cust_phone")
    private String custPhone;
    @Column(name="cust_source")
    private String custSource;

    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     *
     */

    //    @OneToMany(targetEntity = LinkMan.class)
    //    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称\
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         : 所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载

      */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>();
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

LinkMan.java

@Entity
@Table(name = "cst_linkman")
public class LinkMan {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId; //联系人编号(主键)
    @Column(name = "lkm_name")
    private String lkmName;//联系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender;//联系人性别
    @Column(name = "lkm_phone")
    private String lkmPhone;//联系人办公电话
    @Column(name = "lkm_mobile")
    private String lkmMobile;//联系人手机
    @Column(name = "lkm_email")
    private String lkmEmail;//联系人邮箱
    @Column(name = "lkm_position")
    private String lkmPosition;//联系人职位
    @Column(name = "lkm_memo")
    private String lkmMemo;//联系人备注

    /**
     * 配置联系人到客户的多对一关系
     *     使用注解的形式配置多对一关系
     *      1.配置表关系
     *          @ManyToOne : 配置多对一关系
     *              targetEntity:对方的实体类字节码
     *      2.配置外键(中间表)
     *
     * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     *
     */
    @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")

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

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ObjectQueryTest {
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    //could not initialize proxy - no Session
    //测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象)
    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery1() {
        //查询id为1的客户
        Customer customer = customerDao.getOne(1l);
        //对象导航查询,此客户下的所有联系人
        Set<LinkMan> linkMans = customer.getLinkMans();

        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    }

    /**
     * 对象导航查询:
     *      默认使用的是延迟加载的形式查询的
     *          调用get方法并不会立即发送查询,而是在使用关联对象的时候才会差和讯
     *      延迟加载!
     * 修改配置,将延迟加载改为立即加载
     *      fetch,需要配置到多表映射关系的注解上
     *
     */

    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery2() {
        //查询id为1的客户
        Customer customer = customerDao.findOne(1l);
        //对象导航查询,此客户下的所有联系人
        Set<LinkMan> linkMans = customer.getLinkMans();

        System.out.println(linkMans.size());
    }

    /**
     * 从联系人对象导航查询他的所属客户
     *      * 默认 : 立即加载
     *  延迟加载:
     *
     */
    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery3() {
        LinkMan linkMan = linkManDao.findOne(2l);
        //对象导航查询所属的客户
        Customer customer = linkMan.getCustomer();
        System.out.println(customer);
    }

}
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

# QueryDSL

# QueryDSL 简介

QueryDSL 是一个非常活跃的开源项目,目前在 Github 上的发布的 Release 版本已经多达 251 个版本,目前最新版是 4.2.1 ,并且由 Querydsl Google组 和 StackOverflow 两个团队提供支持。

QueryDSL 是一个框架,可用于构造静态类型的类似SQL的查询。可以通过诸如QueryDSL之类的 API 构造查询,而不是将查询编写为内联字符串或将其外部化为XML文件。

例如,与简单字符串相比,使用 API 的好处是

  • IDE中的代码完成
  • 几乎没有语法无效的查询
  • 可以安全地引用域类型和属性
  • 更好地重构域类型的更改

# QueryDSL使用

Repository层

继承QuerydslPredicateExecutor接口,实现分页查询

导入依赖

<!--QueryDSL依赖-->
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.4.0</version>
</dependency>

<!--自动生成查询实体依赖-->
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.4.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13

插件配置

添加这个插件是为了让程序自动生成 query type (查询实体,命名方式为:"Q"+对应实体名)。 上文引入的依赖中 querydsl-apt 即是为此插件服务的。

注:在使用过程中,如果遇到 query type 无法自动生成的情况,用maven更新一下项目即可解决(右键项目 -> Maven -> Update Folders)。

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

根据实体类自动生成Qxxx的类

image-20210607194757294

简单的增删改查

package com.du.pojo.service;

import com.du.pojo.entity.QUser;
import com.du.pojo.entity.User;
import com.du.pojo.repository.UserRepository;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    //JPA
    @Autowired
    private UserRepository userRepository;

    //查询工厂实体
    @Autowired
    private EntityManager entityManager;

    private JPAQueryFactory queryFactory;

    //实例化控制器完成后执行该方法实例化JPAQueryFactory
    @PostConstruct
    public void initFactory() {
        queryFactory = new JPAQueryFactory(entityManager);
    }


    @Override
    public List<User> findAll(User user) {
        // 使用QUser进行查询
        QUser qUser = QUser.user;
        // 定于获取条件
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        // 要查询的条件
        if (!StringUtils.isEmpty(user.getName())) {
            // 设置要查询的条件
            booleanBuilder.and(qUser.name.contains(user.getName()));
        }
        // queryFactory 是上方定义的工厂实体
        // select(生成的实体类的字段).from(生成实体类的名称).where(上方要查询的条件).fetch()进行查询
        return queryFactory.select(qUser)
                .from(qUser)
                .where(booleanBuilder)
                .fetch();
    }

    @Override
    public User findUserById(Integer id) {
        // 使用QUser查询
        QUser Quser = QUser.user;
        // 条件
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        booleanBuilder.and(Quser.id.eq(id));

        //使用queryFactory查询
        return queryFactory.select(Quser)
                .from(Quser)
                .where(booleanBuilder)
                .fetchOne();
    }

    @Override
    public long dropUserById(Integer id) {
        QUser qUser = QUser.user;
        return queryFactory.delete(qUser)
                .where(new BooleanBuilder().and(qUser.id.eq(id)))
                .execute();
    }

    @Override
    @Transactional
    public Long saveUser(User user) {
        userRepository.save(user);
        return 1L;
    }

    @Override
    public Long updateUser(User user) {
        QUser qUser = QUser.user;
        return queryFactory.update(qUser)
                .set(qUser.name, user.getName())
                .set(qUser.password, user.getPassword())
                .where(qUser.id.eq(user.getId()))
                .execute();
    }
    
    @Override
    public List<EmployeeEntity> page(Long page, Long size) {
        QEmployeeDo qEmployeeDo = QEmployeeDo.employeeDo;
        return jpaQueryFactory.selectFrom(qEmployeeDo)
                .where(qEmployeeDo.deleted.isFalse())
                .offset(page * size)
                .limit(size)
                .fetch()
                .stream()
                .map(EmployeeDo::toEntity)
                .collect(Collectors.toList());
    }
}
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

连接查询

@Test
    void findI() {
        QDepartmentDo qDepartmentDo = QDepartmentDo.departmentDo;
        QEmployeeDo qEmployeeDo = QEmployeeDo.employeeDo;
        List<Tuple> list = jpaQueryFactory.select(
                qEmployeeDo.id.as("员工id"),
                qEmployeeDo.employee_name.as("员工姓名"),
                qEmployeeDo.dept_name.as("员工部门"),
                qDepartmentDo.id.as("部门id"),
                qDepartmentDo.dept_name.as("部门名称"))
                .from(qDepartmentDo)
                .innerJoin(qEmployeeDo)
                .on(qDepartmentDo.dept_name.eq(qEmployeeDo.dept_name))
                .fetch();
        list.forEach(System.out::println);
    }

    @Test
    void find() {
        QDepartmentDo qDepartmentDo = QDepartmentDo.departmentDo;
        QEmployeeDo qEmployeeDo = QEmployeeDo.employeeDo;
        List<Tuple> list = jpaQueryFactory.select(
                qEmployeeDo.id.as("员工id"),
                qEmployeeDo.employee_name.as("员工姓名"),
                qEmployeeDo.dept_name.as("员工部门"),
                qDepartmentDo.id.as("部门id"),
                qDepartmentDo.dept_name.as("部门名称"))
                .from(qDepartmentDo)
                .rightJoin(qEmployeeDo)
                .on(qDepartmentDo.dept_name.eq(qEmployeeDo.dept_name))
                .fetch();
        list.forEach(System.out::println);
    }

    @Test
    void findL() {
        QDepartmentDo qDepartmentDo = QDepartmentDo.departmentDo;
        QEmployeeDo qEmployeeDo = QEmployeeDo.employeeDo;
        List<Tuple> list = jpaQueryFactory.select(
                qEmployeeDo.id.as("员工id"),
                qEmployeeDo.employee_name.as("员工姓名"),
                qEmployeeDo.dept_name.as("员工部门"),
                qDepartmentDo.id.as("部门id"),
                qDepartmentDo.dept_name.as("部门名称"))
                .from(qDepartmentDo)
                .leftJoin(qEmployeeDo)
                .on(qDepartmentDo.dept_name.eq(qEmployeeDo.dept_name))
                .fetch();
        list.forEach(System.out::println);
    }
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

# 自定义返回对象

在我们集成queryDsl时,一般是这样用的

@Override
public List<CityHotelVo> findcityHotel() {
    JPAQuery<CityHotelVo> query = new JPAQuery<>(em);
    QTCity c = QTCity.tCity;
    QTHotel h = QTHotel.tHotel;

    JPAQuery<Tuple> on = query.select(
        c.id,
        c.name,
        h.name,
        h.address).from(c).leftJoin(h).on(c.id.eq(h.city));

    QueryResults<Tuple> rts = on.fetchResults();
    List<Tuple> results = rts.getResults();

    return results.stream().map(CityHotelVo::new).collect(Collectors.toList());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

转Vo实现

public CityHotelVo(Tuple t) {
    this.id = t.get(QTCity.tCity.id);
    this.cityName = t.get(QTCity.tCity.name);
    this.hotelName = t.get(QTHotel.tHotel.name);
    this.address = t.get(QTHotel.tHotel.address);
}
1
2
3
4
5
6

返回的是一个List,我们还需将tuple手动转成我们自定义的VO对象,以下总结了可自动Tuple转VO的几种实现。

# 方式一

/**
 * 方式一:使用Bean投影
 * @return
 */
@Override
public List<CityHotelVo> findcityHotel_2() {
    JPAQuery<CityHotelVo> query = new JPAQuery<>(em);
    QTCity c = QTCity.tCity;
    QTHotel h = QTHotel.tHotel;

    List<CityHotelVo> results1 = query.select(Projections.bean(CityHotelVo.class,
                                                               c.id.as("id"),
                                                               c.name.as("cityName"),
                                                               h.name.as("hotelName"),
                                                               h.address.as("address"))).from(c).leftJoin(h).on(c.id.eq(h.city)).fetchResults().getResults();
    return results1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 方式二

/**
 * 方式二 fields 投影
 * @return
 */
@Override
public List<CityHotelVo2> projectionsFields() {
    JPAQuery<CityHotelVo> query = new JPAQuery<>(em);
    QTCity c = QTCity.tCity;
    QTHotel h = QTHotel.tHotel;

    JPAQuery<CityHotelVo2> on = query.select(
        Projections.fields(CityHotelVo2.class,
                           c.id,
                           c.name,
                           h.address))
        .from(c).leftJoin(h).on(c.id.eq(h.city));
    List<CityHotelVo2> resultList = on.createQuery().getResultList();
    return resultList;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 方式三

/**
 * @return
 */
@Override
public List<CityHotelVo2> findcityHotel_31() {
    QTCity c = QTCity.tCity;
    QTHotel h = QTHotel.tHotel;
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    JPAQuery<CityHotelVo2> on = queryFactory.select(
        Projections.constructor(CityHotelVo2.class,
                                c.id,
                                c.name,
                                h.address))
        .from(c).leftJoin(h).on(c.id.eq(h.city));
    List<CityHotelVo2> results = on.fetchResults().getResults();
    return results;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

注意这种构造器方式,只支持对数值和String类型的映射处理,当你定义了Date等等类型,需要在构造函数中,构造如下

@Data
@Accessors(chain = true)
public class CityHotelVo4 implements Serializable {
    private static final long serialVersionUID = 2546523L;
    private Integer id;
    private String cityName;
    private String hotelName;
    private String address;

    private LocalDateTime formatTime;
    public CityHotelVo4(Integer id, String cityName, String hotelName, String address, String formatTime) throws ParseException {
        this.id = id;
        this.cityName = cityName;
        this.hotelName = hotelName;
        this.address = address;
        this.formatTime = DateUtils.parseLocalDateTime(formatTime);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 字符串操作API

//	字符串拼接
/**
this + str
     * Create a {@code concat(this, str)} expression
     *
     * <p>Get the concatenation of this and str</p>
     *
     * @param str string to append
     * @return this + str
     */
public StringExpression append(Expression<String> str) {
    return Expressions.stringOperation(Ops.CONCAT, mixin, str);
}
/**
this + str
     * Create a {@code concat(this, str)} expression
     *
     * <p>Get the concatenation of this and str</p>
     *
     * @param str string to append
     * @return this + str
     */
public StringExpression concat(Expression<String> str) {
    return append(str);
}
/**
str + this
     * Create a {@code concat(str, this)} expression
     *
     * <p>Prepend the given String and return the result</p>
     *
     * @param str string
     * @return str + this
     */
public StringExpression prepend(Expression<String> str) {
    return Expressions.stringOperation(Ops.CONCAT, str, mixin);
}

//是否包含某个字符串
/**
     * Create a {@code this.contains(str)} expression
     *
     * <p>Returns true if the given String is contained</p>
     *
     * @param str string
     * @return this.contains(str)
     * @see java.lang.String#contains(CharSequence)
     */
public BooleanExpression contains(Expression<String> str) {
    return Expressions.booleanOperation(Ops.STRING_CONTAINS, mixin, str);
}
/**
是否包含某个字符串,忽略大小写
     * Create a {@code this.containsIgnoreCase(str)} expression
     *
     * <p>Returns true if the given String is contained, compare case insensitively</p>
     *
     * @param str string
     * @return this.containsIgnoreCase(str) expression
     */
public BooleanExpression containsIgnoreCase(Expression<String> str) {
    return Expressions.booleanOperation(Ops.STRING_CONTAINS_IC, mixin, str);
}

//是否以某个字符串结尾
/**
     * Create a {@code this.endsWith(str)} expression
     *
     * <p>Returns true if this ends with str</p>
     *
     * @param str string
     * @return this.endsWith(str)
     * @see java.lang.String#endsWith(String)
     */
public BooleanExpression endsWith(Expression<String> str) {
    return Expressions.booleanOperation(Ops.ENDS_WITH, mixin, str);
}
//是否以某个字符串结尾,忽略大小写
public BooleanExpression endsWithIgnoreCase(Expression<String> str) {
    return Expressions.booleanOperation(Ops.ENDS_WITH_IC, mixin, str);
}
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

# Number操作API

//比较
max(最大值),min(最小值)
goe(this >= num),goeAll(this >= nums),goeAny(this >= any num)
gt(this > num),gtAll(this > nums),gtAny(this > any num)
loe(this <= num),loeAll(this <= nums),loeany(this <= any num)
lt(this < num),ltAll(this < nums),ltAny(this < any num)
between(from <= this <= to),notBetween(this < from || this > to this < from || this > to)

//运算
add(求和),avg(平均值),divide(除法,this / num)
    
//字节转换
byteValue(num转为byte)
    
//Expression表达式
mod(return this, num),multiply(this * num),negate(this * -1 取反),subtract(this - right)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
God's Plan
Drake