你的位置:首页 > 软件开发 > Java > Spring、SpringMVC、SpringData + JPA 整合详解

Spring、SpringMVC、SpringData + JPA 整合详解

发布时间:2017-11-01 12:00:21
原创播客,如需转载请注明出处。原文地址:qingqing_crawl@163.com-------------------------------------------------------------------------------------------------- ...

Spring、SpringMVC、SpringData + JPA 整合详解

 原创播客,如需转载请注明出处。原文地址:qingqing_crawl@163.com

-----------------------------------------------------------------------------------------------------------------------------------------------------------

前言:之前详细讲解过了 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 包

Spring、SpringMVC、SpringData + JPA 整合详解

3. web.ContextLoaderListener

1 <!-- 配置启动 Spring IOC 的 Listener -->2  <context-param>3   <param-name>contextConfigLocation</param-name>4   <param-value>classpath:applicationContext.</param-value>5  </context-param>6 7  <listener>8   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>9  </listener>

其中 LZ 配置的 Spring 的配置文件在 classpath 下,取名为 applicationContext.

4. 这一步当然就是在类路径下创建 Spring 的配置文件:applicationContext.

二、加入 SpringMVC

1. 首先呢肯定要想到的是先导入 jar 包,这里在加入 Spring 的时候已经导入,前面也已经做了说明。

2. web.DispatcherServlet

 1 <!-- 配置 SpringMVC 的 DispatcherServlet --> 2  <!-- 使用默认的 SpringMVC 的配置文件,为 [servlet-name]-servlet. 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. ,需要注意的是,若使用默认的配置文件,文件的位置需要在 WEB-INF 目录下

3. 在 WEB-INF 目录下新建 SpringMVC 的配置文件: springDispatcherServlet-servlet.

Spring、SpringMVC、SpringData + JPA 整合详解

4. 然后我们对 SpringMVC 的配置文件 springDispatcherServlet-servlet.

1)加入命名空间,因为我们在新建配置文件的时候没有加入命名空间,所以先加入 context 和 mvc 的命名空间。那么如何加入呢,当我们打开 SpringMVC 的配置文件后,大家可以看到文件的左下角有一些选项卡(如下面图片所示),大家点击第二个选项 Namespaces,

Spring、SpringMVC、SpringData + JPA 整合详解

点开后大家吧 context 和 mvc 选项挑上对勾即可。

Spring、SpringMVC、SpringData + JPA 整合详解

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 页面

Spring、SpringMVC、SpringData + JPA 整合详解

 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 包:

Spring、SpringMVC、SpringData + JPA 整合详解

JPA 的 jar 包:

Spring、SpringMVC、SpringData + JPA 整合详解

2)加入 c3p0 和 MySQL 的驱动

c3p0:

Spring、SpringMVC、SpringData + JPA 整合详解

MySQL 驱动:

Spring、SpringMVC、SpringData + JPA 整合详解

3)其实在这个小案例中 LZ 还是用了 JPA 的二级缓存,所以还加入了二级缓存相关的 jar 包,和配置文件,这里就不详细说明了。

关于 JPA 的知识呢,LZ 也写了几篇播客,大家可以参考:

JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 1

JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 2

JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 3

手把手教你解决无法创建 JPA 工程的问题

 四、配置 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=root2 jdbc.password=qiqingqing3 jdbc.driverClass=com.mysql.jdbc.Driver4 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 ClassPath); 7  } 8   9  @Test10  public void testDataSource() throws SQLException {11   DataSource dataSource = ctx.getBean(DataSource.class);12   System.out.println(dataSource.getConnection());13  }14 15 }

执行 testDataSource 方法,结果为:

Spring、SpringMVC、SpringData + JPA 整合详解

这就证明我们的数据源获取成功!

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 @Entity19 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  @GeneratedValue35  @Id36  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 @Cacheable10 @Table(name="SSSP_DEPARTMENT")11 @Entity12 public class Department {13  14  private Integer id;15  16  private String departmentName;17 18  @GeneratedValue19  @Id20  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 中执行测试数据源的那个方法,大家会发现,数据库中多出了两张表

Spring、SpringMVC、SpringData + JPA 整合详解

这就证明 JPA 配置成功

6. 加入 SpringData

1 <!-- 配置 SpringData -->2  <jpa:repositories base-package="com.software.sssp" 3         entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>

 五、web.

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} 条记录 &nbsp;&nbsp;&nbsp;49      共 ${page.totalPages} 页 &nbsp;&nbsp;&nbsp;50      当前为 ${page.number + 1 } 页 &nbsp;&nbsp;&nbsp;51      52      <c:if test="${page.number + 1 > 1 }">53       <a href="?pageNo=${page.number + 1 - 1 }">上一页</a> &nbsp;&nbsp;&nbsp;54      </c:if>55      56      <c:if test="${page.number + 1 < page.totalPages}">57       <a href="?pageNo=${page.number + 1 + 1 }">下一页</a> &nbsp;&nbsp;&nbsp;58      </c:if>59     </td>60    </tr>61       62   </table>63   64  </c:if>

我们注意第 33 行,在显示 DepartmentName 时使用的是级联属性,为了提高效率,减少 SQL 语句,需要设置 Employee 中的 @ManyToOne(fetch=FetchType.LAZY),但是进行了如此设置使用级联属性会发生懒加载异常(添加了 @Transactional 注解的事务方法,entityManager 的作用范围是从方法开始到方法结束,使用 fetch 为 LAZY,获取的是一个代理对象,若再获取级联属性,则会发生懒加载异常),所以需要在 web.OpenEntityManagerInViewFilter:

 1 <!-- 配置 OpenEntityManagerInViewFilter,解决懒加载异常的问题 --> 2  <filter> 3   <filter-name>OpenEntityManagerInViewFilter</filter-name> 4   <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> 5  </filter> 6   7  <filter-mapping> 8   <filter-name>OpenEntityManagerInViewFilter</filter-name> 9   <url-pattern>/*</url-pattern>10  </filter-mapping>

最后呢定义一个程序的入口 index.jsp ,其中定义一个显示员工的超链接:

1 <h4>2  <a href="emps">List All Employees</a>3 </h4>

到此我们分页显示的功能就完成了,贴一下运行结果:

Spring、SpringMVC、SpringData + JPA 整合详解

相比于原生的 JavaWeb 实现分页操作是不是很简单呢,效率也提高了很多。

原标题:Spring、SpringMVC、SpringData + JPA 整合详解

关键词:Spring

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

可能感兴趣文章

我的浏览记录