文章转载自:https://www.jiweichengzhu.com/article/f3dbfa6f56b542feb6be6801317374ea
如果还有问题,加群交流:686430774
案例代码下载,请移步原文链接!
SSM还有一种框架是springmvc + spring + mybatis,在我个人看来,其实应该叫做SM更合适(就是这个名字实在是太骚了),毕竟springmvc也是spring衍生出来的产品,在这个框架中,springmvc取代了struts作为控制层,spring依旧跟以前一样作为中间层负责解耦和数据源、事务的处理,mybatis依旧是负责dao层的处理。
当时从ssh和ssi转过来的使用它的时候,就给我一种感觉:“这个框架突然就火了,struts突然就不行了”,真的是特别的迅速,这么牛逼的框架,当时也是做了一番笔记,将之前整理的笔记分享给大家。
框架版本:
Spring:4.1.5.RELEASE
Mybatis:3.3.0
在springmvc框架中,controller层也是有spring来管理的,一些请求的映射和转发都是它来完成,跟struts使用filter来处理请求相比,它使用的是servlet来处理,入口是DispatcherServlet,所以在web.xml需要单独为其制定一个spring的配置文件来做一些特殊的处理,如:controller扫描路径、静态资源文件映射、返回数据增强处理、视图解析器,具体的点我们来看一下配置文件:
spring-servlet.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<!-- 使用非默认方式扫描注解 -->
<context:component-scan base-package="com.ssm" use-default-filters="false">
<!-- 只扫描@Controller注解,其他的在spring-context.xml中扫描 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 启用注解驱动 -->
<!-- 自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter -->
<!-- 是springMVC为@Controller分发请求所必须用到的 -->
<mvc:annotation-driven/>
<!-- spring升级到4.X版本之后,会对默认请求后缀进行处理,导致不能随意指定如html之类的后缀,否则response中的content-type会出问题,返回不了json -->
<!-- 这个玩意儿很有用,有时候想模拟静态化页面的时候,就是想用.html作为controller的后缀,否则就只能使用rest风格的无后缀url了 -->
<!--<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<!– Turn off working out content type based on URL file extension, should fall back to looking at the Accept headers –>
<property name="favorPathExtension" value="false"/>
</bean>-->
<!-- 增强处理,配置json和字符的返回,可以指定想用的json转换器 -->
<!--<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/plain;charset=UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
<property name="supportedMediaTypes" value="application/octet-stream;charset=UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>-->
<!-- 在web.xml中配置DispatcherServlet拦截了所有的url,所以这里需要做一个映射才能访问到静态文件,如果依旧拦截的是.do/.action这种,这里就不需要配置映射 -->
<mvc:resources location="/resource/" mapping="/resource/**"/>
<!-- 每个想要对外开放的静态目录,都需要配置映射,也可以使用上面的偷懒方式,放开整个目录 -->
<!--
<mvc:resources location="/resource/css/" mapping="/resource/css/**" />
<mvc:resources location="/resource/js/" mapping="/resource/js/**" />
-->
<!-- 配置视图解析器,最终输出到浏览器上的内容就是从这里渲染出去的 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
除了对controller的配置之外,正常spring的配置也是需要的,跟之前的框架一样,数据源、事务、bean管理等等,不过这里需要注意一点,由于controller在mvc中特殊管理,那这里就不需要重复扫描了,直接来看一下配置文件:
spring-context.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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<!-- 扫描@Controller之外的注解 -->
<context:component-scan base-package="com.ssm">
<!-- 注解@Controller在springMVC的配置文件中扫描进来 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 加载jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 数据源配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="100"/>
<!--连接池中保留的最小连接数。 -->
<property name="minPoolSize" value="1"/>
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="10"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="30"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5"/>
<!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。Default: 0 -->
<!-- 但由于预缓存的statements 属于单个connection而不是整个连接池。 -->
<!-- 所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。 -->
<property name="maxStatements" value="0"/>
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60"/>
<!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
<property name="acquireRetryAttempts" value="30"/>
<!-- 获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。 -->
<!-- 但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。 -->
<!-- 如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false -->
<property name="breakAfterAcquireFailure" value="true"/>
<!-- 因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。 -->
<!-- 建议使用idleConnectionTestPeriod或automaticTestTable,等方法来提升连接测试的性能。Default: false -->
<property name="testConnectionOnCheckout" value="false"/>
</bean>
<!-- 类似于ibatis的sql模板,用来和数据库进行交互 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 指定mybatis配置文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 这里的效果和mybatis-config.xml中配置的mappers一样 -->
<!--
<property name="mapperLocations">
<list>
<value>classpath*:mapper/*.xml</value>
</list>
</property>
-->
<!-- 配置mapper文件的路径,简写,上面的注释中是复杂的写法 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 配置alias的扫描路径,可以不用再mybatis-config.xml中逐个配置 -->
<!-- <property name="typeAliasesPackage" value="com.ssm.entity"/> -->
</bean>
<!-- 单独配置一个Mapper; 这种模式就是得给每个mapper接口配置一个bean -->
<!--
<bean id="bookDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.ssm.dao.BookDao" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="schoolDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.ssm.dao.SchoolDao" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="studentDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.ssm.dao.StudentDao" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
-->
<!-- 扫描并生成相应mapper -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的mapper接口路径 -->
<property name="basePackage" value="com.ssm.dao"/>
<!-- 如果上面配置的SqlSessionFactoryBean的id就是sqlSessionFactory,这一句可以省略不写 -->
<!-- 除非是多数据源,则必须指定相对应的SqlSessionFactoryBean,比如说配置了主从数据源,这里就必须指定对应包走哪个数据源 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启@Transactional注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
在这次的案例中,对于mybatis的配置没有太多的改动,就是在mapper中一些小细节的优化:parameterType和resultType这种可以省略不写,因为mybatis会自动绑定类型,找一个mapper来看一下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssm.dao.StudentDao">
<resultMap id="studentResult" type="com.ssm.entity.Student">
<result property="id" column="id"/>
<result property="name" column="student_name"/>
<result property="sex" column="student_sex"/>
<result property="age" column="student_age"/>
<result property="birthday" column="student_birthday"/>
<result property="hobby" column="student_hobby"/>
<result property="schoolId" column="school_id"/>
<result property="schoolName" column="school_name"/>
</resultMap>
<select id="list" resultMap="studentResult">
select
st.*,
sc.school_name
from
students st,
schools sc
where st.school_id = sc.id
<if test="text != null and text != ''">
<!-- and st.student_name like '%' || #{text} || '%' -->
<!-- 换一种写法,上面的写法可读性不高,还容易手抖写错 -->
and st.student_name like concat('%', #{text}, '%')
</if>
</select>
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into students
(student_name, student_sex, student_age, student_birthday, student_hobby, school_id)
values
(#{name}, #{sex}, #{age}, #{birth}, #{hobby}, #{schoolId})
</insert>
<!-- parameterType和resultType可以省略不写,入参时mybatis会自动绑定type,update和delete的出参默认是int -->
<update id="update">
update students
<set>
<if test="name != null and name != ''">
student_name = #{name},
</if>
<if test="sex != null">
student_sex = #{sex},
</if>
<if test="age != null">
student_age = #{age},
</if>
<if test="birthday != null">
student_birthday = #{birthday},
</if>
<if test="hobby != null and hobby != ''">
student_hobby = #{hobby},
</if>
<if test="schoolId != null">
school_id = #{schoolId},
</if>
</set>
where id = #{id}
</update>
<delete id="delete">
delete
from students
where id = #{id}
</delete>
<select id="getById" resultMap="studentResult">
select
st.*,
sc.school_name
from
students st,
schools sc
where st.school_id = sc.id
and st.id = #{id}
</select>
</mapper>
mapper文件中清爽了很多,不用每一个都带上type来指定,这个在之前的框架案例中没有讲到,算是mybatis的一个优势。
再来看一下controller的写法:
package com.ssm.controller;
import com.ssm.entity.Student;
import com.ssm.service.SchoolService;
import com.ssm.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/student")// 加上映射注解,自定义path
public class StudentController {
@Autowired
private SchoolService schoolService;
@Autowired
private StudentService studentService;
// 日期格式处理
@InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
// 日期格式
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 是否严格按照格式来解析
dateFormat.setLenient(true);
// 注册自定义的格式转换器
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
@RequestMapping(value = "/list", method = RequestMethod.GET)// 定义path,并且指定请求方式,只有get能访问到
public String list(ModelMap modelMap, String text) {
modelMap.put("list", studentService.list(text));
return "/student_list";
}
@RequestMapping(value = "/json_list", method = RequestMethod.GET)// 定义path,并且指定请求方式,只有get能访问到
@ResponseBody// 如果不是返回页面,一定要加上此注解,否则返回不了json数据
public List<Student> json_list() {
return studentService.list(null);
}
@RequestMapping("/toAdd")// 如果不定义请求方式,get和post都可以访问
public String toAdd(ModelMap modelMap, String type) {
modelMap.put("type", type);
modelMap.put("schoolList", schoolService.list(null));
return "/student_maintain";
}
@RequestMapping("/add")
public String add(Student student) {
studentService.add(student);
return "redirect:/student/list";
}
@RequestMapping("/toUpdate")
public String toUpdate(ModelMap modelMap, String type, Integer id) {
modelMap.put("type", type);
modelMap.put("student", studentService.getById(id));
modelMap.put("schoolList", schoolService.list(null));
return "/student_maintain";
}
@RequestMapping("/update")
public String update(Student student) {
studentService.update(student);
return "redirect:/student/list";
}
@RequestMapping("/delete")
public String delete(Integer id) {
studentService.delete(id);
return "redirect:/student/list";
}
}
讲真,不要太清爽,终于不要写一堆get、set了,就可以直接从方法上接收参数了,而且控制器上的注解语义也很清晰,返回json也方便的一批,大家自己感受一下~
最后再提一点,关于spring-servlet.xml中提到4.x版本的后缀问题,大家可能不是很明白,我来个大家做个json返回的演示,大家就能看懂了。
在StudentController中有一个json_list方法是以json格式返回所有的学生列表,而且我已经加上了@ResponseBody注解,但是我直接在浏览器上访问的时候,却提示我406,如下图:
根据响应头信息,我们可以看到spring返回的是html,因为它对我的后缀做了分析,看到我是html后缀,就默认返回text/html类型,而不再是application/json了,也就是说,如果我们想要返回json,那么我们不能使用html做后缀,如:/student/json_list.json、/student/json_list.do、/student/json_list.action,或者什么后缀都不写,如:/student/json_list。
为了解决上面这个问题,我们将配置修改一下,加一个negotiation-manager来处理:
<!-- 启用注解驱动 -->
<!-- 自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter -->
<!-- 是springMVC为@Controller分发请求所必须用到的 -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<!-- spring升级到4.X版本之后,会对默认请求后缀进行处理,导致不能随意指定如html之类的后缀,否则response中的content-type会出问题,返回不了json -->
<!-- 这个玩意儿很有用,有时候想模拟静态化页面的时候,就是想用.html作为controller的后缀,否则就只能使用rest风格的无后缀url了 -->
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<!-- Turn off working out content type based on URL file extension, should fall back to looking at the Accept headers -->
<property name="favorPathExtension" value="false"/>
</bean>
再次访问/student/json_list.html请求,就能正常的返回json了,如下图:
整体来说,SpringMVC还是比Struts简单很多,不管是从代码上还是从配置上来看,都很简洁、清爽!