在前面的章節已經講述了SpringDataJpa的CRUD操做以及其底層代理實現的分析,下面介紹SpringDataJpa中的複雜查詢和動態查詢,多表查詢。(保姆級教程)java
文章字數較多,請各位按需閱讀。mysql
不清楚JPA的小夥伴能夠參考這篇文章:JPA簡介;程序員
不清楚SpringDataJPA環境搭建的小夥伴能夠參考這篇文章:SpringDataJPA入門案例;spring
想了解SpringDataJPA代理類實現過程能夠參考這篇文章:SpringDadaJPA底層實現原理sql
如需轉載,請註明出處。數據庫
方法名查詢:只須要按照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);
}
複製代碼
使用 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);
}
複製代碼
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);
複製代碼
springdatajpa的接口規範:
JpaRepository<操做的實體類型,實體類型中的 主鍵 屬性的類型>
封裝了基本的CRUD的操做,分頁等;
JpaSpecificationExecutor<操做的實體類類型>
封裝了複雜查詢。
上述查詢方法使用到的是接口JpaRepository中的方法,下面分析JpaSpecificationExecutor中的方法。
可能有些許疑惑,爲何還須要動態查詢呢?有時候咱們在查詢某個實體的時候哦,給定的查詢條件不是固定的,這個時候就須要動態構建相應的查詢語句,能夠理解爲上述的查詢條件是定義在dao接口中的,而動態查詢條件定義在實現類中。
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接口中,而動態查詢定義在實現類中。
@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);
}
}
複製代碼
@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);
}
}
複製代碼
@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);
}
}
}
複製代碼
@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());
}
}
複製代碼
@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);
}
}
}
複製代碼
上述複雜查詢和動態查詢都是基於單表查詢,只須要指定實體類與數據庫表中一對一的映射。而多表查詢須要修改實體類之間的映射關係。
在數據庫中表與表之間,存在三種關係:多對多、一對多、一對一。
那麼與之對應的實體映射也應該有三種關係。那麼在JPA中表的關係如何分析呢?
案例分析:
採用兩個實體對象:公司與員工
在不考慮兼職的狀況下,每名員工對應一家公司,每家公司有多名員工。
在一對多關係中,咱們習慣把一的一方稱之爲主表,把多的一方稱之爲從表。在數據庫中創建一對 多的關係,須要使用數據庫的外鍵約束。
**什麼是外鍵?**指的是從表中有一列,取值參照主表中的主鍵,這一列就是外鍵。
數據庫表:
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;
複製代碼
注意:使用的註解都是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略... */
}
複製代碼
注意:在上述實體中,均對外鍵進行了維護。
做用:創建一對多的關係映射 屬性:
做用:創建多對一的關係 屬性:
做用:用於定義主鍵字段和外鍵字段的對應關係。 屬性:
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<>();
複製代碼
級聯操做:操做一個對象同時操做它的關聯對象
使用方法:只須要在操做主體的註解上配置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 (?, ?, ?, ?, ?, ?, ?, ?) 複製代碼
刪除公司的同時,刪除對應公司的全部員工。
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=?
複製代碼
注意:通常使用級聯刪除是比較危險的,在一對多的狀況下。若是沒有使用級聯操做,應該如何刪除數據?
只刪除從表數據:能夠任意刪除。
刪除主表數據:
建立方法:根據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=?
複製代碼
案例:用戶和角色。
用戶:指社會上的某我的。
角色:指人們可能有多種身份信息
好比說:小明有多種身份,即便java工程師,仍是後端攻城獅,也是CEO;而Java工程師除了小明,還有張3、李四等等。
因此咱們說,用戶和角色之間的關係是多對多。
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;
}
}
複製代碼
做用:用於映射多對多關係 屬性:
做用:針對中間表的配置 屬性:
做用:用於定義主鍵字段和外鍵字段的對應關係。 屬性:
數據庫表:(其實能夠直接由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並添加數據。
保存用戶的同時,保存其關聯角色。
只須要在操做對象的註解上配置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 (?, ?)
複製代碼
/** * 級聯操做:刪除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=?
複製代碼
注意:
如下例子採用一對多的案例實現。
對象導航查詢的方式就是根據已加載的對象,導航到他的關聯對象。利用實體與實體之間的關係來檢索對象。例如:經過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();
方法,則將不會執行關聯對象的查詢。
/** * 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);
}
}
複製代碼