Spring Data JPA入門及深入
一:Spring Data JPA簡介
Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的程式碼即可實現對資料庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴充套件!學習並使用 Spring Data JPA 可以極大提高開發效率!
Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使資料庫層操作更加簡單,方便解耦
1:Spring Data JPA與JPA和hibernate三者關係
我在接下面的文章中都對它們三者進行擴充套件及應用,以及三者的封裝關係及呼叫關係,我下面也會以一張圖說明,如果此時有對JPA還一竅不通的可以參考我之前的一篇關於JPA文章的介紹
關係:其實看三者框架中,JPA只是一種規範,內部都是由介面和抽象類構建的;hibernate它是我們最初使用的一套由ORM思想構建的成熟框架,但是這個框架內部又實現了一套JPA的規範(實現了JPA規範定義的介面),所有也可以稱hibernate為JPA的一種實現方式我們使用JPA的API程式設計,意味著站在更高的角度上看待問題(面向介面程式設計);Spring Data JPA它是Spring家族提供的,對JPA規範有一套更高階的封裝,是在JPA規範下專門用來進行資料持久化的解決方案。
其實規範是死的,但是實現廠商是有很多的,這裡我對hibernate的實現商介紹,如其它的實現廠商大家可以自行去理解,因為規範在這,實現類可以更好別的,面向介面程式設計。
二:SpringDataJPA快速入門(完成簡單CRUD)
1:環境搭建及簡單查詢
-- 刪除庫 -- drop database demo_jpa; -- 建立庫 create database if not exists demo_jpa charset gbk collate gbk_chinese_ci; -- 使用庫 use demo_jpa; -- 建立表 create table if not exists student( sid int primary key auto_increment, -- 主鍵id sname varchar(10) not null, -- 姓名 sage tinyint unsigned default 22, -- 年齡 smoney decimal(6,1), -- 零花錢 saddress varchar(20) -- 住址 )charset gbk collate gbk_chinese_ci; insert into student values (1,"螞蟻小哥",23,8888.8,"安徽大別山"), (2,"王二麻",22,7777.8,"安徽大別山"), (3,"李小二",23,6666.8,"安徽大別山"), (4,"霍元甲",23,5555.8,null), (5,"葉問",22,4444.8,"安徽大別山"), (6,"李連杰",23,3333.8,"安徽大別山"), (7,"馬克思",20,2222.8,"安徽大別山");
<dependencies> <!--單元測試座標 4.12為最穩定--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--Spring核心座標 注:匯入此座標也同時依賴匯入了一些其它jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring對事務管理座標--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring整合ORM框架的必須座標 如工廠/事務等交由Spring管理--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring單元測試座標--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring Data JPA 核心座標--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.10.4.RELEASE</version> </dependency> <!--匯入AOP切入點表示式解析包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <!--Hibernate核心座標--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.4.10.Final</version> </dependency> <!--hibernate對持久層的操作座標--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.4.10.Final</version> </dependency> <!--這下面的2個el座標是使用Spring data jpa 必須匯入的,不匯入則報el異常--> <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> <!--C3P0連線池座標--> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!--MySQL驅動座標--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <!--JAXB API是java EE 的API,因此在java SE 9.0 中不再包含這個 Jar 包。 java 9 中引入了模組的概念,預設情況下,Java SE中將不再包含java EE 的Jar包 而在 java 6/7 / 8 時關於這個API 都是捆綁在一起的 丟擲:java.lang.ClassNotFoundException: javax.xml.bind.JAXBException異常加下面4個座標 --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> </dependencies>pom.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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/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"> <!--配置註解掃描--> <context:component-scan base-package="cn.xw"></context:component-scan> <!--配置C3P0連線池--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo_jpa"></property> <property name="user" value="root"></property> <property name="password" value="123"></property> </bean> <!--建立EntityManagerFactory交給Spring管理,讓Spring生成EntityManager實體管理器操作JDBC--> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <!--配置一個連線池,後期獲取連線的Connection連線物件--> <property name="dataSource" ref="dataSource"></property> <!--配置要掃描的包,因為ORM操作是基於實體類的--> <property name="packagesToScan" value="cn.xw.domain"></property> <!--配置JPA的實現廠家 實現了JPA的一系列規範--> <property name="persistenceProvider"> <bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean> </property> <!--JPA供應商介面卡 因為我上面使用的是hibernate,所有介面卡也選擇hibernate--> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <!--指定當前操作的資料庫型別 必須大寫,底層是一個列舉類--> <property name="database" value="MYSQL"></property> <!--是否自動建立資料庫表--> <property name="generateDdl" value="false"></property> <!--是否在執行的時候 在控制檯列印操作的SQL語句--> <property name="showSql" value="true"></property> <!--指定資料庫方言:支援的語法,如Oracle和Mysql語法略有不同 org.hibernate.dialect下面的類就是支援的語法--> <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"></property> <!--設定是否準備事務休眠會話的底層JDBC連線,即是否將特定於事務的隔離級別和/或事務的只讀標誌應用於底層JDBC連線。--> <property name="prepareConnection" value="false"></property> </bean> </property> <!--JPA方言:高階特性 我下面配置了hibernate對JPA的高階特性 如一級/二級快取等功能--> <property name="jpaDialect"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean> </property> <!--注入JPA的配置資訊 載入JPA的基本配置資訊和JPA的實現方式(hibernate)的配置資訊 hibernate.hbm2ddl.auto:自動建立資料庫表 create:每次讀取配置檔案都會建立資料庫表 update:有則不做操作,沒有則建立資料庫表 --> <property name="jpaProperties"> <props> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean> <!--配置事務管理器 不同的事務管理器有不同的類 如我們當初使用這個事務管理器DataSourceTransactionManager--> <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <!--把配置好的EntityManagerFactory物件交由Spring內部的事務管理器--> <property name="entityManagerFactory" ref="entityManagerFactory"></property> <!--因為entityManagerFactory內部設定資料庫連線了 所有後面不用設定--> <!--<property name="dataSource" ref="dataSource"></property>--> </bean> <!--配置tx事務--> <tx:advice id="txAdvice" transaction-manager="jpaTransactionManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" read-only="false"/> <tx:method name="insert*" propagation="REQUIRED" read-only="false"/> <tx:method name="update*" propagation="REQUIRED" read-only="false"/> <tx:method name="delete*" propagation="REQUIRED" read-only="false"/> <tx:method name="get*" propagation="SUPPORTS" read-only="true"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> <!--如果命名規範直接使用下面2行控制事務--> <!--<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>--> <!--<tx:method name="*" propagation="REQUIRED" read-only="false"/>--> </tx:attributes> </tx:advice> <!--配置AOP切面--> <aop:config> <!--在日常業務中配置事務處理的都是Service層,因為這裡是案例講解,所有我直接在測試類進行 所有我把事務配置這,也方便後期拷貝配置到真專案中--> <aop:pointcut id="pt1" expression="execution(* cn.xw.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> <!--整合SpringDataJPA--> <!--base-package:指定持久層介面 entity-manager-factory-ref:引用其實體管理器工廠 transaction-manager-ref:引用事務 --> <jpa:repositories base-package="cn.xw.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="jpaTransactionManager"></jpa:repositories> </beans>applicationContext.xml 配置檔案(重要)
@Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "sid") private Integer id; @Column(name = "sname") private String name; @Column(name = "sage") private Integer age; @Column(name = "smoney") private Double money; @Column(name = "saddress") private String address; //下面get/set/構造/toString都省略 //注意:我上面的都使用包裝類,切記要使用包裝類, // 原因可能資料庫某個欄位查詢出來的值為空 null }Student實體類及對映關係
//@Repository("studentDao") 這裡不用加入IOC容器 Spring預設幫我們注入 public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> { /* JpaRepository<T,ID>:T:當前表的型別 ID:當前表主鍵欄位型別 功能:用來完成基本的CRUD操作 ,因為內部定義了很多增刪改查操作 JpaSpecificationExecutor<T>:T:當前表的型別 功能:用來完成複雜的查詢等一些操作 */ }StudentDao介面
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Client { //注入資料 @Autowired @Qualifier(value = "studentDao") private StudentDao sd; @Test public void test() { //查詢id為4學生 Student student = sd.findOne(4); System.out.println("開始列印:" + student); //開始列印:Student{id=4, name='霍元甲', age=23, money=5555.8, address='null'} } }測試類
2:SpringDataJPA簡單單表介面方法查詢圖
3:針對上面圖的一些方法示例
在針對上面方法進行操作的時候,我們的Dao介面必須繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>(後面複雜查詢使用),大家也可以去研究一下CrudRepository類,這個類在這裡就不說了,JpaRespository類間接實現了它
①:Repository:最頂層的介面,是一個空的介面,目的是為了統一所有Repository的型別,且能讓元件掃描的時候自動識別。 ②:CrudRepository :是Repository的子介面,提供CRUD的功能 ③:PagingAndSortingRepository:是CrudRepository的子介面,新增分頁和排序的功能 ④:JpaRepository:是PagingAndSortingRepository的子介面,增加了一些實用的功能,比如:批量操作等。 ⑤:JpaSpecificationExecutor:用來做負責查詢的介面 ⑥:Specification:是Spring Data JPA提供的一個查詢規範,要做複雜的查詢,只需圍繞這個規範來設定查詢條件
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Client { //注入資料 @Autowired @Qualifier(value = "studentDao") private StudentDao sd; /****************************查詢操作*****************************/ @Test //查詢並排序 public void findtestA() { //查詢全部 先對age排序後在進行id排序 Sort sort = new Sort(Sort.Direction.DESC, "age", "id"); List<Student> students = sd.findAll(sort); //列印省略 } @Test //查詢並分頁 public void findtestB() { //查詢分頁 page是第幾頁 size 每頁個數 當前是第2頁查詢3個 相當limit 6,3 Pageable pageable = new PageRequest(2, 3); Page<Student> page = sd.findAll(pageable); System.out.println("當前頁:"+page.getNumber()); System.out.println("每頁顯示條目數:"+page.getSize()); System.out.println("總頁數:"+page.getTotalPages()); System.out.println("結果集總數量:"+page.getTotalElements()); System.out.println("是否是首頁:"+page.isFirst()); System.out.println("是否是尾頁:"+page.isLast()); System.out.println("是否有上一頁:"+page.hasPrevious()); System.out.println("是否有下一頁:"+page.hasNext()); System.out.println("結果集:"+page.getContent()); /* 當前頁:2 每頁顯示條目數:3 總頁數:3 結果集總數量:7 是否是首頁:false 是否是尾頁:true 是否有上一頁:true 是否有下一頁:false 結果集:[Student{id=7, name='馬克思', age=20, money=2222.8, address='安徽大別山'}] */ //總結:以後做分頁操作變容易了呀 } @Test //查詢指定學生 public void findtesC() { //建立一個集合 List就是一個Iterable可迭代容器物件,因為實現了Iterable介面 List<Integer> list = new ArrayList<Integer>() {{ add(1);add(5);add(3); }}; List<Student> students = sd.findAll(list); //列印省略 } /****************************更新操作*****************************/ @Test //更新多個學生 更新學生全部地址為 安徽合肥 @Transactional @Rollback(value = false) public void savetestA() { //建立2個集合 第一個集合查詢全部資料,然後把集合裡物件地址改為新的放入新集合上,後面迭代更新 List<Student> list = sd.findAll(); System.out.println(list.size()); List<Student> newlist = sd.findAll(); for (int i = 0; i < list.size(); i++) { Student student = list.get(i); student.setAddress("安徽合肥"); System.out.println(student); newlist.add(student); } List<Student> save = sd.save(newlist); //列印略 } /****************************刪除操作*****************************/ @Test //刪除指定學生 public void deletetest() { List<Integer> list = new ArrayList<Integer>() {{ add(1);add(5);add(3); }}; List<Student> all = sd.findAll(list); sd.deleteInBatch(all); } /** * 刪除方法介紹: * void deleteAll():底層會一條一條刪除 * void delete(Iterable<? extends T> entities):底層會一條一條刪除 * void deleteAllInBatch():底層會生成一個帶or語句刪除 * void deleteInBatch(Iterable<T> entities):底層會生成一個帶or語句刪除 * 如:Hibernate: delete from student where sid=? or sid=? or sid=? */ }針對圖上面的一些方法操作
提取注意點: