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