zoukankan      html  css  js  c++  java
  • Spring、SpringMVC、SpringData + JPA 整合详解

     原创播客,如需转载请注明出处。原文地址:http://www.cnblogs.com/crawl/p/7759874.html 

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

    笔记中提供了大量的代码示例,需要说明的是,大部分代码示例都是本人所敲代码并进行测试,不足之处,请大家指正~

    本博客中所有言论仅代表博主本人观点,若有疑惑或者需要本系列分享中的资料工具,敬请联系 qingqing_crawl@163.com

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

    前言:之前详细讲解过了 JPA 和 SpringData,包括 Spring 和 SpringMVC 也进行了详细的讲述。很早就想来一个整合了,因为事情比较多,所以就一直拖着。

    那么现在就来说说 Spring + SpringMVC + SpringData + JPA(下文中简写为 SSSP) 的整合,SSSP 的整合之后再来写一个  CRUD 的小案例,楼主不打算把增删改查都来一遍了,只介绍一个查询所有员工信息,然后分页显示即可。大家可以对比在 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>

    其中 楼主配置的 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>

    在这里呢 楼主删除了一部分配置,我们使用默认的 SpringMVC 的配置文件,这个默认的 SpringMVC 的配置文件的名字为 :springDispatcherServlet-servlet.xml ,需要注意的是,若使用默认的配置文件,文件的位置需要在 WEB-INF 目录下

    3. 在 WEB-INF 目录下新建 SpringMVC 的配置文件: springDispatcherServlet-servlet.xml (注意:楼主的 Eclipse 中是安装了 Spring 插件的,实质上楼主使用 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>

    在配置自动扫描的包时,楼主使用了 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 有什么用,在楼主的 深入浅出 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)其实在这个小案例中楼主还是用了 JPA 的二级缓存,所以还加入了二级缓存相关的 jar 包,和配置文件,这里就不详细说明了。

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

    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 文件,设置连接数据库的基本信息,在这楼主只设置必须的属性

    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>

    至此数据源的配置已经结束,在楼主的博客中,楼主不只强调了一次进行分步测试或单元测试的重要性,可以为我们避免不必要的麻烦,接下来就看看我们的数据源是否配置成功。楼主新建一个测试类 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 整合完成后呢,数据库中也生成了对应的数据表,楼主向数据库中写入了十几条测试数据。然后我们实现分页查询显示的效果。

    思路分析:

    1.DAO 层:直接调用 PagingAndSortingRepository findAll(Pageable pageable) 方法,返回 Page 对象即可(注:关于 PagingAndSortingRepository  的使用楼主在介绍 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.xml 中配置能够解决懒加载异常问题的 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>

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

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

  • 相关阅读:
    linux driver ------ 交叉工具链(cross toolchain)
    Qt ------ 截图、获取鼠标指定的RGB值
    Qt ------ QWidget 自定义子类使用信号与槽(Q_OBJECT)后 stylesheet 失效
    Qt error ------ incomplete type 'QApplication' used in nested name specifier
    Qt ------ Q_UNUSED
    SpringCloud 组件Eureka参数配置项详解
    过滤器(Filter)与拦截器(Interceptor)的区别
    事务隔离级别
    事务四大特性
    get与post的区别
  • 原文地址:https://www.cnblogs.com/crawl/p/7759874.html
Copyright © 2011-2022 走看看