1. 程式人生 > >從簡單的JPA到Spring Data JPA

從簡單的JPA到Spring Data JPA

本文程式碼是基於 Hibernate EntityManager,讀者幾乎不用修改任何程式碼,便可以非常容易地切換到其他 JPA 框架,因為程式碼中使用到的都是 JPA 規範提供的介面 / 類,並沒有使用到框架本身的私有特性。示例主要涉及七個檔案,但是很清晰:業務層包含一個介面和一個實現;持久層包含一個介面、一個實現、一個實體類;另外加上一個 JPA 配置檔案和一個測試類。相關類 / 介面程式碼如下:

pom.xml

	<properties>
		<!-- hibernate 版本號 -->
		<hibernate.version>5.1.0.Final</hibernate.version>
	</properties>
	<dependencies>
		<!-- hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.9</version>
		</dependency>
	</dependencies>
dao層程式碼如下:
public interface UserDao {
	UserInfo save(UserInfo user);
}

public class UserDaoImpl implements UserDao {
	@Override
	public UserInfo save(UserInfo user) {
		EntityManagerFactory entityManagerFactory = Persistence
				.createEntityManagerFactory("SimplePU");
		EntityManager entityManager = entityManagerFactory
				.createEntityManager();
		entityManager.getTransaction().begin();
		entityManager.persist(user);
		entityManager.getTransaction().commit();
		entityManagerFactory.close();
		return user;
	}
}
service層程式碼如下:
public interface UserService {
	public UserInfo createNewAccountInfo(String user, String pwd,
			Integer init);
}

public class UserServiceImpl implements UserService {
	private UserDao userDao = new UserDaoImpl();
	@Override
	public UserInfo createNewAccountInfo(String name, String pwd,
			Integer init) {
		UserInfo user = new UserInfo();
		user.setUsername(name);
		user.setPassword(pwd);
		user.setBalance(init);
		return userDao.save(user);
	}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	version="2.0">
	<persistence-unit name="SimplePU" transaction-type="RESOURCE_LOCAL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<class>com。hsb.hibernate.entity.UserInfo</class>
		<class>com。hsb.hibernate.entity.AccountInfo</class>
		<properties>
			<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
			<property name="hibernate.connection.url"
				value="jdbc:mysql://127.0.0.1:3306/hibernatepractice?createDatabaseIfNotExist=true" />
			<property name="hibernate.connection.username" value="root" />
			<property name="hibernate.connection.password" value="" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.format_sql" value="true" />
			<property name="hibernate.use_sql_comments" value="false" />
			<property name="hibernate.hbm2ddl.auto" value="update" />
		</properties>
	</persistence-unit>
</persistence>
單元測試程式碼如下:
public class UserServiceImplTests {
	private UserServiceImpl userService = new UserServiceImpl();
	@Test
	public void test() {
		userService.createNewAccountInfo("yunshixin", "mima", 1);
	}
}

Spring框架對JPA簡單的支援

引入Spring後通過註解完成物件的依賴注入,還可以通過Spring的宣告式事務簡化持久化操作:

pom.xml

<properties>
		<!-- hibernate 版本號 -->
		<hibernate.version>5.1.0.Final</hibernate.version>
		<!-- spring版本號 -->
		<spring.version>4.2.5.RELEASE</spring.version>
	</properties>
	<dependencies>
		<!-- spring核心包 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>1.10.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.5.0</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.5.0</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.5.0</version>
		</dependency>
		<!-- hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.9</version>
		</dependency>
	</dependencies>
dao層程式碼如下:
public interface UserDao {
	UserInfo save(UserInfo user);
}

@Repository
public class UserDaoImpl implements UserDao {
	@PersistenceContext
	private EntityManager entityManager;

	@Transactional
	@Override
	public UserInfo save(UserInfo user) {
		entityManager.persist(user);
		return user;
	}
}
service層程式碼如下:
public interface UserService {
	public UserInfo createNewAccountInfo(String user, String pwd,
			Integer init);
}

@Service
public class UserServiceImpl implements UserService {
	@Autowired
	private UserDao userDao;

	@Override
	@Transactional
	public UserInfo createNewAccountInfo(String name, String pwd,
			Integer init) {
		UserInfo user = new UserInfo();
		user.setUsername(name);
		user.setPassword(pwd);
		user.setBalance(init);
		return userDao.save(user);
	}

}
persistence.xml配置無變化,增加了applicationContext.xml檔案,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.2.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
   http://www.springframework.org/schema/mvc 
   http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
   http://www.springframework.org/schema/data/jpa 
   http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
	<!-- 開啟IOC註解掃描 -->
	<context:component-scan base-package="com.hsb.hibernate" />
	<!-- 啟用 annotation事務 -->
	<tx:annotation-driven transaction-manager="transactionManager" />
	<!-- 配置事務管理器 -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	</bean>
</beans>
單元測試如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:**/applicationContext.xml")
public class UserServiceImplTests {
	@Autowired
	private UserService userService;

	@Test
	public void test() {
		userService.createNewAccountInfo("yunshixin", "mima", 1);
	}

}

通過對比重構前後的程式碼,可以發現 Spring 對 JPA 的簡化已經非常出色了,我們可以大致總結一下 Spring 框架對 JPA 提供的支援主要體現在如下幾個方面:
    首先,它使得 JPA 配置變得更加靈活。JPA 規範要求,配置檔案必須命名為 persistence.xml,並存在於類路徑下的 META-INF 目錄中。該檔案通常包含了初始化 JPA 引擎所需的全部資訊。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常靈活的配置,persistence.xml 中的資訊都可以在此以屬性注入的方式提供。
    其次,Spring 實現了部分在 EJB 容器環境下才具有的功能,比如對 @PersistenceContext、@PersistenceUnit 的容器注入支援。
    第三,也是最具意義的,Spring 將 EntityManager 的建立與銷燬、事務管理等程式碼抽取出來,並由其統一管理,開發者不需要關心這些,如前面的程式碼所示,業務方法中只剩下操作領域物件的程式碼,事務管理和 EntityManager 建立、銷燬的程式碼都不再需要開發者關心了。


Spring Data JPA的進一步支援

①讓dao層整合Repository介面,該介面使用了泛型,需要為其提供兩個型別:第一個為該介面處理的域物件型別,第二個為該域物件的主鍵型別。修改後的dao層程式碼 如下:

public interface UserDao extends Repository<UserInfo, Integer> {
	UserInfo save(UserInfo user);
}
②刪除dao層的實現類。Spring Data JPA會根據定義的方法名自動完成業務邏輯

③在spring的配置檔案中啟用掃描並自動建立代理,springContext.xml增加配置如下:

<pre class="displaycode" style="margin-top: 0px; border: 1px solid rgb(204, 204, 204); outline: 0px; font-size: 11px; vertical-align: baseline; width: 780px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; overflow: auto; clear: right; margin-bottom: 6px !important; padding: 5px 10px 5px 3px !important; background: rgb(247, 247, 247) !important;"><-- 需要在 <beans> 標籤中增加對 jpa 名稱空間的引用 --> 
<jpa:repositories base-package="com.hsb.hibernate"
		entity-manager-factory-ref="entityManagerFactory"
		transaction-manager-ref="transactionManager" />
其他部分不用改變,仍使用以前的單元測試,往資料庫中新增資料。成功。

總結使用SpringDataJPA進行dao層的開發需要的幾個步驟如下:

一、宣告一個繼承了Repository介面的介面

二、在宣告的介面中定義業務需要的一些方法

三、在 Spring 配置檔案中增加配置,讓 Spring 容器為宣告的介面建立代理物件。新增宣告配置後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。

除了直接繼承Repository介面外,還有另一種方式可以完成同樣的工作,兩種方式如下:

public interface UserDao extends Repository<UserInfo, Long> { …… } 

 @RepositoryDefinition(domainClass = UserInfo.class, idClass = Long.class) 
 public interface UserDao { …… }


通過解析方法名建立查詢
通過前面的例子,讀者基本上對解析方法名建立查詢的方式有了一個大致的瞭解,這也是 Spring Data JPA 吸引開發者的一個很重要的因素。框架在進行方法名解析時,會先把方法名多餘的字首擷取掉,比如 find、findBy、read、readBy、get、getBy,然後對剩下部分進行解析。並且如果方法的最後一個引數是 Sort 或者 Pageable 型別,也會提取相關的資訊,以便按規則進行排序或者分頁查詢。
在建立查詢時,我們通過在方法名中使用屬性名稱來表達,比如 findByUserAddressZip ()。框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,詳細規則如下(此處假設該方法針對的域物件為 UserInfo 型別):
先判斷 userAddressZip (根據 POJO 規範,首字母變為小寫,下同)是否為UserInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
從右往左擷取第一個大寫字母開頭的字串(此處為 Zip),然後檢查剩下的字串是否為 UserInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左擷取;最後假設 user 為 UserInfo 的一個屬性;
接著處理剩下部分( AddressZip ),先判斷 user 所對應的型別是否有 addressZip 屬性,如果有,則表示該方法最終是根據 "AccountInfo.user.addressZip" 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左擷取,最終表示根據 "AccountInfo.user.address.zip" 的值進行查詢。
可能會存在一種特殊情況,比如 AccountInfo 包含一個 user 的屬性,也有一個 userAddress 屬性,此時會存在混淆。讀者可以明確在屬性之間加上 "_" 以顯式表達意圖,比如 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。
在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 為此提供了一些表達條件查詢的關鍵字,大致如下:
And --- 等價於 SQL 中的 and 關鍵字,比如 findByUsernameAndPassword(String user, Striang pwd);
Or --- 等價於 SQL 中的 or 關鍵字,比如 findByUsernameOrAddress(String user, String addr);
Between --- 等價於 SQL 中的 between 關鍵字,比如 findBySalaryBetween(int max, int min);
LessThan --- 等價於 SQL 中的 "<",比如 findBySalaryLessThan(int max);
GreaterThan --- 等價於 SQL 中的">",比如 findBySalaryGreaterThan(int min);
IsNull --- 等價於 SQL 中的 "is null",比如 findByUsernameIsNull();
IsNotNull --- 等價於 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
NotNull --- 與 IsNotNull 等價;
Like --- 等價於 SQL 中的 "like",比如 findByUsernameLike(String user);
NotLike --- 等價於 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
OrderBy --- 等價於 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
Not --- 等價於 SQL 中的 "! =",比如 findByUsernameNot(String user);
In --- 等價於 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的引數可以是 Collection 型別,也可以是陣列或者不定長引數;
NotIn --- 等價於 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的引數可以是 Collection 型別,也可以是陣列或者不定長引數;




參考:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/