Jpa 筆記

ORM 思想

對象關係映射, 創建實體類和表的關係映射關係, 實體類和表中字段的映射關係,咱們操做實體類底層是操做數據表, 進而自動的拼接出SQL語句java

Jpa規範

Jpa(Java Persistence Api) java持久層的api,是SUN公司提出的一套規範,也就是說,是由接口和抽象類組冊,jpa自己不幹活,真正幹活的是hibernate,toplink等等對規範具體實現的框架, 有了這套規範以後,咱們是面向這套規範編程的,也就是說,當咱們想把項目中的Hibernate替換成toplink,咱們的java代碼是不須要修改的,而僅僅修改配置文件,切換jar包mysql

上手: jpa規範

常見的註解

咱們經過註解完成兩件事:spring

  1. 實體類和數據表之間的關係的映射
  2. 實例類屬性和數據表字段以前的映射
  • 添加在類頭上的註解
// 聲明此類是實體類
@Entity
// 聲明此類是實體類
@Table(name = "表名")
  • 標記主鍵
主鍵策略 做用
IDENTITY 自增(要求底層的數據庫支持自增如mysql, Oracle就不支持)
SEQUENCE 序列(要求底層的數據庫支持序列, 如Oracle)
TABLE JPA的支援, JPA會幫咱們惟一另外一張表, 裏面記載了本表的記錄數
AUTO 自適應,讓程序根據運行的環境自動選擇策略, 其實就是 TABLE策略
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
  • 實體類屬性和表中的字段的映射
@Column(name = "表中的字段名")

進行CRUD的開發步驟:

  1. 加載配置文件, 獲得實體管理類工廠
myJpa = Persistence.createEntityManagerFactory("myJpa")
  1. 經過實體管理類工廠獲取實體管理器
myJpa.createEntityManager()
  1. 獲取事務對象, 開啓事務
EntityTransaction transaction = entityManager.getTransaction();
 transaction.begin();
  1. CRUD
  2. 提交事務
transaction.commit();
  1. 釋放資源
entityManager.close();

注意點: 1. 若是不添加事務, 是不會持久化的 2. 獲取實體管理類工廠的方法時耗時的,並且實體管理類工廠可重複使用,所以把他抽取出去, 類一加載就執行sql

經常使用方法數據庫

  • 添加public void persist(Object entity);
  • 根據主鍵Id查找public <T> T getReference(Class<T> entityClass, Object primaryKey);
  • 根據主鍵Id查找public <T> T find(Class<T> entityClass, Object primaryKey);
  • 刪除public void remove(Object entity);

find()和getReference()的區別:
find當即執行,返回實體類對象,而和getReference返回的是實體類的代理對象, 懶加載,當我使用對象的屬性時才執行查詢語句編程

#### jpqlapi

jpql: Java Persistence Query Language 根據實體類和屬性進行查詢app

其中jpql沒有select * 這種寫法,而是直接省去了, 由於是面向對象的查詢語言, 因此它的查詢語句向下面這樣寫
java from 帶包名的類的全路徑/直接寫類名框架

  • 排序
from  類名 order by id desc/asc
  • 統計數量
select count(id) from 類名
  • 帶條件的查詢
EntityManager entityManager = JpaUtils.getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
// 查詢所有
String jpql = "from 類名 where name like ?";
//  String jpql = "from  類名";   可省略包名
Query query = entityManager.createQuery(jpql);
// 參數1: 佔位符的位置
// 參數2: 參數的值
query.setParameter(1,"張%");
query.getResultList().forEach(System.out::println);
transaction.commit();
entityManager.close();
  • 分頁查詢
EntityManager entityManager = JpaUtils.getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
// 查詢所有
String jpql = "from 類名";
Query query = entityManager.createQuery(jpql);
// 對分頁的參數賦值
// 起始索引
query.setFirstResult(0);
// 分頁參數, 每次查詢兩條
query.setMaxResults(2);
// 查詢,斌封裝結果集
List resultList = query.getResultList();
resultList.forEach(System.out::println);
transaction.commit();
entityManager.close();

Spring Data Jpa

SpringDataJpa是Spring對jpa的整合,封裝,基於SpringDataJpa的規範咱們能夠更方便的進行持久層的操做, SpringDataJpa底層幹活的是Hibernate框架fetch

開發步驟

被spring整合後,相關的配置可經過spring.jpa....設置

  1. 作好實體類和數據表之間的關係的映射
  2. 面向接口編程,咱們只要本身新建一個接口,而且繼承JpaRepository和JpaSpecificationExecutor這兩個接口就可使用它的方法,而不須要關心實現類如何,就像下面:
public interface CustomerRepository extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {}

其中:

  • JpaRepository(繼承自CRUDRepository) 封裝了基本的CRUD
  • JpaSpecificationExecutor 封裝了複雜查詢

簡單的CRUD
當咱們使用自定義的Repository點一下的時,基本的CRUD基本上打眼一看就知道怎麼使用了, 下面說一下,比較類似的方法

方法名 做用
getOne() 根據Id獲取單個實體類,底層使用的是Jpa的getReference() 懶加載
findOne() 一樣是根據Id獲取單個實體,當即加載
save() 更新 若id存在 / 新增 若id爲空

支持自定義sql/jpql/方法命名規則,查詢

使用註解@Query

例:

@Query(value = "select * from  Customer where name = ?", nativeQuery = true)
public Customer findByNameAndSQL(String name);

// 查詢所有
@Query(value = "select * from  Customer", nativeQuery = true)
public List<Customer> findAllBySQL();

其中的@Query的第三個參數默認是false 表示不是sql查詢,而是jpql查詢

// jpql 查詢所有
@Query(value = "from  Customer where name =?1", nativeQuery = false)
public Customer findAllByNameAndJpql();

SpringDataJpa對jpql再次進行了封裝,支持方法命名規則查詢:

查詢方式 命名規則
根據某個字段查詢 find實體類名By字段名
模糊查詢 find實體類名By字段名Like , 注意傳參時不要忘了添加%
多條件並列查詢 find實體類名By字段名And字段名 ,使用and關鍵字隔開
多條件或查詢 find實體類名By字段名Or字段名 ,使用Or關鍵字隔開

複雜查詢

Optional<T> findOne(@Nullable Specification<T> spec);

List<T> findAll(@Nullable Specification<T> spec);

//Page 是 SpringDataJpa提供的
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

// 查詢條件spec
// 排序條件 sort
List<T> findAll(@Nullable Specification<T> spec, Sort sort);

// 按照條件統計
long count(@Nullable Specification<T> spec);

他們的公共入參都有Specification 這是個接口,咱們須要本身實現, 重寫它的抽象方法

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

其中:

  • root: 是咱們查詢的根對象(查詢的任何屬性都能從根對象中獲取)
  • CriteriaQuery: 頂層的查詢對象
  • CriteriaBuilder: 查詢的構造器, 封裝了不少查詢條件

例:
分頁查詢

// 當前查詢第幾頁, 每一頁查詢的條數
Pageable pageable =  PageRequest.of(0,2);

Page<Customer> page =  customerRepository.findAll((root, query, criteriaBuilder)->{
    return null;
}, pageable);

System.out.println("page.getTotalElements():  "+        page.getTotalElements()); // 總條數
System.out.println("page.getTotalPages():  "+        page.getTotalPages()); // 總頁數
page.getContent().forEach(System.out::println);  // 當前頁結果

排序

/**
 *  參數1 ; 正序 / 倒敘
 *  參數2 : 屬性名
 */
Sort orders = new Sort(Sort.Direction.DESC,"id");

List<Customer> list=  customerRepository.findAll((root,query,criteriaBuilder)->{
    Path<Object> name = root.get("name");
    Predicate like = criteriaBuilder.like(name.as(String.class), "武%");
    return like;
},orders);

模糊查詢

List<Customer> list=  customerRepository.findAll((root,query,criteriaBuilder)->{
    Path<Object> name = root.get("name");
    Predicate like = criteriaBuilder.like(name.as(String.class), "武%");
    return like;
});

多條件查詢

/**
 *  root 獲取屬性
 *  criteriaBuilder: 構造查詢條件
 */
Optional<Customer> customer=  customerRepository.findOne((root,query,criteriaBuilder)->{
      Path<Object> name = root.get("name");
      Path<Object> industry = root.get("industry");
      Predicate namepre = criteriaBuilder.equal(name, "張三");
      Predicate indpre = criteriaBuilder.equal(industry, "學生");

    /* 組合條件
        1. 知足條件1和條件2
        2. 知足條件1或條件2
    * */
   Predicate andpre = criteriaBuilder.and(namepre, indpre);
  //  Predicate or = criteriaBuilder.and(namepre, indpre);
    //  以 或的條件查詢
    return andpre;
});

注意點:

  • 分頁兩種: 帶條件的分頁findAll(Specification spec,Pageable pageable),和不帶條件的分頁findAll(Pageable pageable)
  • 此外: 對於criteriaBuilder的equals方法,能夠直接使用path對象,可是對於 gt lt le like咱們須要分步, 1,獲得path對象,2.根據path對象指定比較的參數類型在進行下一步比較,由於可能比較的是字符串, 也多是數字

多表操做的級聯相關

一對多配置

數據庫表之間不免會出現彼此的約束, 如商品分類表和商品表之間,就是典型的一對多的關係,同一個分類下有多種不一樣的商品,下面就是jpa如何經過註解控制一對多的關係

  1. 雙方都有一個彼此之間的引用, 如在one的一方,維護着多的一方的一個集合,通常使用HashSet,而在many的一方維護着一的一方的引用
  2. 在一的一方使用註解@OneToMany
  3. 在多的一方使用註解@ManyToOne
  4. 維護主鍵的一方須要使用@JoinColumn(name = "customer_id",referencedColumnName = "id") 註解, 做用是指明外鍵列名,以及引用的主鍵列名

關於主鍵的維護:
通常咱們會選在讓多的一方維護外鍵,不是由於一的一方不能維護,在一對多的關係中,雙方均可以進行主鍵的維護,而且咱們把這種關係叫作雙向管理,可是雙方都維護主鍵,就會使得多出一條update語句,產生資源的浪費,緣由以下:

所謂維護主鍵,就好比說咱們經過jpa的save方法插入主表中的實體1和從表中的實體2,若是咱們沒有進行雙方之間的關聯,兩條數據會被添加進數據庫,可是外鍵部分卻爲null; 所以咱們能夠把維護主鍵看做是負責更新外鍵字段,這時若是雙方都維護的話,就是出現兩次update外鍵字段的sql

總結: 如下是OneToMany的最終方案

one:

mappedBy經過他指明,本身放棄維護外鍵,而參考Many端對外鍵的維護的實現
@OneToMany(mappedBy= "customer")  // EAGER當即加載  LAZY: 延遲加載
private Set<LinkMan> linkManSet = new HashSet<>();

Many

targetEntity: 指明One的一方的字節碼
name: 本表中的外鍵的列名, 由於在多的一方維護的外鍵
referencedColumnName:  外鍵引用的主鍵的列名
@ManyToOne(targetEntity:  = Customer.class)
@JoinColumn(name = "customer_id",referencedColumnName = "id")
private Customer customer;

一對多的級聯cascade

級聯操做再One的一端進行配置

類型 做用
ALL 級聯全部(推薦)
PERSIST 保存
MERGE 更新
REMOVE 刪除
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)

級聯保存: 同時存在One和Many兩個對象,咱們在保存One的同時級聯保存Many方的對象

級聯刪除:
狀況1: One的一方在維護主鍵, 這是的級聯刪除就會分兩步走 ,首先刪除外鍵,而後刪除One的一方,同時刪除One級聯的去所有Many方

狀況2: One的一方再也不維護主鍵,不能級聯刪除

多對多配置

  • 多對多配置中,一樣須要一方主動的放棄對外鍵維護權
  • 雙方維護着表明對方的set集合

例子: User和Role 多對多的關係

在User端,主動放棄對外鍵的維護權

@ManyToMany(mappedBy = "users",cascade = CascadeType.ALL)
public Set<Role> roles = new HashSet<>();

在Role端,維護着外鍵, 負責對中間表上外鍵的更新的操做

/**
 *  配置多對多
 *   1. 聲明關係的配置
 *   2. 配置中間表(包含兩個外鍵)
 *   targetEntity: 對方的 實體類字節碼
 *
 */
@ManyToMany(targetEntity =User.class)
@JoinTable(
        name = "user_role",// name 中間表名稱
        joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}, // 當前對象,在中間表中的外鍵名, 以及參照的本表的哪一個主鍵名
        inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")} // 對方對象在中間表的外鍵
)
public Set<User> users = new HashSet<>();

對象導航查詢

所謂對象導航查詢,就是首先使用jpa爲咱們提供的Repository查詢獲得結果對象,再經過該對象,使用該對象的get方法,進而查詢出它關聯的對象的操做

在一對多的關係中, get的屬性是 Set集合, 而在多對一的關係中,get的屬性是它維護的那個One端的引用

總結:

模式 做用
一查多 默認延遲加載,由於有可能一會兒級聯查詢出成百上千的數據,可是咱們卻不用
多查一 默認當即查詢,多查一條數據
相關文章
相關標籤/搜索