spring data jpa 實(shí)戰(zhàn)之增刪改查(干貨!你想要的查詢!)

熟悉了mybatis的靈活,可能你對(duì)他的動(dòng)態(tài)查詢很喜歡,表示各種sql都能勝任。初步接觸jpa時(shí),你會(huì)各種吐槽,不如mybatis來(lái)的方便。其實(shí)jpa也能幫你完成你的各種需求,至于編寫的復(fù)雜度,那可能就仁者見(jiàn)仁智者見(jiàn)智了。習(xí)慣了,其實(shí)也一樣了。

代碼放github和碼云了:spring-data/github spring-data/碼云

save操作(含merge操作,即update也在save里)
  • save方法會(huì)預(yù)檢查該entity是否持久化,isNew會(huì)判斷該對(duì)象的Id類型 是否實(shí)現(xiàn)Persistable或EntityInformation進(jìn)行重寫isNew方法,如果Id是Number類型,直接判斷value==0 true 執(zhí)行entityManager.persist 否則執(zhí)行entityManager.merge()
  /**
   * save方法會(huì)預(yù)檢查該entity是否持久化,isNew會(huì)判斷該對(duì)象的Id類型 是否實(shí)現(xiàn)Persistable或EntityInformation進(jìn)行
   * 重寫isNew方法,如果Id是Number類型,直接判斷value==0 true 執(zhí)行entityManager.persist 否則執(zhí)行entityManager.merge()
   */
  @Test
  public void insert() {
    Customer customer = new Customer();
    customer.setName("lk");
    customer.setEmail("spring.jpa@163.com");
    customer.setAddress("Shanghai PuDong Area XueYe Road");
    customer.setPhone("13699999999");
    //這里保存以后customer的id會(huì)被填充為保存后entity的id
    Customer savedEntity = customerRepository.save(customer);
    //保存并立即刷新數(shù)據(jù)庫(kù),由于customer以及提供id,會(huì)執(zhí)行merge方法進(jìn)行保存
//    Customer savedAndFlush = customerRepository.saveAndFlush(customer);
    List<Customer> batchCustomers = Arrays.asList(new Customer(), new Customer());
    //批量保存,saveAll是循環(huán)單挑插入,并不是batch操作,數(shù)據(jù)較大使用時(shí)請(qǐng)注意性能
//    List<Customer> batchSaves = customerRepository.saveAll(batchCustomers);
  }
delete操作
  • delte操作會(huì)先執(zhí)行查詢(除了batch操作),在執(zhí)行刪除,若查詢不到結(jié)果,拋出異常(EmptyResultDataAccessException)不執(zhí)行刪除
  /**
   * 刪除操作,除了batch操作,其他方法均先查詢后刪除
   */
  @Test
  public void delete() {
    //select * from customer where id=?;delete from customer where id=?;
    //同delete(entity)
    customerRepository.deleteById(38L);
    //select * from customer;循環(huán)遍歷id單個(gè)刪除...delete from customer where id=?...
    customerRepository.deleteAll();
    Customer customer = new Customer();
    customer.setId(Long.valueOf(42L));
    Customer customerOther = new Customer();
    customerOther.setId(41L);
    List<Customer> deleteAll = Arrays.asList(customer,customerOther);
    //循環(huán)執(zhí)行delete(entity)
    customerRepository.deleteAll(deleteAll);
    //不查詢直接:delete from customer;(風(fēng)險(xiǎn)較大清空表)
    customerRepository.deleteAllInBatch();
    //不查詢直接:delete from customer where id=? or id=?
    customerRepository.deleteInBatch(deleteAll);
  }

最常用的query操作

jpa 官方查詢關(guān)鍵字
Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)
單表字段查詢
    //select * from customer;
    customerRepository.findAll();
    //select * from customer where id = 1;
    customerRepository.findById(1L);
    //select * from customer where address = "address";
    customerRepository.findCustomerByAddress("address");
    //select * from customer where name = "lk" and phone = "133";
    customerRepository.findCustomersNameAndPhone("133", "lk");
    //select * from customer where name like '%k';
    customerRepository.findCustomersNameLike("k");
    //select * from customer where name like 'k'; 如果需要模糊查詢需要手動(dòng)拼接 % 連接符
    customerRepository.findCustomersByNameLike("k");
    //select * from customer where name like "%l";
    customerRepository.findCustomersByNameStartingWith("l");
    //select * from customer where name like "%k%";
    customerRepository.findCustomersByNameContains("k");
    / /.....還有很多,不再一一列舉......
分頁(yè),排序
    //select * from customer order by name desc;
    customerRepository.findAll(Sort.by(Direction.DESC, "name"));
    //select * from customer limit 0,10;
    customerRepository.findAll(PageRequest.of(0, 10));
example查詢(場(chǎng)景較少)
    Customer customer = new Customer();
    customer.setAddress("address");
    //select * from customer where address ="address";
    customerRepository.findAll(Example.of(customer));
    customer.setName("lk");
    customer.setPhone("133");
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withMatcher("name", match -> match.contains())
        .withMatcher("phone", match -> match.startsWith());
    //select * from customer where name like '%lk%" and phone like '133%' and address = "address";
    customerRepository.findOne(Example.of(customer, matcher));
namedQuery 也是自定義的@Query的一種
//entity
@Entity(name = "Customer")
@NamedQuery(name = "Customer.findByNameNQ", query = "select c from Customer c where name =?1")
public class Customer {}

//repository定義方法findByNameNQ
List<Customer> findByNameNQ(String name);

//test
//namedQuery:select * from customer where name = "lk";
customerRepository.findByNameNQ("lk");
@Query自定義JQL語(yǔ)句查詢,語(yǔ)法跟sql類似,但注意基于entity的命名,如果屬性nativeQuery為ture則,必須用原生sql語(yǔ)句
  @Query("select c from Customer c where name like %?1")
  List<Customer> findCustomersNameLike(String name);

  @Query("select c from Customer c where name = :name and phone = :phone")
  List<Customer> findCustomersNameAndPhone(@Param("phone") String phone,
      @Param("name") String name);

  @Query(value = "select * from customer where name =?1",nativeQuery = true)
  List<Customer> findByNameSql(String name);
@Modify 配合 @Query實(shí)現(xiàn) 修改部分字段
  @Modifying
  @Query("update Customer c set c.name = :name where c.id = :id")
  int modifyByPhone(@Param("name") String name,@Param("id") Long id);

  @Modifying
  @Query("delete from Customer c  where c.id = ?1")
  int deleteCustomer(Long id);

自定義返回值,基于JQL語(yǔ)法,在拼裝返回結(jié)果集時(shí),是根據(jù)構(gòu)造函數(shù)進(jìn)行組裝的,可以基于接口或者類,要保證屬性是entity內(nèi)的屬性。也可以借助@Query,使用 new map()返回map,或者new Class返回想要的結(jié)果。

  //repository

  List<NameOnlyI> findCustomersByName(String name);

  List<NameOnly> findByName(String name);

  @Query("select new com.spring.jpa.beans.NameOnly(name,address) from Customer where name = ?1")
  List<NameOnly> findByName4Obj(String name);

  @Query("select new map(name as myname,address as myaddress) from Customer where name = :name")
  List<Map<String, Object>> findByName4Map(@Param("name") String name);

//test

    //基于接口的返回值
    List<NameOnlyI> interfaces = customerRepository.findCustomersByName("lk1");
    //基于類的返回值,如果有兩個(gè)構(gòu)造函數(shù)會(huì)報(bào)錯(cuò),無(wú)法解析轉(zhuǎn)換
    List<NameOnly> nameOnlies = customerRepository.findByName("lk1");
    //基于類的返回值,@Query顯式聲明返回bean
    List<NameOnly> objs = customerRepository.findByName4Obj("lk1");
    //@Query返回map 用as做key,不用as默認(rèn)key是0,1,2...
    List<Map<String, Object>> maps = customerRepository.findByName4Map("lk1");

復(fù)雜關(guān)聯(lián)關(guān)系查詢 @OneToOne @ManyToMany @ManyToOne @OneToMany

customer 顧客表,和customer_group 多對(duì)一
customer_group 顧客分組表
book 書(shū)籍表,customer是多對(duì)多。
book_detail 書(shū)籍詳細(xì)表,和book是一對(duì)一

  • 四張表沒(méi)任何業(yè)務(wù),假象出來(lái)的,單純?yōu)榱蓑?yàn)證jpa查詢方式
表關(guān)系注解參數(shù)(@OneToOne @ManyToMany @ManyToOne @OneToMany )
Cascade 級(jí)聯(lián)操作
CascadeType. PERSIST 級(jí)聯(lián)持久化 ( 保存 ) 操作
CascadeType. MERGE 級(jí)聯(lián)更新 ( 合并 ) 操作
CascadeType. REFRESH 級(jí)聯(lián)刷新操作,只會(huì)查詢獲取操作
CascadeType. REMOVE 級(jí)聯(lián)刪除操作
CascadeType. ALL 級(jí)聯(lián)以上全部操作
  • Fetch
    抓取是否延遲加載,默認(rèn)情況一的方為立即加載,多的一方為延遲加載,可以手動(dòng)指定Fetch.EAGER/Fetch.LAZY
  • mappedBy
    關(guān)聯(lián)關(guān)系由此方屬性維護(hù),可以理解成一對(duì)注解使用mappedBy的一方由另一方維護(hù),且必須是注解作用下的屬性名。
可以根據(jù)關(guān)聯(lián)表的屬性作為條件查詢,結(jié)果同樣是根據(jù)兩次sql查詢出來(lái)的。通過(guò)關(guān)聯(lián)表的屬性進(jìn)行查詢時(shí),使用關(guān)聯(lián) entityName_columnName方式,或者直接使用 _columnName進(jìn)行查詢。如果不想級(jí)聯(lián)查詢時(shí),在一方不適用注解即可。

@OneToOne

一對(duì)一關(guān)聯(lián)關(guān)系,有三種形式存在:

  • 兩張表共享主鍵pk,使用@PrimaryKeyJoinColumn來(lái)建立關(guān)聯(lián)關(guān)系
@Entity
public class A {
  @Id
  private Long id;
  @OneToOne(cascade = CascadeType.ALL)
  @PrimaryKeyJoinColumn
  private B b;
}

@Entity
public class B {
  @Id
  private Long id;
}
  • 通過(guò)中間表建立關(guān)聯(lián)關(guān)系,使用@JoinTable注解,joinColumns指定本表和關(guān)聯(lián)表的外鍵,inverseJoinColumns指定關(guān)聯(lián)關(guān)系另一方和關(guān)聯(lián)表的外鍵
@Entity
public class A {
  @Id
  private Long id;
  @OneToOne(cascade = CascadeType.ALL)
  joinColumns = @JoinColumn(name="a_fk"),
  inverseJoinColumns = @JoinColumn(name="b_fk")
  private B b;
}

@Entity
public class B {
  @Id
  private Long id;
  @OneToOne(mappedBy="b")
  private A a;
}
  • 通過(guò)外鍵,唯一約束指定關(guān)聯(lián)關(guān)系,使用@JoinColumn注解,如果不寫該注解,默認(rèn)會(huì)在此表中自動(dòng)創(chuàng)建連接列:主表屬性_關(guān)聯(lián)表主鍵名稱
@Entity(name = "book")
public class Book {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;
  private Integer count;

  @OneToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = "detail_id",referencedColumnName = "id")
//referencedColumnName 不寫默認(rèn)是主鍵,當(dāng)不是主鍵是可以用此聲明,但必須保證連接鍵在連接表是唯一約束的
  private BookDetail bookDetail;

//setter getter
}

@Entity(name = "book_detail")
public class BookDetail {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String size;
//不需要依賴book可以不寫關(guān)聯(lián)屬性

//setter getter
}

public interface BookRepository extends JpaRepository<Book, Long> {

  //可以查出關(guān)聯(lián)表的實(shí)體,結(jié)果是根據(jù)兩次sql查詢出來(lái)的,即select * from book where name = ?;->查出detail_id->select * from book_detail where id = ?;
  List<Book> findByName(String name);
  //可以根據(jù)關(guān)聯(lián)表的屬性作為條件查詢,結(jié)果同樣是根據(jù)兩次sql查詢出來(lái)的。通過(guò)關(guān)聯(lián)表的屬性進(jìn)行查詢時(shí),使用關(guān)聯(lián) entityName_columnName方式,或者直接使用 _columnName進(jìn)行查詢。
  List<Book> findByNameAndBookDetail_Id(String name, Long id);

  //自定義返回值,一次查詢返回結(jié)果
  @Query("select new com.spring.jpa.beans.BookResult(b.id as id,b.name as name ,d.size as size) from Book as b left join BookDetail as d on b.bookDetail = d.id")
  List<BookResult> findResults();

}
@ManyToMany
  • 通過(guò)@ManyToMany 注解定義多對(duì)多關(guān)系,同時(shí)通過(guò) @JoinTable 注解描述關(guān)聯(lián)表和關(guān)聯(lián)條件。
// 維護(hù)端注解
@Entity
public class A {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @ManyToMany (cascade = CascadeType.REFRESH)
  @JoinTable (
           name =  "a_b" , //關(guān)聯(lián)表名
           inverseJoinColumns =  @JoinColumn (name =  "a_id" ),//被維護(hù)端外鍵
           joinColumns =  @JoinColumn (name =  "b_id" ))//維護(hù)端外鍵被維護(hù)端注解
   private B b;

//setter getter
}

@Entity
public class B {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @ManyToMany(cascade = CascadeType.REFRESH,
            mappedBy = "b",//通過(guò)維護(hù)端的屬性關(guān)聯(lián)
            fetch = FetchType.LAZY)
// 關(guān)系維護(hù)端刪除時(shí),如果中間表存在些紀(jì)錄的關(guān)聯(lián)信息,則會(huì)刪除該關(guān)聯(lián)信息;
// 關(guān)系被維護(hù)端刪除時(shí),如果中間表存在些紀(jì)錄的關(guān)聯(lián)信息,則會(huì)刪除失敗 .
  private A a;

//setter getter
}
  • 默認(rèn)joinColumn值:關(guān)聯(lián)表名:主表表名 + 下劃線 + 從表表名;關(guān)聯(lián)表到主表的外鍵:主表表名 + 下劃線 + 主表中主鍵列名;關(guān)聯(lián)表到從表的外鍵名:主表中用于關(guān)聯(lián)的屬性名+ 下劃線 + 從表的主鍵列名
@ManyToOne @OneToMany
  • 注解和上面都差不多,無(wú)非就是誰(shuí)關(guān)聯(lián)誰(shuí)
@Entity
@NamedQuery(name = "Customer.findByNameNQ", query = "select c from Customer c where name =?1")
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
  private String email;
  private String address;
  private String phone;

//  //targetEntity 默認(rèn)是關(guān)聯(lián)實(shí)體,若使用接口作為關(guān)聯(lián)實(shí)體時(shí),應(yīng)指明targetEntity的實(shí)現(xiàn)類,且接口應(yīng)繼承Serializable,否則無(wú)法被解析
//  @ManyToOne(fetch = FetchType.EAGER,targetEntity = CustomerGroup.class)
//  @JoinColumn(name = "group_type",referencedColumnName = "type")
//  private CustomerGroupInterface customerGroup;

  //manyToOne 單向關(guān)聯(lián)或 oneToMany雙向關(guān)聯(lián)
//  //這里使用非主鍵作為外鍵關(guān)聯(lián),type在customer_group表中唯一約束,也可以使用@JoinTable 處理關(guān)聯(lián)表做連接這里不再演示
  @ManyToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
  @JoinColumn(name = "group_type",referencedColumnName = "type")
  private CustomerGroup customerGroup;

  //customerGroup為一的一方,單向關(guān)聯(lián)customer(多方)
//  @Column(name = "group_type")
//  private String type;

//setter getter
}

@Entity
@Table(name = "customer_group")
public class CustomerGroup implements CustomerGroupInterface {

  private static final long serialVersionUID = -6956725658881048590L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(unique = true)
  private String type;
  private String name;
  private Integer level;

  //oneToMany 單向關(guān)聯(lián),customer中無(wú)需注解,注意joinColumn是table中的name
//  @OneToMany(fetch = FetchType.EAGER)
//  @JoinColumn(name = "group_type")

  //oneToMany 雙向關(guān)聯(lián),customer中無(wú)需注解,mappedBy必須指向entity中的屬性名,即標(biāo)有@ManyToOne的屬性名,且此處不可再使用@JoinColumn。
  @OneToMany(fetch = FetchType.EAGER, mappedBy = "customerGroup")
  private List<Customer> customers;

//setter getter
}
其他注解
注解 解釋
@Entity 聲明一個(gè)類為實(shí)體Bean。
@Table 說(shuō)明此實(shí)體類映射的表名,目錄,schema的名字。
@Id 聲明此表的主鍵。
@GeneratedValue 定義主鍵的增長(zhǎng)策略。我這里一般交給底層數(shù)據(jù)庫(kù)處理,所以調(diào)用了名叫g(shù)enerator的增長(zhǎng)方式,由下邊的@GenericGenerator實(shí)現(xiàn)。
@GenericGenerator hibernate內(nèi)部的主鍵增長(zhǎng)方式。
@Version 注解用于支持樂(lè)觀鎖版本控制。一般可以用 數(shù)字 或者 timestamp 類型來(lái)支持 version.
@Column name 可選,列名(默認(rèn)值是屬性名); unique 可選,是否在該列上設(shè)置唯一約束(默認(rèn)值false);nullable 可選,是否設(shè)置該列的值可以為空(默認(rèn)值true); insertable 可選,該列是否作為生成的insert語(yǔ)句中的一個(gè)列(默認(rèn)值true);updatable 可選,該列是否作為生成的update語(yǔ)句中的一個(gè)列(默認(rèn)值true); columnDefinition 可選,為這個(gè)特定列覆蓋SQL DDL片段 (這可能導(dǎo)致無(wú)法在不同數(shù)據(jù)庫(kù)間移植); table 可選,定義對(duì)應(yīng)的表(默認(rèn)為主表);length 可選,列長(zhǎng)度(默認(rèn)值255);precision 可選,列十進(jìn)制精度(decimal precision)(默認(rèn)值0);scale 可選,如果列十進(jìn)制數(shù)值范圍(decimal scale)可用,在此設(shè)置(默認(rèn)值0)
@Index 某一字段加索引 @Table(name = "customer", indexes = {@Index(columnList = "name")}),給name字段加上索引
@Transient 被注解成 @Transient 的 getter 方法或?qū)傩?,將不?huì)被持久化(自己測(cè)試,只有放在getter方法內(nèi)才起作用)
@Basic 所有沒(méi)有定義注解的屬性,等價(jià)于在其上面添加了 @Basic注解可以聲明屬性的獲取策略 ( fetch strategy ),fetch:抓取策略,延時(shí)加載與立即加載,optional:指定在生成數(shù)據(jù)庫(kù)結(jié)構(gòu)時(shí)字段是否允許為 null.
@Temporal 在核心的 Java API 中并沒(méi)有定義時(shí)間精度 ( temporal precision )。因此處理時(shí)間類型數(shù)據(jù)時(shí),你還需要定義將其存儲(chǔ)在數(shù)據(jù)庫(kù)中所預(yù)期的精度。
@Enumerated 枚舉類型成員屬性映射,EnumType.STRING指定屬性映射為字符串,EnumType.ORDINAL指定屬性映射為數(shù)據(jù)序
@Lob 用于標(biāo)注字段類型為Clob和Blob類型,Clob(Character Large Ojects)類型是長(zhǎng)字符串類型,實(shí)體的類型可為char[]、Character[]、或者String類型,Blob(Binary Large Objects)類型是字節(jié)類型,實(shí)體的類型可為byte[]、Byte[]、或者實(shí)現(xiàn)了Serializable接口的類。通常使用惰性加載的方式,@Basic(fetch=FetchType.LAZY)
@SecondaryTable (@javax.persistence.SecondaryTable)將一個(gè)實(shí)體映射到多個(gè)數(shù)據(jù)庫(kù)表中

最后寫的比較急躁,代碼放github和碼云了:spring-data/github spring-data/碼云

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容