Spring Data JPA 介紹和使用

本文參考了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…Byget…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會注冊HandlerMethodArgumentResolverPageableHandlerMethodArgumentResolverSortHandlerMethodArgumentResolver等幾個實例哨苛。它們支持從請求參數(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_pagebar_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 {
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柠横,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子课兄,更是在濱河造成了極大的恐慌牍氛,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烟阐,死亡現(xiàn)場離奇詭異搬俊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蜒茄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門唉擂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人檀葛,你說我怎么就攤上這事楔敌。” “怎么了驻谆?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵卵凑,是天一觀的道長。 經(jīng)常有香客問我胜臊,道長勺卢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任象对,我火速辦了婚禮黑忱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勒魔。我一直安慰自己甫煞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布冠绢。 她就那樣靜靜地躺著抚吠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弟胀。 梳的紋絲不亂的頭發(fā)上楷力,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音孵户,去河邊找鬼萧朝。 笑死,一個胖子當(dāng)著我的面吹牛夏哭,可吹牛的內(nèi)容都是我干的检柬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼竖配,長吁一口氣:“原來是場噩夢啊……” “哼何址!你這毒婦竟也來了酱固?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤头朱,失蹤者是張志新(化名)和其女友劉穎运悲,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體项钮,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡班眯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烁巫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片署隘。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亚隙,靈堂內(nèi)的尸體忽然破棺而出磁餐,到底是詐尸還是另有隱情,我是刑警寧澤阿弃,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布诊霹,位于F島的核電站,受9級特大地震影響渣淳,放射性物質(zhì)發(fā)生泄漏脾还。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一入愧、第九天 我趴在偏房一處隱蔽的房頂上張望鄙漏。 院中可真熱鬧,春花似錦棺蛛、人聲如沸怔蚌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桦踊。三九已至,卻和暖如春彤恶,著一層夾襖步出監(jiān)牢的瞬間钞钙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工声离, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘫怜。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓术徊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲸湃。 傳聞我的和親對象是個殘疾皇子赠涮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,803評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理子寓,服務(wù)發(fā)現(xiàn),斷路器笋除,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 簡介: 本文由淺入深地講述了使用 Spring Data JPA 需要關(guān)注的各個方面斜友,為讀者了解和使用該框架提供了...
    AiPuff閱讀 4,503評論 1 28
  • https://www.ibm.com/developerworks/cn/opensource/os-cn-sp...
    大同若魚閱讀 5,024評論 4 18
  • 一晚上的難過后,磨磨蹭蹭起床時都已經(jīng)九點(diǎn)半了垃它,打開木門鲜屏,揉著昨晚因情緒化而哭腫的眼睛,紛紛雪花飄揚(yáng)国拇,心也不禁一揪洛史,...
    井溢閱讀 197評論 2 2