對 Spring,SpringMVC,SpringData 和 JPA 進行了詳細的整合說明,並實現了一個常見的分頁操作。 ...
原創播客,如需轉載請註明出處。原文地址:http://www.cnblogs.com/crawl/p/7759874.html
----------------------------------------------------------------------------------------------------------------------------------------------------------
筆記中提供了大量的代碼示例,需要說明的是,大部分代碼示例都是本人所敲代碼併進行測試,不足之處,請大家指正~
本博客中所有言論僅代表博主本人觀點,若有疑惑或者需要本系列分享中的資料工具,敬請聯繫 [email protected]
-----------------------------------------------------------------------------------------------------------------------------------------------------------
前言:之前詳細講解過了 JPA 和 SpringData,包括 Spring 和 SpringMVC 也進行了詳細的講述。很早就想來一個整合了,因為事情比較多,所以就一直拖著。
那麼現在就來說說 Spring + SpringMVC + SpringData + JPA(下文中簡寫為 SSSP) 的整合,SSSP 的整合之後再來寫一個 CRUD 的小案例,LZ 不打算把增刪改查都來一遍了,只介紹一個查詢所有員工信息,然後分頁顯示即可。大家可以對比在 JavaWeb 的過程中寫的分頁查詢,看一看是否簡單的許多。下麵進行詳細的介紹~
一、加入 Spring
1. 首先當然要新建一個動態的 WEB 的工程了,取名 sssp
2. 加入 Spring 的 jar 包,觀察這些 jar 包,大家可以發現其中既包含了 Spring 的 jar 包,也包含了 SpringMVC 的 jar 包
3. web.xml 中配置啟動 Spring IOC 的 ContextLoaderListener:
1 <!-- 配置啟動 Spring IOC 的 Listener --> 2 <context-param> 3 <param-name>contextConfigLocation</param-name> 4 <param-value>classpath:applicationContext.xml</param-value> 5 </context-param> 6 7 <listener> 8 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 9 </listener>
其中 LZ 配置的 Spring 的配置文件在 classpath 下,取名為 applicationContext.xml
4. 這一步當然就是在類路徑下創建 Spring 的配置文件:applicationContext.xml 了,在這裡先創建好,稍後進行配置。
二、加入 SpringMVC
1. 首先呢肯定要想到的是先導入 jar 包,這裡在加入 Spring 的時候已經導入,前面也已經做了說明。
2. web.xml 中配置 SpringMVC 的 DispatcherServlet
1 <!-- 配置 SpringMVC 的 DispatcherServlet --> 2 <!-- 使用預設的 SpringMVC 的配置文件,為 [servlet-name]-servlet.xml 放到 WEB-INF 目錄下 --> 3 <servlet> 4 <servlet-name>springDispatcherServlet</servlet-name> 5 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 6 <load-on-startup>1</load-on-startup> 7 </servlet> 8 9 <servlet-mapping> 10 <servlet-name>springDispatcherServlet</servlet-name> 11 <url-pattern>/</url-pattern> 12 </servlet-mapping>
在這裡呢 LZ 刪除了一部分配置,我們使用預設的 SpringMVC 的配置文件,這個預設的 SpringMVC 的配置文件的名字為 :springDispatcherServlet-servlet.xml ,需要註意的是,若使用預設的配置文件,文件的位置需要在 WEB-INF 目錄下
3. 在 WEB-INF 目錄下新建 SpringMVC 的配置文件: springDispatcherServlet-servlet.xml (註意:LZ 的 Eclipse 中是安裝了 Spring 插件的,實質上 LZ 使用 Spring 的插件創建了 SpringMVC 的配置文件,只是文件名不同而已)
4. 然後我們對 SpringMVC 的配置文件 springDispatcherServlet-servlet.xml 進行配置
1)加入命名空間,因為我們在新建配置文件的時候沒有加入命名空間,所以先加入 context 和 mvc 的命名空間。那麼如何加入呢,當我們打開 SpringMVC 的配置文件後,大家可以看到文件的左下角有一些選項卡(如下麵圖片所示),大家點擊第二個選項 Namespaces,
點開後大家吧 context 和 mvc 選項挑上對勾即可。
2)配置自動掃描的包
1 <!-- 配置自動掃描的包:只掃描有 @Controller 和 @ControllerAdvice 註解的包 --> 2 <context:component-scan base-package="com.software.sssp" use-default-filters="false"> 3 <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 4 <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> 5 </context:component-scan>
在配置自動掃描的包時,LZ 使用了 context:include-filter 子標簽,目的就是讓 SpringMVC 只掃描帶有 @Controller 和 @ControllerAdvice 註解的包中的類,為什麼要這樣做呢?因為我們加入 Spring 的時候只創建了 Spring 的配置文件,還沒有進行配置,一會我們配置的時候,也會先配置 Spring 的 IOC 掃描的包,這樣就有可能出現某些包 Spring 的容器掃描了一遍,SpringMVC 又掃描了一遍,這樣就會造成有些對象會創建多次,造成資源的浪費,配置 context:include-filter 標簽就是為瞭解決這個問題,讓 Spring 和 SpringMVC 只掃描各自關註的包即可。
3)配置視圖解析器:
1 <!-- 配置視圖解析器 --> 2 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 3 <property name="prefix" value="/WEB-INF/views/"></property> 4 <property name="suffix" value=".jsp"></property> 5 </bean>
這裡我們使用的是預設的 InternalResourceViewResolver 視圖解析器,然後配置了一個首碼,一個尾碼。這裡 InternalResourceViewResolver 視圖解析器是如何解析工作的,我們配置的 prefix 和 suffix 有什麼用,在 LZ 的 深入淺出 SpringMVC - 1 中已經詳細介紹,大家可以自行查看。
4)既然在配置視圖解析器的時候我們配置了 prefix 和 suffix ,那麼就需要我們在 WEB-INF 目錄下新建一個 views 文件夾,用來存放我們需要的 jsp 頁面
5)然後我們把 SpringMVC 的兩個標配配置上:處理靜態請求資源的 <mvc:default-servlet-handler/> 以及 <mvc:annotation-driven></mvc:annotation-driven>
1 <mvc:default-servlet-handler/> 2 <mvc:annotation-driven></mvc:annotation-driven>
三、加入 JPA
1)使用 JPA 需要導入 HIbernate 和 JPA 的 jar 包
Hibernate 的 jar 包:
JPA 的 jar 包:
2)加入 c3p0 和 MySQL 的驅動
c3p0:
MySQL 驅動:
3)其實在這個小案例中 LZ 還是用了 JPA 的二級緩存,所以還加入了二級緩存相關的 jar 包,和配置文件,這裡就不詳細說明瞭。
關於 JPA 的知識呢,LZ 也寫了幾篇播客,大家可以參考:
JPA + SpringData 操作資料庫原來可以這麼簡單 ---- 深入瞭解 JPA - 1
JPA + SpringData 操作資料庫原來可以這麼簡單 ---- 深入瞭解 JPA - 2
JPA + SpringData 操作資料庫原來可以這麼簡單 ---- 深入瞭解 JPA - 3
四、配置 Spring 的配置文件
1.加入 context 和 tx 的命名空間,關於如何加入命名空間,上文中進行了詳細的說明,此處不再贅述。
2.配置自動掃描的包
<!-- 配置自動掃描的包 --> <context:component-scan base-package="com.software.sssp"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
註意,此處我們使用 context:exclude-filter 來設置不進行掃描的包,原因在上文中也已經詳細說明。
3.配置數據源
1)在類路徑下新建一個 db.properties 文件,設置連接資料庫的基本信息,在這 LZ 只設置必須的屬性
1 jdbc.user=root 2 jdbc.password=qiqingqing 3 jdbc.driverClass=com.mysql.jdbc.Driver 4 jdbc.jdbcUrl=jdbc:mysql://localhost:3306/sssp
2)在 Spring 的配置文件中導入 db.properties 文件,進行數據源的配置
1 <!-- 配置數據源 --> 2 <!-- 導入資源配置文件 --> 3 <context:property-placeholder location="classpath:db.properties"/> 4 5 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 6 <property name="user" value="${jdbc.user}"></property> 7 <property name="password" value="${jdbc.password}"></property> 8 <property name="driverClass" value="${jdbc.driverClass}"></property> 9 <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> 10 </bean>
至此數據源的配置已經結束,在 LZ 的博客中,LZ 不只強調了一次進行分步測試或單元測試的重要性,可以為我們避免不必要的麻煩,接下來就看看我們的數據源是否配置成功。LZ 新建一個測試類 SSSPTest,先來測試一下數據源:
1 public class SSSPTest { 2 3 private ApplicationContext ctx = null; 4 5 { 6 ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 7 } 8 9 @Test 10 public void testDataSource() throws SQLException { 11 DataSource dataSource = ctx.getBean(DataSource.class); 12 System.out.println(dataSource.getConnection()); 13 } 14 15 }
執行 testDataSource 方法,結果為:
這就證明我們的數據源獲取成功!
4. 配置 JPA 的 EntityManagerFactory
1 <!-- 配置 JPA 的 EntityManagerFactory --> 2 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 3 <property name="dataSource" ref="dataSource"></property> 4 <property name="packagesToScan" value="com.software.sssp"></property> 5 <property name="jpaVendorAdapter"> 6 <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> 7 </property> 8 <property name="jpaProperties"> 9 <props> 10 <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> 11 <prop key="hibernate.hbm2ddl.auto">update</prop> 12 <prop key="hibernate.show_sql">true</prop> 13 <prop key="hibernate.format_sql">true</prop> 14 <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> 15 16 <prop key="hibernate.cache.use_second_level_cache">true</prop> 17 <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop> 18 <prop key="hibernate.cache.use_query_cache">true</prop> 19 </props> 20 </property> 21 <!-- 配置使用二級緩存的模式:只允許帶有 @Cacheable 的類使用二級緩存 --> 22 <property name="sharedCacheMode" value="ENABLE_SELECTIVE"></property> 23 </bean>
解釋一下,第 3 行配置數據源,4 行配置掃描的包,5 行配置 JPA 提供商的適配器,8 行配置 JPA 實現產品即 Hibernate 的基本屬性,22 行配置二級緩存相關。
5. 配置 JPA 的註解和基於註解的事務操作
1 <!-- 配置 JPA 的註解 --> 2 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 3 <property name="entityManagerFactory" ref="entityManagerFactory"></property> 4 </bean> 5 6 <!-- 配置基於註解的事務 --> 7 <tx:annotation-driven transaction-manager="transactionManager"/>
到這裡 JPA 的配置已經完成了,我們再來測試一下 JPA 是否配置成功
我們新建我們需要的兩個實體類,Employee 和 Department,為它們添加基本的 JPA 註解
1 package com.software.sssp.entity; 2 3 import java.util.Date; 4 5 import javax.persistence.Entity; 6 import javax.persistence.FetchType; 7 import javax.persistence.GeneratedValue; 8 import javax.persistence.Id; 9 import javax.persistence.JoinColumn; 10 import javax.persistence.ManyToOne; 11 import javax.persistence.Table; 12 import javax.persistence.Temporal; 13 import javax.persistence.TemporalType; 14 15 import org.springframework.format.annotation.DateTimeFormat; 16 17 @Table(name="SSSP_EMPLOYEES") 18 @Entity 19 public class Employee { 20 21 private Integer id; 22 23 private String lastName; 24 25 private String email; 26 27 @DateTimeFormat(pattern="yyyy-MM-dd") 28 private Date Birth; 29 30 private Date createTime; 31 32 private Department department; 33 34 @GeneratedValue 35 @Id 36 public Integer getId() { 37 return id; 38 } 39 40 public void setId(Integer id) { 41 this.id = id; 42 } 43 44 public String getLastName() { 45 return lastName; 46 } 47 48 public void setLastName(String lastName) { 49 this.lastName = lastName; 50 } 51 52 public String getEmail() { 53 return email; 54 } 55 56 public void setEmail(String email) { 57 this.email = email; 58 } 59 60 @Temporal(TemporalType.DATE) 61 public Date getBirth() { 62 return Birth; 63 } 64 65 public void setBirth(Date birth) { 66 Birth = birth; 67 } 68 69 @Temporal(TemporalType.TIMESTAMP) 70 public Date getCreateTime() { 71 return createTime; 72 } 73 74 public void setCreateTime(Date createTime) { 75 this.createTime = createTime; 76 } 77 78 @JoinColumn(name="DEPARTMENT_ID") 79 @ManyToOne(fetch=FetchType.LAZY) 80 public Department getDepartment() { 81 return department; 82 } 83 84 public void setDepartment(Department department) { 85 this.department = department; 86 } 87 88 }
1 package com.software.sssp.entity; 2 3 import javax.persistence.Cacheable; 4 import javax.persistence.Entity; 5 import javax.persistence.GeneratedValue; 6 import javax.persistence.Id; 7 import javax.persistence.Table; 8 9 @Cacheable 10 @Table(name="SSSP_DEPARTMENT") 11 @Entity 12 public class Department { 13 14 private Integer id; 15 16 private String departmentName; 17 18 @GeneratedValue 19 @Id 20 public Integer getId() { 21 return id; 22 } 23 24 public void setId(Integer id) { 25 this.id = id; 26 } 27 28 public String getDepartmentName() { 29 return departmentName; 30 } 31 32 public void setDepartmentName(String departmentName) { 33 this.departmentName = departmentName; 34 } 35 36 }
然後同樣是在 SSSPTest 中執行測試數據源的那個方法,大家會發現,資料庫中多出了兩張表
這就證明 JPA 配置成功
6. 加入 SpringData
1 <!-- 配置 SpringData --> 2 <jpa:repositories base-package="com.software.sssp" 3 entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
五、web.xml 中雜項配置
1. 因為案例中會用到中文,我們配置一個字元編碼的過濾器
1 <!-- 配置字元編碼過濾器 --> 2 <!-- 字元編碼過濾器必須配置在所有過濾器的最前面! --> 3 <filter> 4 <filter-name>CharacterEncodingFilter</filter-name> 5 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 6 <init-param> 7 <param-name>encoding</param-name> 8 <param-value>UTF-8</param-value> 9 </init-param> 10 </filter> 11 12 <filter-mapping> 13 <filter-name>CharacterEncodingFilter</filter-name> 14 <url-pattern>/*</url-pattern> 15 </filter-mapping>
需要註意的是,這個字元編碼過濾器需要配置在所有過濾器的最前面。
2.因為我們的案例中使用 ResultFul 風格的 URL ,所以在配置一個將 POST 請求轉換為 PUT DELETE 請求的 Filter
1 <!-- 配置將 POST 請求轉換為 PUT DELETE 請求的 Filter --> 2 <filter> 3 <filter-name>HiddenHttpMethodFilter</filter-name> 4 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 5 </filter> 6 7 <filter-mapping> 8 <filter-name>HiddenHttpMethodFilter</filter-name> 9 <url-pattern>/*</url-pattern> 10 </filter-mapping>
至此呢,我們的 SSSP 整合所有的配置和測試就完成了!
六、實現分頁操作
SSSP 整合完成後呢,資料庫中也生成了對應的數據表,LZ 向資料庫中寫入了十幾條測試數據。然後我們實現分頁查詢顯示的效果。
思路分析:
1.DAO 層:直接調用 PagingAndSortingRepository 的 findAll(Pageable pageable) 方法,返回 Page 對象即可(註:關於 PagingAndSortingRepository 的使用 LZ 在介紹 SpringData 的博客已經詳細講解過了)。
2.Service 層:把 Controller 傳入的 pageNo 和 pageSize 封裝成為 Pageable 對象,然後調用 DAO 層的方法即可。
3.Controller 層:獲取 pageNo 並對 pageNo 進行校驗,然後調用 Service 層的方法返回 Page 對象,將 Page 對象放入到 Request 域中進行頁面轉發。
4.JSP 頁面:數據的顯示
思路分析完成之後進行編碼實現。
EmployeeRepository:只定義一個繼承 JpaRepository 的介面即可。
1 public interface EmployeeRepository extends JpaRepository<Employee, Integer> { 2 3 4 }
EmployeeService:
1 @Service 2 public class EmployeeService { 3 4 @Autowired 5 private EmployeeRepository employeeRepository; 6 7 @Transactional(readOnly=true) 8 public Page<Employee> getPage(int pageNo, int pageSize) { 9 PageRequest pageable = new PageRequest(pageNo - 1, pageSize); 10 return employeeRepository.findAll(pageable); 11 } 12 13 }
定義了一個 getPage 方法,為該方法添加只讀的事務操作註解,然後調用 DAO 的 findAll() 方法即可,註意 pageNo 是從 0 開始的,所以要對傳入的 pageNo 進行 - 1 的處理。
EmployeeHandler:
1 @Controller 2 public class EmployeeHandler { 3 4 @Autowired 5 private EmployeeService employeeService; 6 7 @RequestMapping("/emps") 8 public String list(@RequestParam(value="pageNo", required=false, defaultValue="1") String pageNoStr, 9 Map<String, Object> map) { 10 11 int pageNo = 1; 12 13 try { 14 //對 pageNo 的校驗 15 pageNo = Integer.parseInt(pageNoStr); 16 if(pageNo < 0) { 17 pageNo = 1; 18 } 19 } catch (Exception e) {} 20 21 Page<Employee> page = employeeService.getPage(pageNo, 5); 22 map.put("page", page); 23 24 return "emp/list"; 25 } 26 27 }
創建了一個 list 方法,傳入的 pageNoStr 被定義成了 String 類型的,11 行定義了一個 int 型的 pageNo,將 pageNoStr 強轉為 int 型,若傳入的是一個非數值型的字元串,則會出異常,出了異常不必理會,pageNo 還是一開始定義的那個 pageNo,為 1。然後調用 Service 層的方法,定義 pageSize 為 5,得到 Page 對象,放入到 request 域中。
list.jsp 進行數據的顯示即可:
1 <c:if test="${page == null || page.numberOfElements == 0 }"> 2 <h3>沒有員工記錄!</h3> 3 </c:if> 4 5 <c:if test="${page != null && page.numberOfElements > 0 }"> 6 7 <table border="1" cellspacing="0" cellpadding="10"> 8 9 <tr> 10 <th>Id</th> 11 <th>LastName</th> 12 13 <th>Email</th> 14 <th>Birth</th> 15 16 <th>CreateTime</th> 17 <th>DepartmentName</th> 18 19 <th>Edit</th> 20 <th>Delete</th> 21 </tr> 22 23 <c:forEach items="${page.content }" var="emp"> 24 25 <tr> 26 <td>${emp.id }</td> 27 <td>${emp.lastName }</td> 28 29 <td>${emp.email }</td> 30 <td>${emp.birth }</td> 31 32 <td>${emp.createTime }</td> 33 <td>${emp.department.departmentName }</td> 34 35 <td> 36 <a href="${pageContext.request.contextPath }/emp/${emp.id}">Edit</a> 37 </td> 38 <td> 39 <a href="${pageContext.request.contextPath }/emp/${emp.id}" class="delete">Delete</a> 40 <input type="hidden" value="${emp.lastName }"> 41 </td> 42 </tr> 43 44 </c:forEach> 45 46 <tr> 47 <td colspan="8"> 48 共 ${page.totalElements} 條記錄 49 共 ${page.totalPages} 頁 50 當前為 ${page.number + 1 } 頁 51 52 <c:if test="${page.number + 1 > 1 }"> 53 <a href="?pageNo=${page.number + 1 - 1 }">上一頁</a> 54 </c:if> 55 56 <c:if test="${page.number + 1 < page.totalPages}"> 57 <a href="?pageNo=${page.number + 1 + 1 }">下一頁</a> 58 </c:if> 59 </td> 60 </tr> 61 62 </table> 63 64 </c:if>
我們註意第 33 行,在顯示 DepartmentName