一、 JPA

1. 简介

1.1 ORM 思想

ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射。

简单的说:ORM 就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。

常见的 orm 框架:Mybatis(ibatis)、Hibernate、Jpa

1.2 hibernate 介绍

Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,它将 POJO 与数据库表建立映射关系,是一个全自动的 orm 框架,hibernate 可以 自动生成SQL 语句,自动执行,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

1.3 JPA 简介

JPA 的全称是 Java Persistence API, 即 Java 持久化 API,是 SUN 公司推出的一套基于 ORM 的规范,内部是由一系列的接口和抽象类构成。JPA 通过注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

1.4 JPA 和 hibernate

02jpa.png

JPA 是规范,hibernate 是实现,就好像 mysql 驱动和 JDBC 规范的关系一样。

1.5 JPA 和 mybatis

性能:mybatis 较好

mybatis 多表操作方便,JPA 单表操作方便

个人感觉还是 mybatis 好用些。。。JPA 就是把 sql 语句换成了 java,其实并不方便

2. 快速入门

导入坐标

<!-- hibernate对jpa的支持包 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>${project.hibernate.version}</version>
</dependency>

<!-- c3p0 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-c3p0</artifactId>
    <version>${project.hibernate.version}</version>
</dependency>

<!-- Mysql and MariaDB -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>

配置文件

JPA 配置文件是固定的,在 src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.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="111111"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>

            <!--配置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>

数据库表

/*创建客户表*/
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.实体类和表的映射关系
 *      @Entity:声明实体类
 *      @Table : 配置实体类和表的映射关系
 *          name : 配置数据库表的名称
 *   2.实体类中属性和表中字段的映射关系
 *
 *
 */
@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:数据库表中字段的名称
     *      unique:是否唯一  
     *      nullable:是否可以为空  
     *      inserttable:是否可以插入  
     *      updateable:是否可以更新  
     *      columnDefinition: 定义建表时创建此列的DDL  
     *      secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境[重点]
     */
    @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;//客户地址
}

操作数据库

@Test
public void testSave() {
    //1.加载配置文件创建工厂(实体管理器工厂)对象
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
    //2.通过实体管理器工厂获取实体管理器
    EntityManager em = factory.createEntityManager();
    EntityManager em = JpaUtils.getEntityManager();
    //3.获取事务对象,开启事务
    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();
}

代码优化

创建 JpaUtils 工具类

/**
 * 解决实体管理器工厂的浪费资源和耗时问题
 *      通过静态代码块的形式,当程序第一次访问此工具类时,创建一个公共的实体管理器工厂对象
 *
 * 第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,再调用方法创建一个EntityManager对象
 * 第二次方法getEntityManager方法:直接通过一个已经创建好的factory对象,创建EntityManager对象
 */
public class JpaUtils {

    private static EntityManagerFactory factory;

    static  {
        //1.加载配置文件,创建entityManagerFactory
        factory = Persistence.createEntityManagerFactory("myJpa");
    }

    /**
     * 获取EntityManager对象
     */
    public static EntityManager getEntityManager() {
       return factory.createEntityManager();
    }
}

在使用的时候,直接获取即可

3. Jpa 基本操作

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

    Persisitence.reateEntityMnagerFactoryc(持久化单元名称):静态方法(根据持久化单元名称创建实体管理器工厂)

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

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

    EntityManagerFactory.createEntityManager :获取 EntityManager 对象。
    内部维护了数据库信息、缓存信息、所有的实体管理器对象。
    再创建 EntityManagerFactory 的过程中会根据配置创建数据库表。
    EntityManagerFactory 的创建过程比较浪费资源
    EntityManagerFactory 是线程安全的

  3. 创建事务对象,开启事务

    EntityManager 对象:实体类管理器

    • getTransaction : 创建事务对象

      • begin:开启事务
      • commit:提交事务
      • rollback:回滚
    • presist : 保存

    • merge : 更新

    • remove : 删除

    • find/getRefrence : 根据id查询

      find 是调用的时候直接查询

      getReference 获取的对象是一个动态代理对象,不会立即发送 sql 语句查询数据,当调用查询结果对象的时候,才会发送查询的 sql 语句,就是所谓的延迟加载

  4. 增删改查操作

  5. 提交事务

  6. 释放资源

例子

@Test
public  void testReference() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    //3.增删改查 -- 根据id查询客户
    /**
        * getReference : 根据id查询数据
        *      class:查询数据的结果需要包装的实体类类型的字节码
        *      id:查询的主键的取值
        */
    Customer customer = entityManager.getReference(Customer.class, 1l);
    System.out.print(customer);
    //4.提交事务
    tx.commit();
    //5.释放资源
    entityManager.close();
}

@Test
public  void testRemove() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    //3.增删改查 -- 删除客户

    //i 根据id查询客户
    Customer customer = entityManager.find(Customer.class,1l);
    //ii 调用remove方法完成删除操作
    entityManager.remove(customer);

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

@Test
public  void testUpdate() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    //3.增删改查 -- 更新操作

    //i 查询客户
    Customer customer = entityManager.find(Customer.class,1l);
    //ii 更新客户
    customer.setCustIndustry("it教育");
    entityManager.merge(customer);

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

4. Jpa 复杂查询

使用的是 jpql 查询

sql:查询的是表和表中的字段
jpql:查询的是实体类和类中的属性
jpql 和 sql 语句的语法相似

/**
    * 查询全部
    *      jqpl:from cn.itcast.domain.Customer
    *      sql:SELECT * FROM cst_customer
    */
@Test
public void testFindAll() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    //3.查询全部
    String jpql = "from Customer ";
    Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

    //发送查询,并封装结果集
    List list = query.getResultList();

    for (Object obj : list) {
        System.out.print(obj);
    }

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

下面只写核心代码部分

/**
    * 排序查询: 倒序查询全部客户(根据id倒序)
    *      sql:SELECT * FROM cst_customer ORDER BY cust_id DESC
    *      jpql:from Customer order by custId desc
    *
    * 进行jpql查询
    *      1.创建query查询对象
    *      2.对参数进行赋值
    *      3.查询,并得到返回结果
    */
@Test
public void testOrders() {
    //3.查询全部
    String jpql = "from Customer order by custId desc";
    Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

    //发送查询,并封装结果集
    List list = query.getResultList();
}
/**
    * 使用jpql查询,统计客户的总数
    *      sql:SELECT COUNT(cust_id) FROM cst_customer
    *      jpql:select count(custId) from Customer
    */
@Test
public void testCount() {
    //3.查询全部
    //i.根据jpql语句创建Query查询对象
    String jpql = "select count(custId) from Customer";
    Query query = em.createQuery(jpql);
    //ii.对参数赋值
    //iii.发送查询,并封装结果

    /**
        * getResultList : 直接将查询结果封装为list集合
        * getSingleResult : 得到唯一的结果集
        */
    Object result = query.getSingleResult();
}
/**
    * 分页查询
    *      sql:select * from cst_customer limit 0,2
    *      jqpl : from Customer
    */
@Test
public void testPaged() {
    //3.查询全部
    //i.根据jpql语句创建Query查询对象
    String jpql = "from Customer";
    Query query = em.createQuery(jpql);
    //ii.对参数赋值 -- 分页参数
    //起始索引
    query.setFirstResult(0);
    //每页查询的条数
    query.setMaxResults(2);

    //iii.发送查询,并封装结果
    List list = query.getResultList();
}
/**
    * 条件查询
    *     案例:查询客户名称以‘传智播客’开头的客户
    *          sql:SELECT * FROM cst_customer WHERE cust_name LIKE  ?
    *          jpql : from Customer where custName like ?
    */
@Test
public void testCondition() {
    //3.查询全部
    //i.根据jpql语句创建Query查询对象
    String jpql = "from Customer where custName like ? ";
    Query query = em.createQuery(jpql);
    //ii.对参数赋值 -- 占位符参数
    //第一个参数:占位符的索引位置(从1开始),第二个参数:取值
    query.setParameter(1,"传智播客%");

    //iii.发送查询,并封装结果
    List list = query.getResultList();
}

二、 springDataJpa

1. 概述

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

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

2. 联系

JPA 是一套规范,内部是有接口和抽象类组成的。hibernate 是一套成熟的 ORM 框架,而且Hibernate 实现了 JPA 规范,所以也可以称 hibernate 为 JPA 的一种实现方式,我们使用 JPA 的 API 编程,意味着站在更高的角度上看待问题(面向接口编程)。

Spring Data JPA 是 Spring 提供的一套对 JPA 操作更加高级的封装,是在 JPA 规范下的专门用来进行数据持久化的解决方案。

01springDataJpajpahibernate关系.png

3. 快速入门

导入坐标

<!-- spring beg -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.8</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- spring对orm框架的支持包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- spring end -->

<!-- hibernate beg -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>${hibernate.version}</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>${hibernate.version}</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.1.Final</version>
</dependency>
<!-- hibernate end -->

<!-- c3p0 beg -->
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>${c3p0.version}</version>
</dependency>
<!-- c3p0 end -->

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
</dependency>

<!-- spring data jpa 的坐标-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.9.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- el beg 使用spring data jpa 必须引入 -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>2.2.4</version>
</dependency>

<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>javax.el</artifactId>
    <version>2.2.4</version>
</dependency>
<!-- el end -->

Spring 整合 SpringDataJpa

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--spring 和 spring data jpa的配置-->

    <!-- 1.创建entityManagerFactory对象交给spring容器管理-->
    <bean id="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--配置的扫描的包(实体类所在的包) -->
        <property name="packagesToScan" value="cn.itcast.domain" />
        <!-- jpa的实现厂家 -->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!--jpa的供应商适配器 -->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置是否自动创建数据库表 -->
                <property name="generateDdl" value="false" />
                <!--指定数据库类型 -->
                <property name="database" value="MYSQL" />
                <!--数据库方言:支持的特有语法 -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
                <!--是否显示sql -->
                <property name="showSql" value="true" />
            </bean>
        </property>

        <!--jpa的方言 :高级的特性 -->
        <property name="jpaDialect" >
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>

    </bean>

    <!--2.创建数据库连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"></property>
        <property name="password" value="111111"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa" ></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    </bean>

    <!--3.整合spring dataJpa-->
    <jpa:repositories base-package="cn.itcast.dao" transaction-manager-ref="transactionManager"
                   entity-manager-factory-ref="entityManagerFactoty" ></jpa:repositories>

    <!--4.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoty"></property>
    </bean>

    <!-- 4.txAdvice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 5.aop-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    </aop:config>


    <!--5.声明式事务 -->

    <!-- 6. 配置包扫描-->
    <context:component-scan base-package="cn.itcast" ></context:component-scan>
</beans>

编写实体类

@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;
}

编写 Dao 层

Spring Data JPA 是 spring 提供的一款对于数据访问层(Dao层)的框架,使用 Spring Data JPA,只需要按照框架的规范提供 dao 接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。

在 Spring Data JPA 中,对于定义符合规范的 Dao 层接口,我们只需要遵循以下几点就可以了

  • 创建一个 Dao 层接口,并实现 JpaRepository 和 JpaSpecificationExecutor
  • 提供相应的泛型
/**
 * JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
 * JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {}

测试

@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {
    @Autowired
    private CustomerDao customerDao;

    /**
     * 根据id查询
     */
    @Test
    public void testFindOne() {
        Customer customer = customerDao.findOne(4l);
        System.out.println(customer);
    }

    /**
     * save : 保存或者更新
     *      根据传递的对象是否存在主键id,
     *      如果没有id主键属性:保存
     *      存在id主键属性,根据id查询数据,更新数据
     */
    @Test
    public void testSave() {
        Customer customer  = new Customer();
        customer.setCustName("黑马程序员");
        customer.setCustLevel("vip");
        customer.setCustIndustry("it教育");
        customerDao.save(customer);
    }

    @Test
    public void testUpdate() {
        Customer customer  = new Customer();
        customer.setCustId(4l);
        customer.setCustName("黑马程序员很厉害");
        customerDao.save(customer);
    }

    @Test
    public void testDelete () {
        customerDao.delete(3l);
    }


    /**
     * 查询所有
     */
    @Test
    public void testFindAll() {
        List<Customer> list = customerDao.findAll();
        for(Customer customer : list) {
            System.out.println(customer);
        }
    }

    /**
     * 测试统计查询:查询客户的总数量
     *      count:统计总条数
     */
    @Test
    public void testCount() {
        long count = customerDao.count();//查询全部的客户数量
        System.out.println(count);
    }

    /**
     * 测试:判断id为4的客户是否存在
     *      1. 可以查询以下id为4的客户
     *          如果值为空,代表不存在,如果不为空,代表存在
     *      2. 判断数据库中id为4的客户的数量
     *          如果数量为0,代表不存在,如果大于0,代表存在
     */
    @Test
    public void  testExists() {
        boolean exists = customerDao.exists(4l);
        System.out.println("id为4的客户 是否存在:"+exists);
    }


    /**
     * 根据id从数据库查询
     *      @Transactional : 保证getOne正常运行
     *
     *  findOne:
     *      em.find()           :立即加载
     *  getOne:
     *      em.getReference     :延迟加载
     *      * 返回的是一个客户的动态代理对象
     *      * 什么时候用,什么时候查询
     */
    @Test
    @Transactional
    public void  testGetOne() {
        Customer customer = customerDao.getOne(4l);
        System.out.println(customer);
    }

}
  • findOne(id) :根据 id 查询
  • getOne(id):根据 id 查询,延迟加载
  • exists(id):判断是否存在
  • save(customer):保存或者更新(依据:传递的实体类对象中,是否包含 id 属性)
  • delete(id) :根据 id 删除
  • findAll() : 查询全部

4. 原理剖析

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

03springdatajpa的运行过程.png

5. 复杂查询

除了以上简单查询以外,我们也可以进行复杂查询,复杂查询有三种方式

5.1 jpql 查询方式

特点:语法或关键字和 sql 语句类似,查询的是类和类中的属性

需要将 JPQL 语句配置 dao 层接口上,在新添加的方法上,使用注解的形式配置 jpql 查询语句

/**
    * 案例:根据客户名称和客户id查询客户
    *      jpql: from Customer where custName = ? and custId = ?
    *
    *  对于多个占位符参数
    *      赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保持一致
    *
    *  可以指定占位符参数的位置
    *      ? 索引的方式,指定此占位的取值来源
    */
@Query(value = "from Customer where custName = ?2 and custId = ?1")
public Customer findCustNameAndId(Long id,String name);

/**
    * 使用jpql完成更新操作
    *      案例 : 根据id更新,客户的名称
    *          更新4号客户的名称,将名称改为“黑马程序员”
    *
    *  sql  :update cst_customer set cust_name = ? where cust_id = ?
    *  jpql : update Customer set custName = ? where custId = ?
    *
    *  @Query : 代表的是进行查询
    *      * 声明此方法是用来进行更新操作
    *  @Modifying
    *      * 当前执行的是一个更新操作
    *
    */
@Query(value = " update Customer set custName = ?2 where custId = ?1 ")
@Modifying
public void updateCustomer(long custId,String custName);

5.2 sql 查询方式

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

  • value :jpql语句 | sql语句
  • nativeQuery :false(使用jpql查询) | true(使用本地查询:sql查询)
//@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);

5.3 方法名称规则查询

/**
* 方法名的约定:
*      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);


public List<Customer> findByCustNameLike(String custName);

//使用客户名称模糊匹配和客户所属行业精准匹配的查询
public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
KeywordSampleJPQL
AndfindByLastnameAndFirstnamewhere x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstnamewhere x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEqualswhere x.firstname = ?1
BetweenfindByStartDateBetweenwhere x.startDate between ?1 and ?2
LessThanfindByAgeLessThanwhere x.age < ?1
LessThanEqualfindByAgeLessThanEqualwhere x.age ⇐ ?1
GreaterThanfindByAgeGreaterThanwhere x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqualwhere x.age >= ?1
AfterfindByStartDateAfterwhere x.startDate > ?1
BeforefindByStartDateBeforewhere x.startDate < ?1
IsNullfindByAgeIsNullwhere x.age is null
IsNotNull,NotNullfindByAge(Is)NotNullwhere x.age not null
LikefindByFirstnameLikewhere x.firstname like ?1
NotLikefindByFirstnameNotLikewhere x.firstname not like ?1
StartingWithfindByFirstnameStartingWithwhere x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWithwhere x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContainingwhere x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDescwhere x.age = ?1 order by x.lastname desc
NotfindByLastnameNotwhere x.lastname <> ?1
InfindByAgeIn(Collection ages)where x.age in ?1
NotInfindByAgeNotIn(Collection age)where x.age not in ?1
TRUEfindByActiveTrue()where x.active = true
FALSEfindByActiveFalse()where x.active = false
IgnoreCasefindByFirstnameIgnoreCasewhere UPPER(x.firstame) = UPPER(?1)

6. 动态查询

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

JpaSpecificationExecutor 的方法列表

//根据条件查询一个对象
T findOne(Specification<T> spec);	
//根据条件查询集合
List<T> findAll(Specification<T> spec);
//根据条件分页查询,Pageable 为分页参数
age<T> findAll(Specification<T> spec, Pageable pageable);
//排序查询查询
List<T> findAll(Specification<T> spec, Sort sort);
//统计查询
 long count(Specification<T> spec);

对于 JpaSpecificationExecutor,这个接口基本是围绕着 Specification 接口来定义的。我们可以简单的理解为,Specification 构造的就是查询条件。

Specification 接口中只定义了如下一个方法:

//构造查询条件
/**
 *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
 *	query	:代表一个顶层查询对象,用来自定义查询,一般不用
 *	cb		:用来构建查询,内部封装了很多的查询条件(模糊匹配,精准匹配)
 **/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

单条件查询例子

Specification<Customer> spec = new Specification<Customer>() {
    @Override
    public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //1.获取比较的属性
        Path<Object> custName = root.get("custName");
        //2.构造查询条件  :    select * from cst_customer where cust_name = '传智播客'
        /**
            * 第一个参数:需要比较的属性(path对象)
            * 第二个参数:当前需要比较的取值
            */
        Predicate predicate = cb.equal(custName, "传智播客");//进行精准的匹配  (比较的属性,比较的属性的取值)
        return predicate;
    }
};
Customer customer = customerDao.findOne(spec);

多条件查询例子

/**
 * 多条件查询
 *      案例:根据客户名(传智播客)和客户所属行业查询(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);
}

排序例子

/**
 * 案例:完成根据客户名称的模糊匹配,返回客户列表
 *      客户名称以 ’传智播客‘ 开头
 *
 * 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;
        }
    };
    //添加排序
    //创建排序对象,需要调用构造方法实例化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);
    }
}

分页查询例子

/**
 * 分页查询
 *      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());//得到总页数
}

7. 一对多

在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:

/**
 * 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<>();
}
@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)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer; 
}

添加

由于我们配置了外键关系,会自动维护,我们只需要在多的一方维护外键关系即可。

@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);
}
@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);
}
/**
    * 会有一条多余的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);
}

在 customer 方放弃外键维护权的方法如下

//@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")	
//设置为
@OneToMany(mappedBy="customer")

级联添加

需要在操作主体的实体类上,配置 casacde 属性

/**
  * 级联添加:保存一个客户的同时,保存客户的所有联系人
  *      需要在操作主体的实体类上,配置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);
}

级联删除操作的说明如下:

删除从表数据:可以随时任意删除。

删除主表数据:

  • 有从表数据
    • 在默认情况下,它会把外键字段置为 null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
    • 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为 null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
    • 如果还想删除,使用级联删除引用
  • 没有从表数据引用:随便删

在实际开发中,级联删除请慎用!(在一对多的情况下)

注解说明

  • @OneToMany

    • 作用:建立一对多的关系映射
    • 属性:
      • targetEntityClass:指定多的多方的类的字节码
      • mappedBy:指定从表实体类中引用主表对象的名称。
      • cascade:指定要使用的级联操作
      • fetch:指定是否采用延迟加载
      • orphanRemoval:是否使用孤儿删除
  • @ManyToOne

    • 作用:建立多对一的关系
    • 属性:
      • targetEntityClass:指定一的一方实体类字节码
      • cascade:指定要使用的级联操作
      • fetch:指定是否采用延迟加载
      • optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
  • @JoinColumn

    • 作用:用于定义主键字段和外键字段的对应关系。
    • 属性:
      • name:指定外键字段的名称
      • referencedColumnName:指定引用主表的主键字段名称
      • unique:是否唯一。默认值不唯一
      • nullable:是否允许为空。默认值允许。
      • insertable:是否允许插入。默认值允许。
      • updatable:是否允许更新。默认值允许。
      • columnDefinition:列的定义信息。

8. 多对多

编写实体类

@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<>();
}
@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<>();
}

添加

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

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

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

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

    userDao.save(user);
    roleDao.save(role);
}

问题:在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。

解决办法:让任意一方放弃维护关联关系的权利

在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:

@ManyToMany(mappedBy = "roles")  //配置多表关系
private Set<User> users = new HashSet<>();

注解说明

  • @ManyToMany

    • 作用:用于映射多对多关系
    • 属性:
      • cascade:配置级联操作。
      • fetch:配置是否采用延迟加载。
      • targetEntity:配置目标的实体类。映射多对多的时候不用写。
  • @JoinTable

    • 作用:针对中间表的配置
    • 属性:
      • nam:配置中间表的名称
      • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
      • inverseJoinColumn:中间表的外键字段关联对方表的主键字段
  • @JoinColumn

    • 作用:用于定义主键字段和外键字段的对应关系。
    • 属性:
      • name:指定外键字段的名称
      • referencedColumnName:指定引用主表的主键字段名称
      • unique:是否唯一。默认值不唯一
      • nullable:是否允许为空。默认值允许。
      • insertable:是否允许插入。默认值允许。
      • updatable:是否允许更新。默认值允许。
      • columnDefinition:列的定义信息。

9. 多表查询

9.1 对象导航查询

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用 Customer 类中的getLinkMans() 方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

查询一个客户,获取该客户下的所有联系人

@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional 
public void testFind() {
	Customer customer = customerDao.findOne(5l);
	Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
	for(LinkMan linkMan : linkMans) {
 		System.out.println(linkMan);
	}
}

查询一个联系人,获取该联系人的所有客户

@Test
public void testFind() {
	LinkMan linkMan = linkManDao.findOne(4l);
	Customer customer = linkMan.getCustomer(); //对象导航查询
	System.out.println(customer);
}

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

/**
 * 在客户对象的@OneToMany注解中添加fetch属性
 * 		FetchType.EAGER	:立即加载
 * 		FetchType.LAZY	:延迟加载
 */
@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>(0);

问题2:我们查询联系人时,要不要把客户查询出来?

分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。

解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来

配置方式:

/**
 * 在联系人对象的@ManyToOne注解中添加fetch属性
 * 		FetchType.EAGER	:立即加载
 * 		FetchType.LAZY	:延迟加载
 */
@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
private Customer customer;

从一方查询多方:默认使用延迟加载

从多方查询一方:默认使用立即加载

9.2 使用 Specification 查询

/**
 * Specification的多表查询
 */
@Test
public void testFind() {
	Specification<LinkMan> spec = new Specification<LinkMan>() {
		public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			//Join代表链接查询,通过root对象获取
			//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
			//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
			Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
			return cb.like(join.get("custName").as(String.class),"传智播客1");
		}
	};
	List<LinkMan> list = linkManDao.findAll(spec);
	for (LinkMan linkMan : list) {
		System.out.println(linkMan);
	}
}