熟悉了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ù)表中 |