一步步學習 Spring Data 系列之JPA(一)

引入:
Spring Data是SpringSource基金會下的一個用於簡化數據庫訪問,並支持雲服務的開源框架。其主要目標是使得數據庫的訪問變得方便快捷,並支持map-reduce框架和雲計算數據服務。對於擁有海量數據的項目,可以用Spring Data來簡化項目的開發。

然而針對不同的數據儲存訪問使用相對的類庫來操作訪問。Spring Data中已經為我們提供瞭很多業務中常用的一些接口和實現類來幫我們快速構建項目,比如分頁、排序、DAO一些常用的操作。

今天主要是對Spring Data下的JPA模塊進行講解。

為什麼說Spring Data能幫助我們快速構建項目呢,因為Spring Data已經在數據庫訪問層上幫我們實現瞭公用功能瞭,而我們隻需寫一個接口去繼承Spring Data提供給我們接口,便可實現對數據庫的訪問及操作,類似於spring-orm的TemplateDAO。

 

———————————————-邪惡的分割——————————————————-

核心接口:
1 public interface Repository<T, ID extends Serializable> { 

2   

3 }

這個接口隻是一個空的接口,目的是為瞭統一所有Repository的類型,其接口類型使用瞭泛型,泛型參數中T代表實體類型,ID則是實體中id的類型。

再來看一下Repository的直接子接口CrudRepository中的方法:
01 public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { 

02   

03     <S extends T> S save(S entity); 

04   

05     <S extends T> Iterable<S> save(Iterable<S> entities); 

06   

07     T findOne(ID id); 

08   

09     boolean exists(ID id); 

10   

11     Iterable<T> findAll(); 

12   

13     Iterable<T> findAll(Iterable<ID> ids); 

14   

15     long count(); 

16   

17     void delete(ID id); 

18   

19     void delete(T entity); 

20   

21     void delete(Iterable<? extends T> entities); 

22   

23     void deleteAll(); 

24 }

此接口中的方法大多是我們在訪問數據庫中常用的一些方法,如果我們要寫自己的DAO類的時候,隻需定義個接口來集成它便可使用瞭。

再來看看Spring Data未我們提供分頁和排序的Repository的接口PagingAndSortingRepository:

1 public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { 

2   

3     Iterable<T> findAll(Sort sort); 

4   

5     Page<T> findAll(Pageable pageable); 

6 }

這些Repository都是spring-data-commons提供給我們的核心接口,spring-data-commons是Spring Data的核心包。這個接口中為我們提供瞭數據的分頁方法,以及排序方法。[b]看吧,spring-data讓我們省瞭很多心瞭,一切都按照這個規范進行構造,就連業務系統中常用到的一些操作都為我們考慮到瞭,而我們隻需更用心的去關註業務邏輯層。[/b]spring-data將repository的顆粒度劃得很細,其實我覺得spring的框架中將每個類的顆粒度都劃得很細,這主要也是為瞭責任分離。

———————————————-邪惡的分割線——————————————————

JPA實現:
針對spring-data-jpa又提供瞭一系列repository接口,其中有JpaRepository和JpaSpecificationExecutor,這2個接口又有什麼區別呢,我們分別來看看這2個接口的源碼。

JpaRepository.class

01 public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> { 

02   

03     List<T> findAll(); 

04   

05     List<T> findAll(Sort sort); 

06   

07     <S extends T> List<S> save(Iterable<S> entities); 

08     void flush(); 

09   

10     T saveAndFlush(T entity); 

11   

12     void deleteInBatch(Iterable<T> entities); 

13   

14     void deleteAllInBatch();

這個類繼承自PagingAndSortingRepository,看其中的方法,可以看出裡面的方法都是一些簡單的操作,並未涉及到復雜的邏輯。當你在處理一些簡單的數據邏輯時,便可繼承此接口,看一個小例子吧。本文JPA供應者選擇的是Hibernate EntityManager,當然讀者們也可以選擇其他的JPA供應者,比如EclipseLink、OpenJPA,反正JPA是個標準,在無須修改的情況下便可移植。

先定義一用戶實體類User.class:

01 @Entity 

02 @Table( name = "spring_data_user" ) 

03 @PrimaryKeyJoinColumn( name = "id" ) 

04 public class User extends IdGenerator{ 

05   

06     private static final long serialVersionUID = 1L; 

07       

08     private String name; 

09     private String username; 

10     private String password; 

11     private String sex; 

12     private Date birth; 

13     private String address; 

14     private String zip; 

15           

16         //省略getter和setter 

17 }

Id生成策略是采用的表生成策略,這裡就不貼代碼瞭,spring的配置文件我也就不貼出來瞭,反正就那些東西,網上一查,遍地都是。後續我會在將demo附上來。

實體類是有瞭,現在得寫一個持久層,這樣才能操作數據庫啊,現在我們來看一下持久層。IUserDao.class:
1 @Repository("userDao") 

2 public interface IUserDao extends JpaRepository<User, Long>{}

再在spring的配置文件中加上以下代碼。

1 <beans xmlns="http://www.springframework.org/schema/beans"

2     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

3     xmlns:jpa="http://www.springframework.org/schema/data/jpa"

4     xsi:schemaLocation="http://www.springframework.org/schema/beans

5     http://www.springframework.org/schema/data/jpa

6     http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> 

7   

8     <jpa:repositories base-package="org.tea.springdata.**.dao" /> 

9 </beans>

加上這段後Spring就會將指定包中@Repository的類註冊為bean,將bean托管給Spring。這樣定義完瞭就OK瞭!哦,就這樣就可以操作數據庫瞭?
是的,前面我就已經說瞭,Spring data已經幫我們寫好一個實現類瞭,而簡單的操作我們隻須這樣繼承JpaRepository就可以做CRUD操作瞭。再寫個業務類來測試一把吧。由於我用的Cglib來動態代理,所以就不定義接口瞭,直接定義類UserService.class:

 

01 @Service("userService") 

02 public class UserService { 

03       

04     @Autowired

05     private IUserDao dao; 

06       

07     public void save(User user) { 

08         dao.save(user); 

09     } 

10   

11     public void delete(Long id) { 

12         dao.delete(id); 

13     } 

14   

15     public void update(User user) { 

16         dao.saveAndFlush(user); 

17     } 

18   

19     public List<User> findAll() { 

20         return dao.findAll(); 

21     } 

22 }

來寫一單元測試。

 

01 public class UserServiceTest { 

02       

03     private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 

04       

05     private static UserService userService = (UserService) context.getBean("userService"); 

06       

07     public void saveUser() { 

08         StopWatch sw = new StopWatch(getClass().getSimpleName()); 

09         sw.start("Add a user information."); 

10         User u = new User(); 

11         u.setName("John"); 

12         u.setSex("Man"); 

13         u.setUsername("JohnZhang"); 

14         u.setPassword("123456"); 

15         u.setBirth(new Date()); 

16         userService.save(u); 

17         sw.stop(); 

18         System.err.println(sw.prettyPrint()); 

19     } 

20   

21      public static void main(String[] args) { 

22         UserServiceTest test = new UserServiceTest(); 

23         test.saveUser(); 

24     } 

25 }

綠瞭,高興瞭,測試通過!
額,都沒用Junit怎麼會綠呢,開個玩笑。
其餘繼承下來的操作方法,大傢都可以自己測試一下,如沒意外,應該都會測試通過。

這隻是spring data jpa簡單的使用,而往往在項目中這一點功能並不能滿足我們的需求。這是當然的,在業務中查詢是一件非常頭疼的事,畢竟不可能隻是對一張表的查詢是吧? 其實在業務中往往會涉及到多張表的查詢,以及查詢時需要的各種條件。當然這不用擔心,畢竟這是對JPA的支持,而我們在用JPA原生態API的時候往往可能會把一些個方法寫得很凌亂,沒得一個具體的規范來寫自己的方法在後期維護上肯定會很困難。當然你自己也可以封裝一些方法來使用,而當我們使用到Spring Data JPA時,它已經幫助我們完成瞭這個方法的規范瞭。

來一起看一下復雜查詢時它為我們提供的接口。

JpaSpecificationExecutor.class

01 public interface JpaSpecificationExecutor<T> { 

02   

03     T findOne(Specification<T> spec); 

04   

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

06   

07     Page<T> findAll(Specification<T> spec, Pageable pageable); 

08   

09     List<T> findAll(Specification<T> spec, Sort sort); 

10   

11     long count(Specification<T> spec); 

12 }

在這個接口裡面出現次數最多的類就是Specification.class,而這個類主要也就是圍繞Specification來打造的,Specification.class是Spring Data JPA提供的一個查詢規范,而你隻需圍繞這個規范來設置你的查詢條件便可,我們來看一下Specification.class這個接口中有些什麼東西。

Specification.class

view sourceprint?1 public interface Specification<T> { 

2   

3     Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 

4 }

隻有一個方法toPredicate,而其中的參數大傢並不陌生,都是JPA規范中的,ROOT查詢中的條件表達式、CriteriaQuery條件查詢設計器、CriteriaBuilder條件查詢構造器,而我們在使用復雜對象查詢時,實現該方法用JPA去構造對象查詢便可。

下面來看一個小例子:

 

1 @Repository("userDao") 

2 public interface IUserDao extends JpaSpecificationExecutor<User>{ 

3 }

仍然隻是一個空接口,這次繼承的是JpaSpecificationExecutor瞭。
再寫一測試用例:查詢用戶表中name包含Sam的記錄,並分頁按照birth排倒序

 
01 public class UserDaoTest { 

02   

03     private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 

04   

05     private static IUserDao userDao = (IUserDao) context.getBean("userDao"); 

06   

07     public void findBySpecAndPaginate() { 

08         Page<User> page = userDao.findAll(new Specification<User>() { 

09             @Override

10             public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 

11                 root = query.from(User.class); 

12                 Path<String> nameExp = root.get("name"); 

13                 return cb.like(nameExp, "%Sam%"); 

14             } 

15   

16         }, new PageRequest(1, 5, new Sort(Direction.DESC, new String[] { "birth" }))); 

17   

18         StringBuilder stout = new StringBuilder(" 以下是姓名包含Sam人員信息 : ").append("\n"); 

19         stout.append("| 序號 | username | password | name | sex | birth |").append("\n"); 

20         int sortIndex = 1; 

21         for (User u : page.getContent()) { 

22             stout.append(" | ").append(sortIndex); 

23             stout.append(" | ").append(u.getUsername()); 

24             stout.append(" | ").append(u.getPassword()); 

25             stout.append(" | ").append(u.getName()); 

26             stout.append(" | ").append(u.getSex()); 

27             stout.append(" | ").append(u.getBirth()); 

28             stout.append(" | \n"); 

29             sortIndex++; 

30         } 

31         System.err.println(stout); 

32     } 

33   

34     public static void main(String[] args) { 

35         UserDaoTest test = new UserDaoTest(); 

36         test.findBySpecAndPaginate(); 

37     } 

38 }

當然,這隻是一個測試,很簡單的一個條件查詢方法。你也可以設計復雜的查詢來得到自己所需的結果,我這隻是寫一個很簡單的方法來帶大傢入門。

 

發佈留言