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

大概有半年多沒有寫博客了,主要是最近忙於工做,也沒來得及與你們分享技術。固然如今的技術大多都有人寫其博客分享了,也找不到合適寫的,因此也就懶得寫了。最近在SpringSource上看到了一好玩的東東,因而就照着官方的文檔玩了一把,再根據本身的理解寫給博友們品鑑,若有不對的地方,歡迎博友們提出,筆者會一一記錄下來,以便後續改進。java

引入:

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

然而針對不一樣的數據儲存訪問使用相對的類庫來操做訪問。Spring Data中已經爲咱們提供了不少業務中經常使用的一些接口和實現類來幫咱們快速構建項目,好比分頁、排序、DAO一些經常使用的操做。

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

爲何說Spring Data能幫助咱們快速構建項目呢,由於Spring Data已經在數據庫訪問層上幫咱們實現了公用功能了,而咱們只需寫一個接口去繼承Spring Data提供給咱們接口,即可實現對數據庫的訪問及操做,相似於spring-orm的TemplateDAO。spring

 

----------------------------------------------邪惡的分割-------------------------------------------------------數據庫

核心接口:

public interface Repository<T, ID extends Serializable> {

}


這個接口只是一個空的接口,目的是爲了統一全部Repository的類型,其接口類型使用了泛型,泛型參數中T表明實體類型,ID則是實體中id的類型。c#

再來看一下Repository的直接子接口CrudRepository中的方法:app

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

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

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

	T findOne(ID id);

	boolean exists(ID id);

	Iterable<T> findAll();

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

	long count();

	void delete(ID id);

	void delete(T entity);

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

	void deleteAll();
}

此接口中的方法大可能是咱們在訪問數據庫中經常使用的一些方法,若是咱們要寫本身的DAO類的時候,只需定義個接口來集成它即可使用了。

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

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

	Iterable<T> findAll(Sort sort);

	Page<T> findAll(Pageable pageable);
}

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

----------------------------------------------邪惡的分割線------------------------------------------------------單元測試

JPA實現:

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

JpaRepository.class學習

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

	List<T> findAll();

	List<T> findAll(Sort sort);

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

	T saveAndFlush(T entity);

	void deleteInBatch(Iterable<T> entities);

	void deleteAllInBatch();

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

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

@Entity
@Table( name = "spring_data_user" )
@PrimaryKeyJoinColumn( name = "id" )
public class User extends IdGenerator{

	private static final long serialVersionUID = 1L;
	
	private String name;
	private String username;
	private String password;
	private String sex;
	private Date birth;
	private String address;
	private String zip;
        
        //省略getter和setter
}

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

實體類是有了,如今得寫一個持久層,這樣才能操做數據庫啊,如今咱們來看一下持久層。IUserDao.class:

@Repository("userDao")
public interface IUserDao extends JpaRepository<User, Long>{}

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

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/data/jpa
	http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

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

 

@Service("userService")
public class UserService {
	
	@Autowired
	private IUserDao dao;
	
	public void save(User user) {
		dao.save(user);
	}

	public void delete(Long id) {
		dao.delete(id);
	}

	public void update(User user) {
		dao.saveAndFlush(user);
	}

	public List<User> findAll() {
		return dao.findAll();
	}
}

來寫一單元測試。

 

public class UserServiceTest {
	
	private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
	
	private static UserService userService = (UserService) context.getBean("userService");
	
	public void saveUser() {
		StopWatch sw = new StopWatch(getClass().getSimpleName());
		sw.start("Add a user information.");
		User u = new User();
		u.setName("John");
		u.setSex("Man");
		u.setUsername("JohnZhang");
		u.setPassword("123456");
		u.setBirth(new Date());
		userService.save(u);
		sw.stop();
		System.err.println(sw.prettyPrint());
	}

     public static void main(String[] args) {
		UserServiceTest test = new UserServiceTest();
		test.saveUser();
	}
}

綠了,高興了,測試經過!
額,都沒用Junit怎麼會綠呢,開個玩笑。
其他繼承下來的操做方法,你們均可以本身測試一下,如沒意外,應該都會測試經過。

這只是spring data jpa簡單的使用,而每每在項目中這一點功能並不能知足咱們的需求。這是固然的,在業務中查詢是一件很是頭疼的事,畢竟不可能只是對一張表的查詢是吧? 其實在業務中每每會涉及到多張表的查詢,以及查詢時須要的各類條件。固然這不用擔憂,畢竟這是對JPA的支持,而咱們在用JPA原生態API的時候每每可能會把一些個方法寫得很凌亂,沒得一個具體的規範來寫本身的方法在後期維護上確定會很困難。固然你本身也能夠封裝一些方法來使用,而當咱們使用到Spring Data JPA時,它已經幫助咱們完成了這個方法的規範了。

來一塊兒看一下複雜查詢時它爲咱們提供的接口。

JpaSpecificationExecutor.class

public interface JpaSpecificationExecutor<T> {

	T findOne(Specification<T> spec);

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

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

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

	long count(Specification<T> spec);
}

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

Specification.class

public interface Specification<T> {

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

只有一個方法toPredicate,而其中的參數你們並不陌生,都是JPA規範中的,ROOT查詢中的條件表達式、CriteriaQuery條件查詢設計器、CriteriaBuilder條件查詢構造器,而咱們在使用複雜對象查詢時,實現該方法用JPA去構造對象查詢即可。

下面來看一個小例子:

 

@Repository("userDao")
public interface IUserDao extends JpaSpecificationExecutor<User>{
}

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

 

public class UserDaoTest {

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

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

	public void findBySpecAndPaginate() {
		Page<User> page = userDao.findAll(new Specification<User>() {
			@Override
			public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				root = query.from(User.class);
				Path<String> nameExp = root.get("name");
				return cb.like(nameExp, "%Sam%");
			}

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

		StringBuilder stout = new StringBuilder(" 如下是姓名包含Sam人員信息 : ").append("\n");
		stout.append("| 序號 | username | password | name | sex | birth |").append("\n");
		int sortIndex = 1;
		for (User u : page.getContent()) {
			stout.append(" | ").append(sortIndex);
			stout.append(" | ").append(u.getUsername());
			stout.append(" | ").append(u.getPassword());
			stout.append(" | ").append(u.getName());
			stout.append(" | ").append(u.getSex());
			stout.append(" | ").append(u.getBirth());
			stout.append(" | \n");
			sortIndex++;
		}
		System.err.println(stout);
	}

	public static void main(String[] args) {
		UserDaoTest test = new UserDaoTest();
		test.findBySpecAndPaginate();
	}
}

固然,這只是一個測試,很簡單的一個條件查詢方法。你也能夠設計複雜的查詢來獲得本身所需的結果,我這只是寫一個很簡單的方法來帶你們入門。

寫了兩篇文章了,尚未講Spring Data JPA爲何只需定義接口就可使用,其實這也不難發現,查看源碼,能夠找到針對JpaRepository和JpaSpecificationExecutor有一個實現類,SimpleJpaRepository.class,這個類實現了剛纔所提的兩個接口。而Spring在給咱們注入實現類的時候,就正是這個SimpleJpaRepository.class,具體的實現方式我就不在這意義贅述了,你們若是有興趣能夠去查看它的源碼,和傳統的JPA實現是同樣的。

經過這篇文章咱們學習到了,當要使用複雜的條件查詢時,咱們能夠選擇使用此接口來完善咱們的需求,這篇文章就講到這裏,在下一篇文章中我主要是講Spring Data JPA爲咱們提供的註解查詢。

相關文章
相關標籤/搜索