第1章 Spring Data JPA的快速入門 1.1 需求說明 Spring Data JPA完成客戶的基本CRUD操作 1.2 搭建Spring Data JPA的開發環境 1.2.1 引入Spring Data JPA的坐標 使用Spring Data JPA,需要整合Spring與Spri ...
第1章 Spring Data JPA的快速入門
1.1 需求說明
Spring Data JPA完成客戶的基本CRUD操作
1.2 搭建Spring Data JPA的開發環境
1.2.1 引入Spring Data JPA的坐標
使用Spring Data JPA,需要整合Spring與Spring Data JPA,並且需要提供JPA的服務提供者hibernate,所以需要導入spring相關坐標,hibernate坐標,資料庫驅動坐標等
<properties> <spring.version>4.2.4.RELEASE</spring.version> <hibernate.version>5.0.7.Final</hibernate.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> <c3p0.version>0.9.1.2</c3p0.version> <mysql.version>5.1.6</mysql.version> </properties>
<dependencies> <!-- junit單元測試 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> <scope>test</scope> </dependency>
<!-- spring beg --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.8</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</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-orm</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency>
<!-- spring end -->
<!-- hibernate beg --> <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>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.1.Final</version> </dependency> <!-- hibernate end -->
<!-- c3p0 beg --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>${c3p0.version}</version> </dependency> <!-- c3p0 end -->
<!-- log end --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency>
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency>
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- log end -->
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency>
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.9.0.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.4.RELEASE</version> </dependency>
<!-- el beg 使用spring data jpa 必須引入 --> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>2.2.4</version> </dependency>
<dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.el</artifactId> <version>2.2.4</version> </dependency> <!-- el end --> </dependencies>
|
1.2.2 整合Spring Data JPA與Spring
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- 1.dataSource 配置資料庫連接池--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" /> <property name="user" value="root" /> <property name="password" value="111111" /> </bean>
<!-- 2.配置entityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="cn.itcast.entity" /> <property name="persistenceProvider"> <bean class="org.hibernate.jpa.HibernatePersistenceProvider" /> </property> <!--JPA的供應商適配器--> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="generateDdl" value="false" /> <property name="database" value="MYSQL" /> <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" /> <property name="showSql" value="true" /> </bean> </property> <property name="jpaDialect"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" /> </property> </bean>
<!-- 3.事務管理器--> <!-- JPA事務管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
<!-- 整合spring data jpa--> <jpa:repositories base-package="cn.itcast.dao" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
<!-- 4.txAdvice--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
<!-- 5.aop--> <aop:config> <aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" /> </aop:config>
<context:component-scan base-package="cn.itcast"></context:component-scan>
<!--組裝其它 配置文件-->
</beans>
|
1.2.3 使用JPA註解配置映射關係
我們使用昨天案例中的Customer實體類對象,已經配置好了映射關係
package cn.itcast.entity;
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table;
/** * * * 所有的註解都是使用JPA的規範提供的註解, * * 所以在導入註解包的時候,一定要導入javax.persistence下的 */ @Entity //聲明實體類 @Table(name="cst_customer") //建立實體類和表的映射關係 public class Customer {
@Id//聲明當前私有屬性為主鍵 @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主鍵的生成策略 @Column(name="cust_id") //指定和表中cust_id欄位的映射關係 private Long custId;
@Column(name="cust_name") //指定和表中cust_name欄位的映射關係 private String custName;
@Column(name="cust_source")//指定和表中cust_source欄位的映射關係 private String custSource;
@Column(name="cust_industry")//指定和表中cust_industry欄位的映射關係 private String custIndustry;
@Column(name="cust_level")//指定和表中cust_level欄位的映射關係 private String custLevel;
@Column(name="cust_address")//指定和表中cust_address欄位的映射關係 private String custAddress;
@Column(name="cust_phone")//指定和表中cust_phone欄位的映射關係 private String custPhone;
public Long getCustId() { return custId; } public void setCustId(Long custId) { this.custId = custId; } public String getCustName() { return custName; } public void setCustName(String custName) { this.custName = custName; } public String getCustSource() { return custSource; } public void setCustSource(String custSource) { this.custSource = custSource; } public String getCustIndustry() { return custIndustry; } public void setCustIndustry(String custIndustry) { this.custIndustry = custIndustry; } public String getCustLevel() { return custLevel; } public void setCustLevel(String custLevel) { this.custLevel = custLevel; } public String getCustAddress() { return custAddress; } public void setCustAddress(String custAddress) { this.custAddress = custAddress; } public String getCustPhone() { return custPhone; } public void setCustPhone(String custPhone) { this.custPhone = custPhone; } }
|
1.3 使用Spring Data JPA完成需求
1.3.1 編寫符合Spring Data JPA規範的Dao層介面
Spring Data JPA是spring提供的一款對於數據訪問層(Dao層)的框架,使用Spring Data JPA,只需要按照框架的規範提供dao介面,不需要實現類就可以完成資料庫的增刪改查、分頁查詢等方法的定義,極大的簡化了我們的開發過程。
在Spring Data JPA中,對於定義符合規範的Dao層介面,我們只需要遵循以下幾點就可以了:
1.創建一個Dao層介面,並實現JpaRepository和JpaSpecificationExecutor
2.提供相應的泛型
package cn.itcast.dao;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import cn.itcast.entity.Customer;
/** * JpaRepository<實體類類型,主鍵類型>:用來完成基本CRUD操作 * JpaSpecificationExecutor<實體類類型>:用於複雜查詢(分頁等查詢操作) */ public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> { } |
這樣我們就定義好了一個符合Spring Data JPA規範的Dao層介面
1.3.2 完成基本CRUD操作
完成了Spring Data JPA的環境搭建,並且編寫了符合Spring Data JPA 規範的Dao層介面之後,就可以使用定義好的Dao層介面進行客戶的基本CRUD操作
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:applicationContext.xml") public class CustomerDaoTest {
@Autowired private CustomerDao customerDao;
/** * 保存客戶:調用save(obj)方法 */ @Test public void testSave() { Customer c = new Customer(); c.setCustName("傳智播客"); customerDao.save(c); }
/** * 修改客戶:調用save(obj)方法 * 對於save方法的解釋:如果執行此方法是對象中存在id屬性,即為更新操作會先根據id查詢,再更新 * 如果執行此方法中對象中不存在id屬性,即為保存操作 * */ @Test public void testUpdate() { //根據id查詢id為1的客戶 Customer customer = customerDao.findOne(1l); //修改客戶名稱 customer.setCustName("傳智播客順義校區"); //更新 customerDao.save(customer); }
/** * 根據id刪除:調用delete(id)方法 */ @Test public void testDelete() { customerDao.delete(1l); }
/** * 根據id查詢:調用findOne(id)方法 */ @Test public void testFindById() { Customer customer = customerDao.findOne(2l); System.out.println(customer); } } |
第2章 Spring Data JPA的內部原理剖析
2.1 Spring Data JPA的常用介面分析
在客戶的案例中,我們發現在自定義的CustomerDao中,並沒有提供任何方法就可以使用其中的很多方法,那麼這些方法究竟是怎麼來的呢?答案很簡單,對於我們自定義的Dao介面,由於繼承了JpaRepository和JpaSpecificationExecutor,所以我們可以使用這兩個介面的所有方法。
在使用Spring Data JPA時,一般實現JpaRepository和JpaSpecificationExecutor介面,這樣就可以使用這些介面中定義的方法,但是這些方法都只是一些聲明,沒有具體的實現方式,那麼在 Spring Data JPA中它又是怎麼實現的呢?
2.2 Spring Data JPA的實現過程
通過對客戶案例,以debug斷點調試的方式,通過分析Spring Data JPA的原來來分析程式的執行過程
我們以findOne方法為例進行分析
l 代理子類的實現過程
通過JDKDynamicAopProxy 創建動態代理對象
動態代理對象:simpleJpaRepository 實現了JapRepository 和 JpaSpecificationExecutor。對基本增刪改查進行了封裝。最終通過EntityManager完成增刪改查操作
斷點執行到方法上時,我們可以發現註入的customerDao對象,本質上是通過JdkDynamicAopProxy生成的一個代理對象
l 代理對象中方法調用的分析
當程式執行的時候,會通過JdkDynamicAopProxy的invoke方法,對customerDao對象生成動態代理對象。根據對Spring Data JPA介紹而知,要想進行findOne查詢方法,最終還是會出現JPA規範的API完成操作,那麼這些底層代碼存在於何處呢?答案很簡單,都隱藏在通過JdkDynamicAopProxy生成的動態代理對象當中,而這個動態代理對象就是SimpleJpaRepository
通過SimpleJpaRepository的源碼分析,定位到了findOne方法,在此方法中,返回em.find()的返回結果,那麼em又是什麼呢?
帶著問題繼續查找em對象,我們發現em就是EntityManager對象,而他是JPA原生的實現方式,所以我們得到結論Spring Data JPA只是對標準JPA操作進行了進一步封裝,簡化了Dao層代碼的開發
2.3 Spring Data JPA完整的調用過程分析
第3章 Spring Data JPA的查詢方式
3.1 使用Spring Data JPA中介面定義的方法進行查詢
在繼承JpaRepository,和JpaRepository介面後,我們就可以使用介面中定義的方法進行查詢
l 繼承JpaRepository後的方法列表
delete
刪除
findAll
查詢所有
findOne
直接載入
getOne
底層是getRefresh()。是延遲載入的
save:
save方法保存的對象主鍵為null,視為保存。主鍵存在值,視為修改
l 繼承JpaSpecificationExecutor的方法列表
繼承了以 Specification 為參數的動態查詢方法
3.2 使用JPQL的方式查詢
使用Spring Data JPA提供的查詢方法已經可以解決大部分的應用場景,但是對於某些業務來說,我們還需要靈活的構造查詢條件,這時就可以使用@Query註解,結合JPQL的語句方式完成查詢
@Query 註解的使用非常簡單,只需在方法上面標註該註解,同時提供一個JPQL查詢語句即可
public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> { //@Query 使用jpql的方式查詢。 @Query(value="from Customer") public List<Customer> findAllCustomer();
//@Query 使用jpql的方式查詢。?1代表參數的占位符,其中1對應方法中的參數索引 @Query(value="from Customer where custName = ?1") public Customer findCustomer(String custName); } |
此外,也可以通過使用 @Query 來執行一個更新操作,為此,我們需要在使用 @Query 的同時,用 @Modifying 來將該操作標識為修改查詢,這樣框架最終會生成一個更新的操作,而非查詢
@Query(value="update Customer set custName = ?1 where custId = ?2") @Modifying public void updateCustomer(String custName,Long custId); |
3.3 使用SQL語句查詢
Spring Data JPA同樣也支持sql語句的查詢,如下:
/** * nativeQuery : 使用本地sql的方式查詢 */ @Query(value="select * from cst_customer",nativeQuery=true) public void findSql(); |
3.4 方法命名規則查詢
顧名思義,方法命名規則查詢就是根據方法的名字,就能創建查詢。只需要按照Spring Data JPA提供的方法命名規則定義方法的名稱,就可以完成查詢工作。Spring Data JPA在程式執行的時候會根據方法名稱進行解析,並自動生成查詢語句進行查詢
按照Spring Data JPA 定義的規則,查詢方法以findBy開頭,涉及條件查詢時,條件的屬性用條件關鍵字連接,要註意的是:條件屬性首字母需大寫。框架在進行方法名解析時,會先把方法名多餘的首碼截取掉,然後對剩下部分進行解析。
//方法命名方式查詢(根據客戶名稱查詢客戶) public Customer findByCustName(String custName); |
具體的關鍵字,使用方法和生產成SQL如下表所示
Keyword |
Sample |
JPQL |
||
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
||
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
||
Is,Equals |
findByFirstnameIs, findByFirstnameEquals |
… where x.firstname = ?1 |
||
Between |
findByStartDateBetween |
… where x.startDate between ?1 and ?2 |
||
LessThan |
findByAgeLessThan |
… where x.age < ?1 |
||
LessThanEqual |
findByAgeLessThanEqual |
… where x.age ⇐ ?1 |
||
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
||
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
||
After |
findByStartDateAfter |
… where x.startDate > ?1 |
||
Before |
findByStartDateBefore |
… where x.startDate < ?1 |
||
IsNull |
findByAgeIsNull |
… where x.age is null |
||
IsNotNull,NotNull |
findByAge(Is)NotNull |
… where x.age not null |
||
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
||
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
||
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (parameter bound with appended %) |
||
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended %) |
||
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in %) |
||
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
||
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
||
In |
findByAgeIn(Collection ages) |
… where x.age in ?1 |
||
NotIn |
findByAgeNotIn(Collection age) |
… where x.age not in ?1 |
||
TRUE |
findByActiveTrue() |
… where x.active = true |
||
FALSE |
findByActiveFalse() |
… where x.active = false |
||
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstame) = UPPER(?1) |