熟悉了mybatis的靈活骡送,可能你對他的動態(tài)查詢很喜歡昂羡,表示各種sql都能勝任。初步接觸jpa時摔踱,你會各種吐槽虐先,不如mybatis來的方便。其實jpa也能幫你完成你的各種需求派敷,至于編寫的復(fù)雜度蛹批,那可能就仁者見仁智者見智了撰洗。習(xí)慣了,其實也一樣了腐芍。
代碼放github和碼云了:spring-data/github spring-data/碼云
save操作(含merge操作差导,即update也在save里)
- save方法會預(yù)檢查該entity是否持久化,isNew會判斷該對象的Id類型 是否實現(xiàn)Persistable或EntityInformation進(jìn)行重寫isNew方法猪勇,如果Id是Number類型设褐,直接判斷value==0 true 執(zhí)行entityManager.persist 否則執(zhí)行entityManager.merge()
/**
* save方法會預(yù)檢查該entity是否持久化,isNew會判斷該對象的Id類型 是否實現(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會被填充為保存后entity的id
Customer savedEntity = customerRepository.save(customer);
//保存并立即刷新數(shù)據(jù)庫,由于customer以及提供id椅您,會執(zhí)行merge方法進(jìn)行保存
// Customer savedAndFlush = customerRepository.saveAndFlush(customer);
List<Customer> batchCustomers = Arrays.asList(new Customer(), new Customer());
//批量保存,saveAll是循環(huán)單挑插入外冀,并不是batch操作,數(shù)據(jù)較大使用時請注意性能
// List<Customer> batchSaves = customerRepository.saveAll(batchCustomers);
}
delete操作
- delte操作會先執(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單個刪除...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)險較大清空表)
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'; 如果需要模糊查詢需要手動拼接 % 連接符
customerRepository.findCustomersByNameLike("k");
//select * from customer where name like "%l";
customerRepository.findCustomersByNameStartingWith("l");
//select * from customer where name like "%k%";
customerRepository.findCustomersByNameContains("k");
/ /.....還有很多,不再一一列舉......
分頁固灵,排序
//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查詢(場景較少)
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語句查詢捅伤,語法跟sql類似,但注意基于entity的命名巫玻,如果屬性nativeQuery為ture則丛忆,必須用原生sql語句
@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實現(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語法仍秤,在拼裝返回結(jié)果集時熄诡,是根據(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òu)造函數(shù)會報錯瓣窄,無法解析轉(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 多對一
customer_group 顧客分組表
book 書籍表,customer是多對多姨谷。
book_detail 書籍詳細(xì)表逗宁,和book是一對一
- 四張表沒任何業(yè)務(wù),假象出來的梦湘,單純?yōu)榱蓑炞Cjpa查詢方式
表關(guān)系注解參數(shù)(@OneToOne @ManyToMany @ManyToOne @OneToMany )
Cascade | 級聯(lián)操作 |
---|---|
CascadeType. PERSIST | 級聯(lián)持久化 ( 保存 ) 操作 |
CascadeType. MERGE | 級聯(lián)更新 ( 合并 ) 操作 |
CascadeType. REFRESH | 級聯(lián)刷新操作瞎颗,只會查詢獲取操作 |
CascadeType. REMOVE | 級聯(lián)刪除操作 |
CascadeType. ALL | 級聯(lián)以上全部操作 |
- Fetch
抓取是否延遲加載,默認(rèn)情況一的方為立即加載践叠,多的一方為延遲加載言缤,可以手動指定Fetch.EAGER/Fetch.LAZY - mappedBy
關(guān)聯(lián)關(guān)系由此方屬性維護(hù)嚼蚀,可以理解成一對注解使用mappedBy的一方由另一方維護(hù)禁灼,且必須是注解作用下的屬性名。
可以根據(jù)關(guān)聯(lián)表的屬性作為條件查詢轿曙,結(jié)果同樣是根據(jù)兩次sql查詢出來的弄捕。通過關(guān)聯(lián)表的屬性進(jìn)行查詢時,使用關(guān)聯(lián) entityName_columnName方式导帝,或者直接使用 _columnName進(jìn)行查詢守谓。如果不想級聯(lián)查詢時,在一方不適用注解即可您单。
@OneToOne
一對一關(guān)聯(lián)關(guān)系斋荞,有三種形式存在:
- 兩張表共享主鍵pk,使用@PrimaryKeyJoinColumn來建立關(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ā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ān)聯(lián)關(guān)系蜈彼,使用@JoinColumn注解,如果不寫該注解俺驶,默認(rèn)會在此表中自動創(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)表的實體暮现,結(jié)果是根據(jù)兩次sql查詢出來的还绘,即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查詢出來的栖袋。通過關(guān)聯(lián)表的屬性進(jìn)行查詢時拍顷,使用關(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
- 通過@ManyToMany 注解定義多對多關(guān)系凭舶,同時通過 @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",//通過維護(hù)端的屬性關(guān)聯(lián)
fetch = FetchType.LAZY)
// 關(guān)系維護(hù)端刪除時爱沟,如果中間表存在些紀(jì)錄的關(guān)聯(lián)信息帅霜,則會刪除該關(guān)聯(lián)信息;
// 關(guān)系被維護(hù)端刪除時,如果中間表存在些紀(jì)錄的關(guān)聯(lián)信息呼伸,則會刪除失敗 .
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
- 注解和上面都差不多括享,無非就是誰關(guān)聯(lián)誰
@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)實體搂根,若使用接口作為關(guān)聯(lián)實體時,應(yīng)指明targetEntity的實現(xiàn)類铃辖,且接口應(yīng)繼承Serializable剩愧,否則無法被解析
// @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中無需注解犬第,注意joinColumn是table中的name
// @OneToMany(fetch = FetchType.EAGER)
// @JoinColumn(name = "group_type")
//oneToMany 雙向關(guān)聯(lián)锦积,customer中無需注解,mappedBy必須指向entity中的屬性名,即標(biāo)有@ManyToOne的屬性名歉嗓,且此處不可再使用@JoinColumn丰介。
@OneToMany(fetch = FetchType.EAGER, mappedBy = "customerGroup")
private List<Customer> customers;
//setter getter
}
其他注解
注解 | 解釋 |
---|---|
@Entity | 聲明一個類為實體Bean。 |
@Table | 說明此實體類映射的表名鉴分,目錄哮幢,schema的名字。 |
@Id | 聲明此表的主鍵冠场。 |
@GeneratedValue | 定義主鍵的增長策略家浇。我這里一般交給底層數(shù)據(jù)庫處理,所以調(diào)用了名叫g(shù)enerator的增長方式碴裙,由下邊的@GenericGenerator實現(xiàn)钢悲。 |
@GenericGenerator | hibernate內(nèi)部的主鍵增長方式。 |
@Version | 注解用于支持樂觀鎖版本控制舔株。一般可以用 數(shù)字 或者 timestamp 類型來支持 version. |
@Column | name 可選莺琳,列名(默認(rèn)值是屬性名); unique 可選,是否在該列上設(shè)置唯一約束(默認(rèn)值false);nullable 可選载慈,是否設(shè)置該列的值可以為空(默認(rèn)值true); insertable 可選惭等,該列是否作為生成的insert語句中的一個列(默認(rèn)值true);updatable 可選,該列是否作為生成的update語句中的一個列(默認(rèn)值true); columnDefinition 可選办铡,為這個特定列覆蓋SQL DDL片段 (這可能導(dǎo)致無法在不同數(shù)據(jù)庫間移植); table 可選辞做,定義對應(yīng)的表(默認(rèn)為主表);length 可選琳要,列長度(默認(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ū)傩裕瑢⒉粫怀志没ㄗ约簻y試框喳,只有放在getter方法內(nèi)才起作用) |
@Basic | 所有沒有定義注解的屬性课幕,等價于在其上面添加了 @Basic注解可以聲明屬性的獲取策略 ( fetch strategy ),fetch:抓取策略,延時加載與立即加載,optional:指定在生成數(shù)據(jù)庫結(jié)構(gòu)時字段是否允許為 null. |
@Temporal | 在核心的 Java API 中并沒有定義時間精度 ( temporal precision )五垮。因此處理時間類型數(shù)據(jù)時乍惊,你還需要定義將其存儲在數(shù)據(jù)庫中所預(yù)期的精度。 |
@Enumerated | 枚舉類型成員屬性映射,EnumType.STRING指定屬性映射為字符串,EnumType.ORDINAL指定屬性映射為數(shù)據(jù)序 |
@Lob | 用于標(biāo)注字段類型為Clob和Blob類型放仗,Clob(Character Large Ojects)類型是長字符串類型,實體的類型可為char[]润绎、Character[]、或者String類型匙监,Blob(Binary Large Objects)類型是字節(jié)類型,實體的類型可為byte[]凡橱、Byte[]、或者實現(xiàn)了Serializable接口的類亭姥。通常使用惰性加載的方式,@Basic(fetch=FetchType.LAZY) |
@SecondaryTable | (@javax.persistence.SecondaryTable)將一個實體映射到多個數(shù)據(jù)庫表中 |