本文參考了Spring Data JPA官方文檔,引用了部分文檔的代碼读拆。
Spring Data JPA是Spring基于Hibernate開發(fā)的一個JPA框架汉形。如果用過Hibernate或者M(jìn)yBatis的話纸镊,就會知道對象關(guān)系映射(ORM)框架有多么方便倍阐。但是Spring Data JPA框架功能更進(jìn)一步,為我們做了 一個數(shù)據(jù)持久層框架幾乎能做的任何事情逗威。下面來逐步介紹它的強(qiáng)大功能峰搪。
添加依賴
我們可以簡單的聲明Spring Data JPA的單獨(dú)依賴項。以Gradle為例凯旭,依賴項如下概耻,Spring Data JPA會自動添加它的Spring依賴項。當(dāng)前版本需要Spring框架版本為4.3.7.RELEASE
或更新罐呼,使用舊版本的Spring框架可能會出現(xiàn)bug鞠柄。由于Spring Data JPA基于Hibernate,所以別忘了添加Hibernate的依賴項嫉柴。
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.1.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.8.Final'
基本使用
創(chuàng)建環(huán)境
Spring Data JPA也是一個JPA框架春锋,因此我們需要數(shù)據(jù)源、JPA Bean差凹、數(shù)據(jù)庫驅(qū)動期奔、事務(wù)管理器等等。下面以XML配置為例危尿,我們來配置一下所需的Bean呐萌。重點(diǎn)在于<jpa:repositories base-package="yitian.study.dao"/>
一句,它告訴Spring去哪里尋找并創(chuàng)建這些接口類谊娇。
<!--啟用注解配置和包掃描-->
<context:annotation-config/>
<context:component-scan base-package="yitian.study"/>
<!--創(chuàng)建Spring Data JPA實例對象-->
<jpa:repositories base-package="yitian.study.dao"/>
<!--數(shù)據(jù)源-->
<bean id="dataSource"
class="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource">
<property name="useSSL" value="false"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="12345678"/>
</bean>
<!--JPA工廠對象-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="yitian.study.entity"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true"/>
<property name="showSql" value="true"/>
</bean>
</property>
</bean>
<!--事務(wù)管理器-->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--事務(wù)管理-->
<tx:advice id="transactionAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="daoPointCut" expression="execution(* yitian.study.dao.*.*(..))"/>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="daoPointCut"/>
</aop:config>
創(chuàng)建DAO對象
前幾天學(xué)了一點(diǎn)Groovy肺孤,再回頭看看Java,實在是麻煩济欢。所以這里我用Groovy寫的實體類赠堵,不過語法和Java很相似。大家能看懂意思即可法褥。不過確實Groovy能比Java少些很多代碼茫叭,對開發(fā)挺有幫助的。有興趣的同學(xué)可以看看我的Groovy學(xué)習(xí)筆記半等。
Groovy類的字段默認(rèn)是私有的揍愁,方法默認(rèn)是公有的,分號可以省略杀饵,對于默認(rèn)字段Groovy編譯器還會自動生成Getter和Setter莽囤,可以減少不少代碼量。只不過equals等方法不能自動生成切距,多少有點(diǎn)遺憾朽缎。這里使用了JPA注解,建立了一個實體類和數(shù)據(jù)表的映射。
@Entity
class User {
@Id
@GeneratedValue
int id
@Column(unique = true, nullable = false)
String username
@Column(nullable = false)
String nickname
@Column
String email
@Column
LocalDate birthday
@Column(nullable = false)
LocalDateTime registerTime
String toString() {
"User(id:$id,username:$username,nickname:$nickname,email:$email,birthday:$birthday,registerTime:$registerTime)"
}
}
然后就是Spring Data JPA的魔法部分了话肖!我們繼承Spring提供的一個接口北秽,放到前面jpa:repositories
指定的包下。
interface CommonUserRepository extends CrudRepository<User, Integer> {
}
然后測試一下狼牺,會發(fā)生什么事情呢?查看一下數(shù)據(jù)庫就會發(fā)現(xiàn)數(shù)據(jù)已經(jīng)成功插入了礼患。好吧是钥,好像沒什么有魔力的事情。
@RunWith(SpringRunner)
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
class DaoTest {
@Autowired
CommonUserRepository commonUserRepository
@Test
void testCrudRepository() {
User user = new User(username: 'yitian', nickname: '易天', registerTime: LocalDateTime.now())
commonUserRepository.save(user)
}
}
這次我們在接口中再定義一個方法缅叠。
interface CommonUserRepository extends CrudRepository<User, Integer> {
List<User> getByUsernameLike(String username)
}
我們再測試一下悄泥。這里也是用的Groovy代碼,意思應(yīng)該很容易懂肤粱,就是循環(huán)20次弹囚,然后插入20個用戶,用戶的名字和郵箱都是由循環(huán)變量生成的领曼。然后調(diào)用我們剛剛的方法鸥鹉。這次真的按照我們的要求查詢出了用戶名以2結(jié)尾的所有用戶!
@Test
void testCrudRepository() {
(1..20).each {
User user = new User(username: "user$it", nickname: "用戶$it", email: "user$it@yitian.com", registerTime: LocalDateTime.now())
commonUserRepository.save(user)
}
List<User> users = commonUserRepository.getByUsernameLike('%2')
println(users)
}
//結(jié)果如下
//[User(id:3,username:user2,nickname:用戶2,email:user2@yitian.com,birthday:null,registerTime:2017-03-08T20:25:58), User(id:13,username:user12,nickname:用戶12,email:user12@yitian.com,birthday:null,registerTime:2017-03-08T20:25:59)]
Spring Data 接口
從上面的例子中我們可以看到Spring Data JPA的真正功能了庶骄。我們只要繼承它提供的接口毁渗,然后按照命名規(guī)則定義相應(yīng)的查詢方法。Spring就會自動創(chuàng)建實現(xiàn)了該接口和查詢方法的對象单刁,我們直接使用就可以了灸异。也就是說,Spring Data JPA連查詢方法都可以幫我們完成羔飞,我們幾乎什么也不用干了肺樟。
下面來介紹一下Spring的這些接口。上面的例子中逻淌,我們繼承了CrudRepository
接口么伯。CrudRepository
接口的定義如下。如果我們需要增刪查改功能卡儒。只需要繼承該接口就可以立即獲得該接口的所有功能蹦狂。CrudRepository
接口有兩個泛型參數(shù),第一個參數(shù)是實際儲存的類型朋贬,第二個參數(shù)是主鍵凯楔。
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity);
T findOne(ID primaryKey);
Iterable<T> findAll();
Long count();
void delete(T entity);
boolean exists(ID primaryKey);
// … more functionality omitted.
}
CrudRepository
接口雖然方便,但是暴露了增刪查改的所有方法锦募,如果你的DAO層不需要某些方法摆屯,就不要繼承該接口。Spring提供了其他幾個接口,org.springframework.data.repository.Repository
接口沒有任何方法虐骑。
如果對數(shù)據(jù)訪問需要詳細(xì)控制准验,就可以使用該接口。PagingAndSortingRepository
接口則提供了分頁和排序功能廷没。PagingAndSortingRepository
接口的方法接受額外的Pagable和Sort對象糊饱,用來指定獲取結(jié)果的頁數(shù)和排序方式。返回類型則是Page<T>類型颠黎,我們可以調(diào)用它的方法獲取總頁數(shù)和可迭代的數(shù)據(jù)集合另锋。下面是一個Groovy寫的例子。注意Pageable是一個接口狭归,如果我們需要創(chuàng)建Pageable對象夭坪,使用PageRequest類并指定獲取的頁數(shù)和每頁的數(shù)據(jù)量。頁是從0開始計數(shù)的过椎。
@Test
void testPagingRepository() {
int countPerPage = 5
long totalCount = pageableUserRepository.count()
int totalPage = totalCount % 5 == 0L ? totalCount / 5 : totalCount / 5 + 1
(0..totalPage - 1).each {
Page<User> users = pageableUserRepository.findAll(new PageRequest(it, countPerPage))
println "第${it}頁數(shù)據(jù)室梅,共${users.totalPages}頁"
users.each {
println it
}
}
}
查詢方法
查詢方法可以由我們聲明的命名查詢生成,也可以像前面那樣由方法名解析疚宇。下面是官方文檔的例子亡鼠。方法名稱規(guī)則如下。如果需要詳細(xì)說明的話可以查看官方文檔Appendix C: Repository query keywords一節(jié)敷待。
- 方法名以
find…By
,read…By
,query…By
,count…By
和get…By
做開頭拆宛。在By之前可以添加Distinct表示查找不重復(fù)數(shù)據(jù)。By之后是真正的查詢條件讼撒。 - 可以查詢某個屬性浑厚,也可以使用條件進(jìn)行比較復(fù)雜的查詢,例如
Between
,LessThan
,GreaterThan
,Like
根盒,And
,Or
等钳幅。 - 字符串屬性后面可以跟
IgnoreCase
表示不區(qū)分大小寫,也可以后跟AllIgnoreCase
表示所有屬性都不區(qū)分大小寫炎滞。 - 可以使用
OrderBy
對結(jié)果進(jìn)行升序或降序排序敢艰。 - 可以查詢屬性的屬性,直接將幾個屬性連著寫即可册赛,如果可能出現(xiàn)歧義屬性钠导,可以使用下劃線分隔多個屬性。
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// 唯一查詢
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// 對某一屬性不區(qū)分大小寫
List<Person> findByLastnameIgnoreCase(String lastname);
// 所有屬性不區(qū)分大小寫
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// 啟用靜態(tài)排序
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
//查詢Person.Address.ZipCode
List<Person> findByAddressZipCode(ZipCode zipCode);
//避免歧義可以這樣
List<Person> findByAddress_ZipCode(ZipCode zipCode);
如果需要限制查詢結(jié)果也很簡單森瘪。
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
如果查詢很費(fèi)時間牡属,也可以方便的使用異步查詢。只要添加@Async注解扼睬,然后將返回類型設(shè)定為異步的即可逮栅。
@Async
Future<User> findByFirstname(String firstname);
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
@Async
ListenableFuture<User> findOneByLastname(String lastname);
Spring Data擴(kuò)展功能
Querydsl擴(kuò)展
Querydsl擴(kuò)展能讓我們以流式方式代碼編寫查詢方法。該擴(kuò)展需要一個接口QueryDslPredicateExecutor
,它定義了很多查詢方法措伐。
public interface QueryDslPredicateExecutor<T> {
T findOne(Predicate predicate);
Iterable<T> findAll(Predicate predicate);
long count(Predicate predicate);
boolean exists(Predicate predicate);
// … more functionality omitted.
}
只要我們的接口繼承了該接口特纤,就可以使用該接口提供的各種方法了。
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
}
查詢方法可以這樣簡單的編寫侥加。
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
Spring Web Mvc集成
這個功能需要我們引入Spring Web Mvc的相應(yīng)依賴包捧存。然后在程序中啟用Spring Data支持。使用Java配置的話担败,在配置類上添加@EnableSpringDataWebSupport注解昔穴。
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }
使用XML配置的話,添加下面的Bean聲明氢架。
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- 如果使用Spring HATEOAS 的話用下面這個替換上面這個 -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
不管使用哪種方式傻咖,都會向Spring額外注冊幾個組件朋魔,支持Spring Data的額外功能岖研。首先會注冊一個DomainClassConverter
,它可以自動將查詢參數(shù)或者路徑參數(shù)轉(zhuǎn)換為領(lǐng)域模型對象警检。下面的例子中孙援,Spring Data會自動用主鍵查詢對應(yīng)的用戶,然后我們直接就可以從處理方法參數(shù)中獲得用戶實例扇雕。注意拓售,Spring Data需要調(diào)用findOne
方法查詢對象,現(xiàn)版本下我們必須繼承CrudRepository
镶奉,才能實現(xiàn)該功能础淤。
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
另外Spring會注冊HandlerMethodArgumentResolver
、PageableHandlerMethodArgumentResolver
和SortHandlerMethodArgumentResolver
等幾個實例哨苛。它們支持從請求參數(shù)中讀取分頁和排序信息鸽凶。
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired UserRepository repository;
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
對于上面的例子,如果在請求參數(shù)中包含sort建峭、page玻侥、size等幾個參數(shù),它們就會被映射為Spring Data的Pageable和Sort對象亿蒸。請求參數(shù)的詳細(xì)信息如下凑兰。
- page 想要獲取的頁數(shù),默認(rèn)是0边锁,以零開始計數(shù)的姑食。
- size 每頁的數(shù)據(jù)大小,默認(rèn)是20.
- 數(shù)據(jù)的排序規(guī)則茅坛,默認(rèn)是升序矢门,也可以對多個屬性執(zhí)行排序,這時候需要多個sort參數(shù),例如
?sort=firstname&sort=lastname,asc
如果需要多個分頁對象祟剔,我們可以用@Qualifier注解隔躲,然后請求對象就可以寫成foo_page
,bar_page
這樣的了物延。
public String showUsers(Model model,
@Qualifier("foo") Pageable first,
@Qualifier("bar") Pageable second) { … }
如果需要自定義這些行為宣旱,可以讓配置類繼承SpringDataWebConfiguration
基類,然后重寫pageableResolver()
和sortResolver()
方法叛薯。這樣就不需要使用@EnableXXX注解了浑吟。
最后一個功能就是Querydsl 了。如果相關(guān)Jar包在類路徑上耗溜,@EnableSpringDataWebSupport
注解同樣會啟用該功能组力。比方說,在前面的例子中抖拴,如果在用戶用戶參數(shù)上添加下面的查詢參數(shù)燎字。
?firstname=Dave&lastname=Matthews
那么就會被QuerydslPredicateArgumentResolver
解析為下面的查詢語句。
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
還可以將QuerydslPredicate
注解到對應(yīng)類型的方法參數(shù)上阿宅,Spring會自動實例化相應(yīng)的參數(shù)候衍。為了Spring能夠準(zhǔn)確找到應(yīng)該查找什么領(lǐng)域?qū)ο螅覀冏詈弥付╮oot屬性洒放。
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
官方文檔的其他內(nèi)容
JPA命名查詢
如果查詢方法不能完全滿足需要蛉鹿,我們可以使用自定義查詢來滿足需求。使用XML配置的話往湿,在類路徑下添加META/orm.xml
文件妖异,類似下面這樣。我們用named-query
就定義命名查詢了领追。
<?xml version="1.0" ?>
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
<named-query name="User.findByNickname">
<query>select u from User u where u.nickname=?1</query>
</named-query>
</entity-mappings>
還可以使用注解他膳,在對應(yīng)實體類上注解命名查詢。
@Entity
@NamedQuery(name = "User.findByNickname",
query = "select u from User u where u.nickname=?1")
public class User {
}
之后蔓腐,在接口中聲明對應(yīng)名稱的查詢方法矩乐。這樣我們就可以使用JPQL語法自定義查詢方法了。
List<User> findByNickname(String nickname)
使用Query注解
在上面的方法中回论,查詢方法和JPQL是對應(yīng)的散罕,但是卻不在同一個地方定義。如果查詢方法很多的話傀蓉,查找和修改就很麻煩欧漱。這時候可以改用@Query注解。下面的例子直接在方法上定義了JPQL語句葬燎,如果需要引用orm.xml文件中的查詢語句误甚,使用注解的name屬性缚甩,如果沒有指定,會使用領(lǐng)域模型名.方法名
作為命名查詢語句的名稱窑邦。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
細(xì)心的同學(xué)會發(fā)現(xiàn)擅威,該注解還有一個nativeQuery屬性,用作直接執(zhí)行SQL使用冈钦。如果我們將該屬性指定為true郊丛,查詢語句也要相應(yīng)的修改為SQL語句。
Modifying注解
@Modifying注解用來指定某個查詢是一個更新操作瞧筛,這樣可以讓Spring執(zhí)行相應(yīng)的優(yōu)化厉熟。
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
投影
有時候數(shù)據(jù)庫和實體類之間并不存在一一對應(yīng)的關(guān)系,或者根據(jù)某些情況需要隱藏數(shù)據(jù)庫中的某些字段较幌。這可以通過投影實現(xiàn)揍瑟。來看看Spring的例子。
假設(shè)有下面的實體類和倉庫乍炉。我們在獲取人的時候會順帶獲取它的地址绢片。
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String street, state, country;
…
}
interface PersonRepository extends CrudRepository<Person, Long> {
Person findPersonByFirstName(String firstName);
}
如果不希望同時獲取地址的話,可以定義一個新接口恩急,其中定義一些Getter方法杉畜,暴露你需要的屬性纪蜒。然后倉庫方法也做相應(yīng)修改衷恭。
interface NoAddresses {
String getFirstName();
String getLastName();
}
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
利用@Value注解和SpEl,我們可以靈活的組織屬性纯续。例如下面随珠,定義一個接口,重命名了lastname屬性猬错。關(guān)于Spring表達(dá)式窗看,可以看看我的文章Spring EL 簡介。
interface RenamedProperty {
String getFirstName();
@Value("#{target.lastName}")
String getName();
}
或者組合多個屬性也可以倦炒,下面的例子將姓和名組合成全名显沈。Spring El的使用很靈活,合理使用可以達(dá)到事半功倍的效果逢唤。
interface FullNameAndCountry {
@Value("#{target.firstName} #{target.lastName}")
String getFullName();
@Value("#{target.address.country}")
String getCountry();
}
規(guī)范
這里說的規(guī)范指的是JPA 2 引入的新的編程方式實現(xiàn)查詢的規(guī)范拉讯。其他框架比如Hibernate也廢棄了自己的Criteria查詢方法,改為使用JPA規(guī)范的Criteria鳖藕。這種方式的好處就是完全是編程式的魔慷,不需要額外的功能,使用IDE的代碼提示功能即可著恩。但是我個人不太喜歡院尔,一來沒怎么詳細(xì)了解蜻展,二來感覺不如JPQL這樣的查詢簡單粗暴。
廢話不多說邀摆,直接看官方的例子吧纵顾。首先倉庫接口需要繼承JpaSpecificationExecutor接口。
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
…
}
這樣倉庫接口就繼承了一組以Specification接口作參數(shù)的查詢方法栋盹,類似下面這樣片挂。
List<T> findAll(Specification<T> spec);
而Specification又是這么個東西。所以我們要使用JPA規(guī)范的查詢方法贞盯,就需要實現(xiàn)toPredicate方法音念。
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
官方文檔有這么個例子,這個類中包含了多個靜態(tài)方法躏敢,每個方法都返回一個實現(xiàn)了的Specification對象闷愤。
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get(_Customer.createdAt), date);
}
};
}
//其他方法
}
之后我們將Specification對象傳遞給倉庫中定義的方法即可。
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
多個規(guī)范組合起來的查詢也可以件余。
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
Example查詢
前段時間在研究Spring的時候讥脐,發(fā)現(xiàn)Spring對Hibernate有一個封裝類HibernateTemplate
,它將Hibernate的Session
封裝起來啼器,由Spring的事務(wù)管理器管理旬渠,我們只需要調(diào)用HibernateTemplate
的方法即可。在HibernateTemplate
中有一組Example方法我沒搞明白啥意思端壳,后來才發(fā)現(xiàn)這是Spring提供的一組簡便查詢方式告丢。不過這種查詢方式的介紹居然在Spring Data這個框架中。
這種方式的優(yōu)點(diǎn)就是比較簡單损谦,如果使用上面的JPA規(guī)范岖免,還需要再學(xué)習(xí)很多知識。使用Example查詢的話要學(xué)習(xí)的東西就少很多了照捡。我們只要使用已有的實體對象颅湘,創(chuàng)建一個例子,然后在例子上設(shè)置各種約束(即查詢條件)栗精,然后將例子扔給查詢方法即可闯参。這種方式也有缺點(diǎn),就是不能實現(xiàn)所有的查詢功能,我們只能進(jìn)行前后綴匹配等的字符串查詢和其他類型屬性的精確查詢。
首先隧膏,倉庫接口需要繼承QueryByExampleExecutor
接口矢炼,這樣會引入一組以Example作參數(shù)的方法。然后創(chuàng)建一個ExampleMatcher
對象,最后再用Example
的of方法構(gòu)造相應(yīng)的Example對象并傳遞給相關(guān)查詢方法。我們看看Spring的例子。
ExampleMatcher
用于創(chuàng)建一個查詢對象玩讳,下面的代碼就創(chuàng)建了一個查詢對象涩蜘。withIgnorePaths
方法用來排除某個屬性的查詢。withIncludeNullValues
方法讓空值也參與查詢熏纯,如果我們設(shè)置了對象的姓同诫,而名為空值,那么實際查詢條件也是這樣的樟澜。
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("lastname")
.withIncludeNullValues()
.withStringMatcherEnding();
Example<Person> example = Example.of(person, matcher);
withStringMatcher
方法用于指定字符串查詢误窖。例如下面的例子就是查詢所有昵稱以2結(jié)尾的用戶。雖然用的Groovy代碼但是大家應(yīng)該很容易看懂吧秩贰。
@Test
void testExamples() {
User user = new User(nickname: '2')
ExampleMatcher matcher = ExampleMatcher.matching()
.withStringMatcher(ExampleMatcher.StringMatcher.ENDING)
.withIgnorePaths('id')
Example<User> example = Example.of(user, matcher)
Iterable<User> users = exampleRepository.findAll(example)
users.each {
println it
}
}
如果用Java 8的話還可以使用lambda表達(dá)式寫出漂亮的matcher語句霹俺。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
基本的審計
文章寫得非常長了,所以這里最后就在寫一個小特性吧毒费,那就是審計功能丙唧。這里說的是很基本的審計功能,也就是追蹤誰創(chuàng)建和修改相關(guān)實體類觅玻。相關(guān)的注解有4個:@CreatedBy
, @LastModifiedBy
,@CreatedDate
和@LastModifiedDate
想际,分別代表創(chuàng)建和修改實體類的對象和時間。
這幾個時間注解支持JodaTime溪厘、java.util.Date
胡本、Calender、Java 8 的新API以及long
基本類型畸悬。在我們的程序中這幾個注解可以幫我們省不少事情侧甫,比如說,一個博客系統(tǒng)中的文章傻昙,就可以使用這些注解輕松實現(xiàn)新建和修改文章的時間記錄闺骚。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
// … further properties omitted
}
當(dāng)然不是直接用了這兩個注解就行了彩扔。我們還需要啟用審計功能妆档。審計功能需要spring-aspects.jar
這個包,因此首先需要引入Spring Aspects虫碉。在Gradle項目中是這樣的贾惦。
compile group: 'org.springframework', name: 'spring-aspects', version: '4.3.7.RELEASE'
如果使用Java配置的話,在配置類上使用@EnableJpaAuditing注解敦捧。
@Configuration
@EnableJpaAuditing
class Config {
如果使用XML配置的話须板,添加下面的一行。
<jpa:auditing/>
最后在實體類上添加@EntityListeners(AuditingEntityListener)
注解兢卵。這樣习瑰,以后當(dāng)我們創(chuàng)建和修改實體類時,不需要管@LastModifiedDate
和@CreatedDate
這種字段秽荤,Spring會幫我們完成一切甜奄。
@Entity
@EntityListeners(AuditingEntityListener)
class Article {