likes
comments
collection
share

SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

作者站长头像
站长
· 阅读数 15

在前面的章节已经讲述了SpringDataJpa的CRUD操作以及其底层代理实现的分析,下面介绍SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

文章字数较多,请各位按需阅读。

不清楚JPA的小伙伴可以参考这篇文章:JPA简介

不清楚SpringDataJPA环境搭建的小伙伴可以参考这篇文章:SpringDataJPA入门案例

想了解SpringDataJPA代理类实现过程可以参考这篇文章:SpringDadaJPA底层实现原理

如需转载,请注明出处。

1.复杂查询

i.方法名称规则查询

方法名查询:只需要按照SpringDataJpa提供的方法名称规则去定义方法,在dao接口中定义方法即可。

其中对于方法的名称有一套约定。

KeyWord Sample JPQL
And findByLastnameAndFirstname where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname where x.lastname = ?1 or x.firstname = ?2
Between findByAgeBetween where x.Age between ?1 and ?2
LessThan findByAgeLessThan where x.age < ?1
GreaterThan findByAgeGreaterThan where x.age > ?1
Like findByFirstnameLike where x.firstname like ?1
NotLike findByFirstnameNotLike where x.firstname not like ?1
TRUE findByActiveTrue() where x.active = true
FALSE findByActiveFalse() where x.active = false
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
    /**
     * 方法名的约定:
     *         findBy:查询
     *              对象中的属性名(首字母大写):查询条件
     *              *默认情况:使用 =的方式查询
     *              特殊的查询方式,比如模糊查询
     *         findByCustName-----根据客户名称查询   findBy表示要查询  CustName属性名
     *     springDataJpa在运行阶段
     *              会根据方法名称进行解析 findBy  from XXX(实体类)
     *                                          属性名称  where custName
     *     1. findBy+属性名称(根据属性名称进行完成匹配任务)
     *     2. findBy+属性名称+查询方式(Like|isnull)
     *     3. 多条件查询
     *          findBy+属性名称+查询方式+多条件连接符(and|or)+属性名+查询方式
     */
    public List<Customer> findByCustName(String name);
    //查询id为3且name中含有大学的用户
    public Customer findByCustId(Long id);
    public Customer findByCustIdAndCustNameLike(Long id,String name);
}

ii.JPQL查询

使用 Spring Data JPA 提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来 说,我们还需要灵活的构造查询条件,这时就可以使用@Query 注解,结合 JPQL 的语句方式完成 查询 。

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个 JPQL 查询语句即可

注意:

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

public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
    /**
     *  1.根据客户名称查询客户
     *  jpql:from Customer where custName=?
     */
    @Query(value="from Customer where custName =?")
    public List<Customer> findCustomerJpql(String name);
    /**
     * 2.根据客户名称和客户id查询
     * 对多个占位符参数
     *      默认情况下,占位符的位置需要和方法参数中的位置保持一致
     *  也可以指定占位符参数的位置(注意:中间不要有空格)
     *         ? 索引的方式,指定此占位符的取值来源  eg ?2表示此占位符对应第二个参数
     */
    @Query(value="from Customer where custName=?2 and custId=?1")
    public Customer findByNameAndId(Long id,String name);
    /**
     * 3.根据id更新客户的name
     * 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=? where custId=?")
    @Modifying
    public void updateCustomerName(String name,Long id);
}

注意:在执行springDataJpa中使用jpql完成更新,删除操作时,需要手动添加事务的支持 必须的;因为默认会执行结束后,回滚事务。

 @Test
    @Transactional//添加事务的支持
    @Rollback(value = false)
    public void updateCustomerName(){
        customerDao.updateCustomerName("学生公寓",4L);
    }

iii.SQL查询

Spring Data JPA 同样也支持 sql 语句的查询,如下:

/**
     * 查询所有用户:使用sql查询
     *  Sql:select * from cst_customer
     *  nativeQuery = true配置查询方式,true表示Sql查询,false表示Jpql查询
     *  注意:返回值是一个Object[]类型的list
     */
//    @Query(value = "select * from cst_customer",nativeQuery = true)
    //    public List<Object []>findSql();
    @Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
    public List<Object []>findSql(String name);

2.动态查询

springdatajpa的接口规范:

  • JpaRepository<操作的实体类型,实体类型中的 主键 属性的类型>

    封装了基本的CRUD的操作,分页等;

  • JpaSpecificationExecutor<操作的实体类类型>

    封装了复杂查询。

上述查询方法使用到的是接口JpaRepository中的方法,下面分析JpaSpecificationExecutor中的方法。

i.为什么需要动态查询

可能有些许疑惑,为什么还需要动态查询呢?有时候我们在查询某个实体的时候哦,给定的查询条件不是固定的,这个时候就需要动态构建相应的查询语句,可以理解为上述的查询条件是定义在dao接口中的,而动态查询条件定义在实现类中。

ii.JpaSpecificationExecutor中定义的方法

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> var1);

    List<T> findAll(Specification<T> var1);

    Page<T> findAll(Specification<T> var1, Pageable var2);

    List<T> findAll(Specification<T> var1, Sort var2);

    long count(Specification<T> var1);
}

在上述方法中,我们可以看到接口Specification。可以简单理解为,Specification构造的就是查询条件。我们看看Specification中定义的方法。

/*
* root :T表示查询对象的类型,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

与上述查询方法不同,复杂查询定义在dao接口中,而动态查询定义在实现类中。

1)单条件查询

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;
    @Test
    public void conditionTest(){
        /**
         * 自定义查询条件
         *      1.实现Specification接口(提供泛型:查询对象类型,需要那个对象就写哪个泛型)
         *      2.实现toPredicate方法(构造查询条件)
         *      3.需要借书方法参数中的两个形参
         *              root:用于获取查询的对象属性
         *              CriteriaBuilder:构造查询条件,内部封装了很多的查询条件(例如:模糊匹配,精准匹配)
         *  需求:根据客户名称查询,查询客户名称为大学
         *          查询条件
         *              1.查询方法 (精准匹配,是否为空...)
         *                  CriteriaBuilder对象
         *              2.比较的属性名称(与哪个字段去以什么方式去比较)
         *                  root对象
         */

        Specification<Customer> spec=new Specification<Customer>() {
            @Override
             public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                //1.获取比较的属性(不是字段名)
                Path<Object> custName = root.get("custName");
                //2.构造查询条件
                /**
                 * 第一个参数:需要比较的属性(Path)
                 * 第二个参数:当前比较的取值
                 */
                Predicate predicate = cb.equal(custName, "三峡大学");//进行精准匹配   (比较的属性,比较的属性的取值)
                return predicate;
            }
        };
        //根据返回的对象个数选择findOne或者findAll
        Customer customer = customerDao.findOne(spec);
        System.out.println(customer);
    }
}

2)多条件查询

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;
/**
     * 多条件查询:根据用户名和所属行业进行查询
     *         root:获取属性
     *              用户名
     *              所属行业
     *         cb:构造查询
     *              1.构造客户名的精准匹配查询
     *              2.构造所属行业的精准匹配查询
     *              3,将以上两个查询联系起来
     */
    @Test
    public void findByNmaeAndIndustray(){
        Specification<Customer> spec=new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                //1.获取属性
                Path<Object> custName = root.get("custName");
                Path<Object> industry = root.get("custIndustry");
                //2.构造查询
                Predicate p1 = cb.equal(custName, "6测试数据-coderxz");
                Predicate p2 = cb.equal(industry, "6测试数据-java工程师");
                //3。将多个查询条件组合到一起(and/or)
                Predicate predicate = cb.and(p1, p2);
                return predicate;
            }
        };
        Customer customer = customerDao.findOne(spec);
        System.out.println(customer);
    }
}

3)模糊查询

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;
    /**
     * 案例:根据客户名称进行模糊配置,返回客户列表
     *
     * equal:直接的path对象(属性),然后直接进行比较即可
     *
     * 对于gt,lt,le,like:得到path对象,根据path对象指定比较参数的类型(字符串or数字...),再进行比较
     * 指定参数类型  path.as(类型的字节码对象)
     */
    @Test
    public void findVagueCustomer(){
        Specification<Customer>spec=new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
                return predicate;
            }
        };
        List<Customer> customers = customerDao.findAll(spec);
        for(Customer c:customers){
            System.out.println(c);
        }
    }
}

4)分页查询

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;
/**
     * 分页查询
     *   findAll(Pageable) 没有条件的分页查询
     *   findAll(Specification,Pageable)
     *      Specification查询条件
     *      Pageable分页参数   查询的页码,每页查询的条件
     *  返回:Pahe(StringDataJpa)为我们封装好的pageBean对象,数据列表,
     */
    @Test
    public void pageCustomer(){
        Specification<Customer> spec=new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                return null;
            }
        };
        /**
         * Pageable 接口
         *    PageRequest是其实现类
         *    第一个参数:当前查询的页数(从0开始)
         *    第二个参数:每页查询的数量
         *    注意:在新版本的jpa中,此方法已过时,新方法是PageRequest.of(page,size)
         */
        Pageable pageable = new PageRequest(0,1);
        //分页查询   page是SpringDataJpa为我们封装的一个JavaBean
        Page<Customer> page = customerDao.findAll(spec, pageable);
        //获得总页数(这些数据需要分几页)
        System.out.println("查询总页数:"+page.getTotalPages());
        //获得总记录数(数据库的总记录数)
        System.out.println("查询总记录数:"+page.getTotalElements());
        //得到数据集合列表
        System.out.println("数据集合列表:"+page.getContent());
    }
}

5)对查询结果进行排序

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;
    /**
     * 对查询结果进行排序
     */
    @Test
    public void findSortCustomer(){
        Specification<Customer>spec=new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
                return predicate;
            }
        };
        /**
         *创建排序对象,需要调用构造方法实例化对象
         * 第一个参数:排序的顺序(正序,倒序)
         *      sort.Direction.DESC:倒序
         *      sort.Direction.ASC:升序
         * 第二个参数:排序的属性名称
         */
        Sort sort = new Sort(Sort.Direction.DESC, "custId");
        List<Customer> customers = customerDao.findAll(spec,sort);
        for(Customer c:customers){
            System.out.println(c);
        }
    }
}

3.多表查询

上述复杂查询和动态查询都是基于单表查询,只需要指定实体类与数据库表中一对一的映射。而多表查询需要修改实体类之间的映射关系。

在数据库中表与表之间,存在三种关系:多对多、一对多、一对一。

SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

那么与之对应的实体映射也应该有三种关系。那么在JPA中表的关系如何分析呢?

1.建立表与表之间的关系

  • 第一步:首先确定两张表之间的关系。 如果关系确定错了,后面做的所有操作就都不可能正确。
  • 第二步:在数据库中实现两张表的关系
  • 第三步:在实体类中描述出两个实体的关系
  • 第四步:配置出实体类和数据库表的关系映射(重点)

4.JPA中的一对多

案例分析:

采用两个实体对象:公司与员工

在不考虑兼职的情况下,每名员工对应一家公司,每家公司有多名员工。

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对 多的关系,需要使用数据库的外键约束。

**什么是外键?**指的是从表中有一列,取值参照主表中的主键,这一列就是外键。

SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

数据库表:

CREATE TABLE `cst_customer` (
  `cust_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cust_address` varchar(255) DEFAULT NULL,
  `cust_industry` varchar(255) DEFAULT NULL,
  `cust_level` varchar(255) DEFAULT NULL,
  `cust_name` varchar(255) DEFAULT NULL,
  `cust_phone` varchar(255) DEFAULT NULL,
  `cust_source` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

CREATE TABLE `cst_linkman` (
  `lkm_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `lkm_email` varchar(255) DEFAULT NULL,
  `lkm_gender` varchar(255) DEFAULT NULL,
  `lkm_memo` varchar(255) DEFAULT NULL,
  `lkm_mobile` varchar(255) DEFAULT NULL,
  `lkm_name` varchar(255) DEFAULT NULL,
  `lkm_phone` varchar(255) DEFAULT NULL,
  `lkm_position` varchar(255) DEFAULT NULL,
  `lkm_cust_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`lkm_id`),
  KEY `FKh9yp1nql5227xxcopuxqx2e7q` (`lkm_cust_id`),
  CONSTRAINT `FKh9yp1nql5227xxcopuxqx2e7q` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

1.建立实体与表之间的映射关系

注意:使用的注解都是JPA规范的,导包需要导入javac.persistence下的包

package ctgu.pojo;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 *我们需要配置:
 *  1.实体类与表的映射关系(此pojo与数据库中的那一张表关系映射)
 *         @ Entity
 *         @ Table(name="cst_customer")name表示数据库中表的名称
 *  2.实体类中属性与表中字段的映射关系
 *      @ Id声明主键的设置
 *      @ GeneratedValue配置主键是生成策略(自动增长)
 *          strategy=
 *                    GenerationType.IDENTITY:自增  Mysql(底层数据库支持的自增长方式对id自增)
 *                    GenerationType.SEQUENCE:序列 Oracle(底层数据库必须支持序列)
 *                    GenerationType.TABLE:jpa提供的一种机制,通过一张数据库表的形式帮助我们完成自增
 *                    GenerationType.AUTO:有程序自动的帮助我们选择主键生成策略
 *      @ Column(name = "cust_id")数据库中表中字段的名字
 */
@Entity
@Table(name = "cst_customer")
public class Customer {
    /**
     * @ Id声明主键的设置
     * @ GeneratedValue配置主键是生成策略(自动增长)
     *              GenerationType.IDENTITY
     * @ Column(name = "cust_id")数据库中表中字段的名字
     */
    @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_industry")
    private String custIndustry;
    @Column(name = "cust_level")
    private String custLevel;
    @Column(name = "cust_address")
    private String custAddress;
    @Column(name = "cust_phone")
    private String custPhone;
    /**
     * 配置客户与联系人之间的关系(一个客户对应多个联系人)
     * 使用注解的形式配置多表关系
     *      1 声明关系
     *         @ OnetoMany:配置一对多关系
     *              targetEntity:对方对象的字节码对象
     *      2.配置外键(中间表)
     *          @ JoinColumn
     *              name:外键的在从表的字段名称(不是属性,是数据库的字段名称)
     *              referencedColumnName:参照的主表的字段名称
     */
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Set<LinkMan> linkMans=new HashSet<>();
    /*
    get/set/toString()方法略......
    */
}
package ctgu.pojo;
import javax.persistence.*;
@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;
	/*
	get/set/toString略...
	*/
}

注意:在上述实体中,均对外键进行了维护。

2.映射的注解说明

i.@OneToMany

作用:建立一对多的关系映射 属性:

  • targetEntityClass:指定多的多方的类的字节码(常用)
  • mappedBy:指定从表实体类中引用主表对象的名称。(常用)
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • orphanRemoval:是否使用孤儿删除

ii.@ManyToOne

作用:建立多对一的关系 属性:

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

iii.@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。 属性:

  • name:指定外键字段的名称(常用)
  • referencedColumnName:指定引用主表的主键字段名称(常用)
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

3.一对多测试

i.保存公司和联系人

package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;
    /**
     * 保存一个客户,保存一个联系人
     *     现象:从表(联系人)的外键为空
     *   原因:
     *      主表中没有配置关系
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void addTest(){
        Customer customer = new Customer();
        LinkMan linkMan = new LinkMan();
        customer.setCustName("TBD云集中心");
        customer.setCustLevel("VIP客户");
        customer.setCustSource("网络");
        customer.setCustIndustry("商业办公");
        customer.setCustAddress("昌平区北七家镇");
        customer.setCustPhone("010-84389340");
        
        linkMan.setLkmName("小明");
        linkMan.setLkmGender("male");
        linkMan.setLkmMobile("13811111111");
        linkMan.setLkmPhone("010-34785348");
        linkMan.setLkmEmail("123456@qq.com");
        linkMan.setLkmPosition("老师");
        linkMan.setLkmMemo("还行吧");
        /**
         * 配置了客户到联系人的关系
         *      从客户的角度上,发送了两条insert语句,发送一条更新语句更新数据库(更新从表中的外键值)
         *   由于我们配置了客户到联系人的关系,客户可以对外键进行维护
         */
        
        linkMan.setCustomer(customer);
        //此添加可以不写会
        customer.getLinkMans().add(linkMan);
        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
}

运行结果:

Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

分析:

执行了两条insert语句以及一条update语句,有一条update的语句是多余的。产生这种现象的原因是:我们在两个实体类中均对外键进行了维护,相当于维护了两次,解决的办法是放弃一方的维权。

修改:将主表中的关系映射修改为:

 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
 private Set<LinkMan> linkMans=new HashSet<>();

ii.级联添加

级联操作:操作一个对象同时操作它的关联对象

使用方法:只需要在操作主体的注解上配置casade

 /**
     * 放弃外键维护权:我的一对多映射参照对方的属性就可以了
     *    mappedBy:对方维护关系的属性名称
     *   cascade = CascadeType.ALL  进行级联操作,all表示级联所有(insert,delete,update)
     *                            .merge 更新
     *                            .persist保存
     *                            .remove 删除
     *      fetch  配置延迟加载
     */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    private Set<LinkMan> linkMans=new HashSet<>() 

一般是对配置在主表中,但是:注意:慎用CascadeType.ALL

 package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;
 /**
     * 级联添加:
     *          保存一个客户的同时,保存客户的所有联系人
     *      需要在操作主题的实体类上,配置casache属性
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void cascadeAdd(){
        Customer customer = new Customer();
        LinkMan linkMan = new LinkMan();
        customer.setCustName("测试公司1");
        linkMan.setLkmName("测试员工张三1");
        //注意此处添加
        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);
        
        customerDao.save(customer);
    }
}

测试结果:

Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

iii.级联删除

删除公司的同时,删除对应公司的所有员工。

JPA中删除是先执行查询再执行删除。

 /**
     * 级联删除:删除1号客户的同时,删除1号客户的所有联系人
     *      1.需要区分操作主体(你对那个对象进行操作)
     * 		2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
     * 		3.cascade(配置级联)
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void cascadeDelete(){
    
//        Customer customer = customerDao.findOne(1L);
        customerDao.delete(40L);
    }

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?

注意:一般使用级联删除是比较危险的,在一对多的情况下。如果没有使用级联操作,应该如何删除数据?

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

删除主表数据:

  • 有从表数据
    1. 在默认情况下,会将外键字段置为null,然后再执行删除。此时如果从表的结构上,外键字段存在非空约束将会报错。
    2. 使用级联删除。
    3. 应该先根据外键值,删除从表中的数据,再删除主表中的数据。
  • 没有从表数据:随便删

iv.一对多删除(非级联删除)

创建方法:根据customer删除员工。(使用复杂查询中的自定义方法)

package ctgu.dao;

import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
    //根据外键值进行删除
    public void deleteByCustomer(Customer customer);
}

此时的主表的关键映射为设置级联操作:

    @OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
    private Set<LinkMan> linkMans=new HashSet<>();

测试:

 package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;
@Test
    @Transactional
    @Rollback(value = false)
    public void cascadeDelete(){
        Customer customer = customerDao.findOne(47L);
        linkManDao.deleteByCustomer(customer);
        customerDao.delete(47L);
    }
}

测试结果:

Hibernate: select linkman0_.lkm_id as lkm_id1_1_, linkman0_.lkm_cust_id as lkm_cust9_1_, linkman0_.lkm_email as lkm_emai2_1_, linkman0_.lkm_gender as lkm_gend3_1_, linkman0_.lkm_memo as lkm_memo4_1_, linkman0_.lkm_mobile as lkm_mobi5_1_, linkman0_.lkm_name as lkm_name6_1_, linkman0_.lkm_phone as lkm_phon7_1_, linkman0_.lkm_position as lkm_posi8_1_ from cst_linkman linkman0_ left outer join cst_customer customer1_ on linkman0_.lkm_cust_id=customer1_.cust_id where customer1_.cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?

5.JPA中的多对多

案例:用户和角色。

用户:指社会上的某个人。

角色:指人们可能有多种身份信息

比如说:小明有多种身份,即使java工程师,还是后端攻城狮,也是CEO;而Java工程师除了小明,还有张三、李四等等。

所以我们说,用户和角色之间的关系是多对多。

SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

1.建立实体类与表直接的关系映射

package ctgu.pojo;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@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声明对方的实体类字节码
     *          2.配置中间表(两个外键)
     *              @JoinTable
     *                  name  :中间表的名称
     *                  joinColumns,当前对象在中间表的位置
     *                          @JoinColumn
     *                             name:外键在中间表的字段名称
     *                             referencedColumnName:参照的主表的主键名称
     *                  inverseJoinColumns,对方对象在中间表的位置
     */
//    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @ManyToMany(targetEntity = Role.class)
    @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;
    }
}
package ctgu.pojo;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@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(targetEntity = User.class)
    @JoinTable(name = "sys_user_role",
            //joinColumns,当前对象在中间表的位置
            joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
            //inverseJoinColumns,对方对象在中间表的位置
            inverseJoinColumns ={@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
    )
    //@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;
    }
}

2,映射注解说明

i.@ManyToMany

作用:用于映射多对多关系 属性:

  • cascade:配置级联操作。
  • fetch:配置是否采用延迟加载。
  • targetEntity:配置目标的实体类。映射多对多的时候不用写。
  • mappedBy:指定从表实体类中引用主表对象的名称。(常用)

ii.@JoinTable

作用:针对中间表的配置 属性:

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

iii.@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。 属性:

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

3.多对多测试

i.保存用户和角色

数据库表:(其实可以直接由springdataJPA自动生成)

CREATE TABLE `sys_user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

CREATE TABLE `sys_role` (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

dao接口:

package ctgu.dao;

import ctgu.pojo.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface RoleDao extends JpaRepository<Role,Long>, JpaSpecificationExecutor<Role> {
}
package ctgu.dao;

import ctgu.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}

测试案例:

package ctgu;
import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    /**
     * 保存一个用户,保存一个角色
     *      多对多放弃维护权:
     *              被动的一方放弃,谁被选择谁放弃
     */
    @Test
    @Transactional
    @Rollback(false)
    public void addUserAndRole(){
        User user = new User();
        Role role1 = new Role();
        Role role2 = new Role();
        Role role3 = new Role();
        user.setUserName("李大明");
        role1.setRoleName("后端攻城狮");
        role2.setRoleName("java程序员");
        role3.setRoleName("CEO");
        //用户和角色都可以对中间表进行维护,添加两次就重复了
        //配置角色到用户的关系,可以对中间表中的数据进行维护
        role1.getUsers().add(user);
        role2.getUsers().add(user);
        role3.getUsers().add(user);
        //配置用户到角色的关系,
        user.getRoles().add(role1);
        user.getRoles().add(role2);
        user.getRoles().add(role3);
        userDao.save(user);
        roleDao.save(role1);
        roleDao.save(role2);
        roleDao.save(role3);
    }
}

测试结果:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

原因:

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

//放弃对中间表的维护权,解决保存中主键冲突的问题
@ManyToMany(mappedBy="roles")
private Set<SysUser> users = new HashSet<SysUser>(0);

正确结果:

Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_role_id, sys_user_id) values (?, ?)

系统会自动创建表sys_user_role并添加数据。

ii.级联保存

保存用户的同时,保存其关联角色。

只需要在操作对象的注解上配置cascade

@ManyToMany(mappedBy = "roles",cascade = CascadeType.ALL)
    private Set<User> users = new HashSet<>();
package ctgu;

import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;
    /**
     * 级联操作:保存一个用户的同时,保存用户的关联角色
     *      只需要在操作对象的注解上配置cascade
     */
    @Test
    @Transactional
    @Rollback(false)
    public void addCasecade() {
        User user = new User();
        Role role = new Role();
        user.setUserName("张三");
        role.setRoleName("java程序员");
        //用户和角色都可以对中间表进行维护,添加两次就重复了
        //配置角色到用户的关系,可以对中间表中的数据进行维护
        role.getUsers().add(user);
        //配置用户到角色的关系,
        user.getRoles().add(role);
        roleDao.save(role);
    }
}

测试结果:

Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)

iii.级联删除

   /**
     * 级联操作:删除id为1的用户,同时删除他的关联对象
     */
    @Test
    @Transactional
    @Rollback(false)
    public void deleteCasecade() {
        roleDao.delete(23L);
    }

测试结果:

Hibernate: select role0_.role_id as role_id1_0_0_, role0_.role_name as role_nam2_0_0_ from sys_role role0_ where role0_.role_id=?
Hibernate: select users0_.sys_role_id as sys_role2_2_0_, users0_.sys_user_id as sys_user1_2_0_, user1_.user_id as user_id1_1_1_, user1_.age as age2_1_1_, user1_.user_name as user_nam3_1_1_ from sys_user_role users0_ inner join sys_user user1_ on users0_.sys_user_id=user1_.user_id where users0_.sys_role_id=?
Hibernate: delete from sys_user_role where sys_user_id=?
Hibernate: delete from sys_user where user_id=?
Hibernate: delete from sys_role where role_id=?

注意:

  • 调用的对象是role,所有需要在role对象中配置级联cascade = CascadeType.ALL;
  • 慎用!可能会清空相关联的数据;

6.SpringDataJPA中的多表查询

以下例子采用一对多的案例实现。

i.对象导航查询

对象导航查询的方式就是根据已加载的对象,导航到他的关联对象。利用实体与实体之间的关系来检索对象。例如:通过ID查询出一个Customer,可以调用Customer对象中的getLinkMans()方法来获取该客户的所有联系人。

对象导航查询使用的要求是:两个对象之间必须存在关联联系。

案例:查询公司,获取公司下所有的员工

package ctgu.QueryTest;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ObjectQuery {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;
    /**
     * 测试导航查询(查询一个对象的时候,通过此查询他的关联对象)
     *          对于对象导航查询,默认使用的是延迟加载的形式来查询的,(需要才去查询)
     *          调用get方法并不会立即发送查询,而实在关联对象使用的时候才会查询
     *  修改配置,将延迟加载改为立即加载
     *        fetch 需要配置多表映射关系发注解上
     *
     */
    @Test
    @Transactional//解决在java代码中的no Session问题
    public void QueryTest01(){
        Customer customer = customerDao.findOne(26L);
        Set<LinkMan> linkMans = customer.getLinkMans();
        for(LinkMan man:linkMans){
            System.out.println(man);
        }
    }
}

问题:我们在查询Customer时,一定要把LinkMan查出来吗?

分析:如果我们不查的话,在需要的时候需要重新写代码,调用方法查询;但是每次都查出来又会浪费服务器的内存。

解决:查询主表对象时,采用延迟加载的思想,通过配置的方式,当我们需要使用的时候才查询。

延迟加载

由于上述调用的对象为Customer,故而在Customer对象中需要配置延迟加载。Customer对象

@OneToMany(mappedBy = "customer",fetch = FetchType.LAZY)
    private Set<LinkMan> linkMans=new HashSet<>();

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?
Hibernate: select linkmans0_.lkm_cust_id as lkm_cust9_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_cust_id as lkm_cust9_1_1_, linkmans0_.lkm_email as lkm_emai2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_memo as lkm_memo4_1_1_, linkmans0_.lkm_mobile as lkm_mobi5_1_1_, linkmans0_.lkm_name as lkm_name6_1_1_, linkmans0_.lkm_phone as lkm_phon7_1_1_, linkmans0_.lkm_position as lkm_posi8_1_1_ from cst_linkman linkmans0_ where linkmans0_.lkm_cust_id=?
LinkMan{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}

分析:我们发现其执行了两条select语句。

问题:在我们查LinkMan时,是否需要把Customer查出来?

分析:由于一个用户只属于一家公司,及每个LinkMan都有唯一的Customer与之对应。如果我们不查,在使用的时候需要额外代码查询。且查询出的是单个对象,对内存消耗较小。

解决:在从表中采用立即加载的思想,只要查询从表实体,就把主表对象同时查出来。

立即加载

    @OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
    private Set<LinkMan> linkMans=new HashSet<>();

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}

分析结果:我们发现其只执行了一条select语句。

对比可以发现,立即加载是一次性将查询对象以及关联对象查出来,而延迟加载是先查询目标对象,如果未调用Set<LinkMan> linkMans = customer.getLinkMans();方法,则将不会执行关联对象的查询。

ii.使用 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);
		}
	}