论坛首页 Java企业应用论坛

springmvc + spring + mybatis整合搭建图文教程

浏览 1263 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2019-06-03  

文章转载自: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">
        &lt;!&ndash; Turn off working out content type based on URL file extension, should fall back to looking at the Accept headers &ndash;&gt;
        <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,如下图:

springmvc + spring + mybatis整合搭建图文教程

根据响应头信息,我们可以看到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 + spring + mybatis整合搭建图文教程

整体来说,SpringMVC还是比Struts简单很多,不管是从代码上还是从配置上来看,都很简洁、清爽!

论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics