SpringDataJpa中的複雜查詢和動態查詢,多表查詢。(保姆級教程)

在前面的章節已經講述了SpringDataJpa的CRUD操做以及其底層代理實現的分析,下面介紹SpringDataJpa中的複雜查詢和動態查詢,多表查詢。(保姆級教程)java

文章字數較多,請各位按需閱讀。mysql

不清楚JPA的小夥伴能夠參考這篇文章:JPA簡介程序員

不清楚SpringDataJPA環境搭建的小夥伴能夠參考這篇文章:SpringDataJPA入門案例spring

想了解SpringDataJPA代理類實現過程能夠參考這篇文章:SpringDadaJPA底層實現原理sql

如需轉載,請註明出處。數據庫

1.複雜查詢

i.方法名稱規則查詢

方法名查詢:只須要按照SpringDataJpa提供的方法名稱規則去定義方法,在dao接口中定義方法便可。後端

其中對於方法的名稱有一套約定。bash

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.多表查詢

上述複雜查詢和動態查詢都是基於單表查詢,只須要指定實體類與數據庫表中一對一的映射。而多表查詢須要修改實體類之間的映射關係。

在數據庫中表與表之間,存在三種關係:多對多、一對多、一對一。

多表查詢01

那麼與之對應的實體映射也應該有三種關係。那麼在JPA中表的關係如何分析呢?

1.創建表與表之間的關係

  • 第一步:首先肯定兩張表之間的關係。 若是關係肯定錯了,後面作的全部操做就都不可能正確。
  • 第二步:在數據庫中實現兩張表的關係
  • 第三步:在實體類中描述出兩個實體的關係
  • 第四步:配置出實體類和數據庫表的關係映射(重點)

4.JPA中的一對多

案例分析:

採用兩個實體對象:公司與員工

在不考慮兼職的狀況下,每名員工對應一家公司,每家公司有多名員工。

在一對多關係中,咱們習慣把一的一方稱之爲主表,把多的一方稱之爲從表。在數據庫中創建一對 多的關係,須要使用數據庫的外鍵約束。

**什麼是外鍵?**指的是從表中有一列,取值參照主表中的主鍵,這一列就是外鍵。

springdatajpa進階01

數據庫表:

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工程師除了小明,還有張3、李四等等。

因此咱們說,用戶和角色之間的關係是多對多。

springdatajpa進階02

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

複製代碼
相關文章
相關標籤/搜索