1. 程式人生 > >Spring Data JPA 介紹和使用

Spring Data JPA 介紹和使用

本文參考了Spring Data JPA官方文件,引用了部分文件的程式碼。

Spring Data JPA是Spring基於Hibernate開發的一個JPA框架。如果用過Hibernate或者MyBatis的話,就會知道物件關係對映(ORM)框架有多麼方便。但是Spring Data JPA框架功能更進一步,為我們做了 一個數據持久層框架幾乎能做的任何事情。下面來逐步介紹它的強大功能。

新增依賴

我們可以簡單的宣告Spring Data JPA的單獨依賴項。以Gradle為例,依賴項如下,Spring Data JPA會自動新增它的Spring依賴項。當前版本需要Spring框架版本為4.3.7.RELEASE

或更新,使用舊版本的Spring框架可能會出現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'

基本使用

建立環境

Spring Data JPA也是一個JPA框架,因此我們需要資料來源、JPA Bean、資料庫驅動、事務管理器等等。下面以XML配置為例,我們來配置一下所需的Bean。重點在於<jpa:repositories base-package="yitian.study.dao"/>

一句,它告訴Spring去哪裡尋找並建立這些介面類。

<!--啟用註解配置和包掃描-->
<context:annotation-config/>
<context:component-scan base-package="yitian.study"/>
<!--建立Spring Data JPA例項物件-->
<jpa:repositories base-package="yitian.study.dao"/>
<!--資料來源-->
<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> <!--事務管理器--> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!--事務管理--> <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>

建立DAO物件

前幾天學了一點Groovy,再回頭看看Java,實在是麻煩。所以這裡我用Groovy寫的實體類,不過語法和Java很相似。大家能看懂意思即可。不過確實Groovy能比Java少些很多程式碼,對開發挺有幫助的。有興趣的同學可以看看我的Groovy學習筆記

Groovy類的欄位預設是私有的,方法預設是公有的,分號可以省略,對於預設欄位Groovy編譯器還會自動生成Getter和Setter,可以減少不少程式碼量。只不過equals等方法不能自動生成,多少有點遺憾。這裡使用了JPA註解,建立了一個實體類和資料表的對映。

@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> {
}

然後測試一下,會發生什麼事情呢?檢視一下資料庫就會發現資料已經成功插入了。好吧,好像沒什麼有魔力的事情。

@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程式碼,意思應該很容易懂,就是迴圈20次,然後插入20個使用者,使用者的名字和郵箱都是由迴圈變數生成的。然後呼叫我們剛剛的方法。這次真的按照我們的要求查詢出了使用者名稱以2結尾的所有使用者!

    @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)
    }
//結果如下
//[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的真正功能了。我們只要繼承它提供的介面,然後按照命名規則定義相應的查詢方法。Spring就會自動建立實現了該介面和查詢方法的物件,我們直接使用就可以了。也就是說,Spring Data JPA連查詢方法都可以幫我們完成,我們幾乎什麼也不用幹了。

下面來介紹一下Spring的這些介面。上面的例子中,我們繼承了CrudRepository介面。CrudRepository介面的定義如下。如果我們需要增刪查改功能。只需要繼承該介面就可以立即獲得該介面的所有功能。CrudRepository介面有兩個泛型引數,第一個引數是實際儲存的型別,第二個引數是主鍵。

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介面沒有任何方法。

如果對資料訪問需要詳細控制,就可以使用該介面。PagingAndSortingRepository介面則提供了分頁和排序功能。PagingAndSortingRepository介面的方法接受額外的Pagable和Sort物件,用來指定獲取結果的頁數和排序方式。返回型別則是Page型別,我們可以呼叫它的方法獲取總頁數和可迭代的資料集合。下面是一個Groovy寫的例子。注意Pageable是一個介面,如果我們需要建立Pageable物件,使用PageRequest類並指定獲取的頁數和每頁的資料量。頁是從0開始計數的。

    @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}頁資料,共${users.totalPages}頁"
            users.each {
                println it
            }
        }

    }

查詢方法

查詢方法可以由我們宣告的命名查詢生成,也可以像前面那樣由方法名解析。下面是官方文件的例子。方法名稱規則如下。如果需要詳細說明的話可以檢視官方文件Appendix C: Repository query keywords一節。

  • 方法名以find…By, read…By, query…By, count…Byget…By做開頭。在By之前可以新增Distinct表示查詢不重複資料。By之後是真正的查詢條件。
  • 可以查詢某個屬性,也可以使用條件進行比較複雜的查詢,例如Between, LessThan, GreaterThan, LikeAnd,Or等。
  • 字串屬性後面可以跟IgnoreCase表示不區分大小寫,也可以後跟AllIgnoreCase表示所有屬性都不區分大小寫。
  • 可以使用OrderBy對結果進行升序或降序排序。
  • 可以查詢屬性的屬性,直接將幾個屬性連著寫即可,如果可能出現歧義屬性,可以使用下劃線分隔多個屬性。
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);

  // 對某一屬性不區分大小寫
  List<Person> findByLastnameIgnoreCase(String lastname);
  // 所有屬性不區分大小寫
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // 啟用靜態排序
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
  //查詢Person.Address.ZipCode
  List<Person> findByAddressZipCode(ZipCode zipCode);
  //避免歧義可以這樣
  List<Person> findByAddress_ZipCode(ZipCode zipCode);

如果需要限制查詢結果也很簡單。

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);

如果查詢很費時間,也可以方便的使用非同步查詢。只要新增@Async註解,然後將返回型別設定為非同步的即可。

@Async
Future<User> findByFirstname(String firstname);               

@Async
CompletableFuture<User> findOneByFirstname(String firstname); 

@Async
ListenableFuture<User> findOneByLastname(String lastname); 

Spring Data擴充套件功能

Querydsl擴充套件

Querydsl擴充套件能讓我們以流式方式程式碼編寫查詢方法。該擴充套件需要一個介面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的相應依賴包。然後在程式中啟用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,它可以自動將查詢引數或者路徑引數轉換為領域模型物件。下面的例子中,Spring Data會自動用主鍵查詢對應的使用者,然後我們直接就可以從處理方法引數中獲得使用者例項。注意,Spring Data需要呼叫findOne方法查詢物件,現版本下我們必須繼承CrudRepository,才能實現該功能。

@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等幾個例項。它們支援從請求引數中讀取分頁和排序資訊。

@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";
  }
}

對於上面的例子,如果在請求引數中包含sort、page、size等幾個引數,它們就會被對映為Spring Data的Pageable和Sort物件。請求引數的詳細資訊如下。

  • page 想要獲取的頁數,預設是0,以零開始計數的。
  • size 每頁的資料大小,預設是20.
  • 資料的排序規則,預設是升序,也可以對多個屬性執行排序,這時候需要多個sort引數,例如?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 了。如果相關Jar包在類路徑上,@EnableSpringDataWebSupport註解同樣會啟用該功能。比方說,在前面的例子中,如果在使用者使用者引數上新增下面的查詢引數。

?firstname=Dave&lastname=Matthews

那麼就會被QuerydslPredicateArgumentResolver解析為下面的查詢語句。

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

還可以將QuerydslPredicate註解到對應型別的方法引數上,Spring會自動例項化相應的引數。為了Spring能夠準確找到應該查詢什麼領域物件,我們最好指定root屬性。

@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";
  }
}

官方文件的其他內容

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>

還可以使用註解,在對應實體類上註解命名查詢。

@Entity
@NamedQuery(name = "User.findByNickname",
  query = "select u from User u where u.nickname=?1")
public class User {

}

之後,在介面中宣告對應名稱的查詢方法。這樣我們就可以使用JPQL語法自定義查詢方法了。

List<User> findByNickname(String nickname)

使用Query註解

在上面的方法中,查詢方法和JPQL是對應的,但是卻不在同一個地方定義。如果查詢方法很多的話,查詢和修改就很麻煩。這時候可以改用@Query註解。下面的例子直接在方法上定義了JPQL語句,如果需要引用orm.xml檔案中的查詢語句,使用註解的name屬性,如果沒有指定,會使用領域模型名.方法名作為命名查詢語句的名稱。

public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

細心的同學會發現,該註解還有一個nativeQuery屬性,用作直接執行SQL使用。如果我們將該屬性指定為true,查詢語句也要相應的修改為SQL語句。

Modifying註解

@Modifying註解用來指定某個查詢是一個更新操作,這樣可以讓Spring執行相應的優化。

@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

投影

有時候資料庫和實體類之間並不存在一一對應的關係,或者根據某些情況需要隱藏資料庫中的某些欄位。這可以通過投影實現。來看看Spring的例子。

假設有下面的實體類和倉庫。我們在獲取人的時候會順帶獲取它的地址。

@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方法,暴露你需要的屬性。然後倉庫方法也做相應修改。

interface NoAddresses {  

  String getFirstName(); 

  String getLastName();  
}

interface PersonRepository extends CrudRepository<Person, Long> {

  NoAddresses findByFirstName(String firstName);
}

利用@Value註解和SpEl,我們可以靈活的組織屬性。例如下面,定義一個介面,重新命名了lastname屬性。關於Spring表示式,可以看看我的文章Spring EL 簡介

interface RenamedProperty {    

  String getFirstName();       

  @Value("#{target.lastName}")
  String getName();            
}

或者組合多個屬性也可以,下面的例子將姓和名組合成全名。Spring El的使用很靈活,合理使用可以達到事半功倍的效果。

interface FullNameAndCountry {

  @Value("#{target.firstName} #{target.lastName}")
  String getFullName();

  @Value("#{target.address.country}")
  String getCountry();
}

規範

這裡說的規範指的是JPA 2 引入的新的程式設計方式實現查詢的規範。其他框架比如Hibernate也廢棄了自己的Criteria查詢方法,改為使用JPA規範的Criteria。這種方式的好處就是完全是程式設計式的,不需要額外的功能,使用IDE的程式碼提示功能即可。但是我個人不太喜歡,一來沒怎麼詳細瞭解,二來感覺不如JPQL這樣的查詢簡單粗暴。

廢話不多說,直接看官方的例子吧。首先倉庫介面需要繼承JpaSpecificationExecutor介面。

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
 …
}

這樣倉庫介面就繼承了一組以Specification介面作引數的查詢方法,類似下面這樣。

List<T> findAll(Specification<T> spec);

而Specification又是這麼個東西。所以我們要使用JPA規範的查詢方法,就需要實現toPredicate方法。

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

官方文件有這麼個例子,這個類中包含了多個靜態方法,每個方法都返回一個實現了的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());

多個規範組合起來的查詢也可以。

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

Example查詢

前段時間在研究Spring的時候,發現Spring對Hibernate有一個封裝類HibernateTemplate,它將Hibernate的Session封裝起來,由Spring的事務管理器管理,我們只需要呼叫HibernateTemplate的方法即可。在HibernateTemplate中有一組Example方法我沒搞明白啥意思,後來才發現這是Spring提供的一組簡便查詢方式。不過這種查詢方式的介紹居然在Spring Data這個框架中。

這種方式的優點就是比較簡單,如果使用上面的JPA規範,還需要再學習很多知識。使用Example查詢的話要學習的東西就少很多了。我們只要使用已有的實體物件,建立一個例子,然後在例子上設定各種約束(即查詢條件),然後將例子扔給查詢方法即可。這種方式也有缺點,就是不能實現所有的查詢功能,我們只能進行前後綴匹配等的字串查詢和其他型別屬性的精確查詢。

首先,倉庫介面需要繼承QueryByExampleExecutor介面,這樣會引入一組以Example作引數的方法。然後建立一個ExampleMatcher物件,最後再用Example的of方法構造相應的Example物件並傳遞給相關查詢方法。我們看看Spring的例子。

ExampleMatcher用於建立一個查詢物件,下面的程式碼就建立了一個查詢物件。withIgnorePaths方法用來排除某個屬性的查詢。withIncludeNullValues方法讓空值也參與查詢,如果我們設定了物件的姓,而名為空值,那麼實際查詢條件也是這樣的。

Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcherEnding();                          

Example<Person> example = Example.of(person, matcher);

withStringMatcher方法用於指定字串查詢。例如下面的例子就是查詢所有暱稱以2結尾的使用者。雖然用的Groovy程式碼但是大家應該很容易看懂吧。

    @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表示式寫出漂亮的matcher語句。

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

基本的審計

文章寫得非常長了,所以這裡最後就在寫一個小特性吧,那就是審計功能。這裡說的是很基本的審計功能,也就是追蹤誰建立和修改相關實體類。相關的註解有4個:@CreatedBy, @LastModifiedBy,@CreatedDate@LastModifiedDate,分別代表建立和修改實體類的物件和時間。

這幾個時間註解支援JodaTime、java.util.Date、Calender、Java 8 的新API以及long基本型別。在我們的程式中這幾個註解可以幫我們省不少事情,比如說,一個部落格系統中的文章,就可以使用這些註解輕鬆實現新建和修改文章的時間記錄。

class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

當然不是直接用了這兩個註解就行了。我們還需要啟用審計功能。審計功能需要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)註解。這樣,以後當我們建立和修改實體類時,不需要管@LastModifiedDate@CreatedDate這種欄位,Spring會幫我們完成一切。

@Entity
@EntityListeners(AuditingEntityListener)
class Article {