08-MyBatis(2)

MyBatis 动态 SQL

简介

  • 动态 SQL 是 MyBatis 强大特性之一。极大的简化我们拼装 SQL 的操作
  • 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似
  • MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作
    • if
    • choose (when,otherwise)
    • trim (where,set)
    • foreach
  • OGNL (Object Graph Navigation Language) 对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。类似于我们的 EL,SpEL 等。
    • 访问对象属性:person.name
    • 调用方法:person.getName()
    • 调用静态属性/方法:@java.lang.Math@PI,@java.util.UUID@randomUUID()
    • 调用构造方法:new cn.edu.nuist.Person('admin').name
    • 运算符:+,-,*,/,%
    • 逻辑运算符:in,not in,>,>=,<,<=,==,!=
    • 注意:xml 中特殊符号如 ",>,< 等这些都需要使用转义字符

if & where

<if> 用于完成简单的判断

<!-- List<Emp> getEmpListByConditions(); -->
<select id="getEmpListByConditions" resultType="Emp">
    SELECT eid, ename, age, sex, did FROM emp WHERE 1 = 1
    <if test="eid != null">
        AND eid = #{eid}
    </if>
    <if test="ename != null and ename != ''">
        AND ename = #{ename}
    </if>
    <if test="age != null and age != '' ">
        AND age = #{age}
    </if>
    <if test="sex == 1 or sex == 0">
        AND sex = #{sex}
    </if>
</select>

<where> 用于添加 where 关键字,并解决 SQL 语句的条件中第一个 AND 或者 OR 的问题

<!-- List<Emp> getEmpListByConditions(); -->
<select id="getEmpListByConditions" resultType="Emp">
    SELECT eid, ename, age, sex, did FROM emp
    <where>
        <!-- 添加 where 关键字,去掉多余的 and -->
        <if test="eid != null">
            AND eid = #{eid}
        </if>
        <if test="ename != null and ename != ''">
            AND ename = #{ename}
        </if>
        <if test="age != null and age != '' ">
            AND age = #{age}
        </if>
        <if test="sex == 1 or sex == 0">
            AND sex = #{sex}
        </if>
    </where>
</select>

Quiz:若 AND 放在条件尾部,此时如果只给 age 条件,SQL 语句末尾就会多出一个 AND

<!-- List<Emp> getEmpListByConditions(); -->
<select id="getEmpListByConditions" resultType="Emp">
    SELECT eid, ename, age, sex, did FROM emp
    <where>
        <!-- 添加 where 关键字,去掉多余的 and -->
        <if test="eid != null">
            eid = #{eid} AND
        </if>
        <if test="ename != null and ename != ''">
            ename = #{ename} AND
        </if>
        <if test="age != null and age != '' ">
            age = #{age} AND
        </if>
        <if test="sex == 1 or sex == 0">
            sex = #{sex}
        </if>
    </where>
</select>

测试时报错:

Preparing: SELECT eid, ename, age, sex, did FROM emp WHERE age = ? AND
Parameters: 40(Integer)

trim

trim 可以在条件判断完的 SQL 语句前后添加或者去掉指定的字符(可解决上述 where 尾部多一个 AND 的问题)

  • prefix:在 SQL 前添加指定的前缀
  • prefixOverrides:去掉 SQL 中指定的前缀
  • suffix:在 SQL 后添加指定的后缀
  • suffixOverrides:去掉 SQL 中指定的后缀
<!-- List<Emp> getEmpListByConditions(); -->
<select id="getEmpListByConditions" resultType="Emp">
    SELECT eid, ename, age, sex, did FROM emp
    <!-- 添加 where 关键字,去掉多余的 and 或者 or -->
    <trim prefix="where" suffixOverrides="and|or">
        <if test="eid != null">
            eid = #{eid} AND
        </if>
        <if test="ename != null and ename != ''">
            ename = #{ename} AND
        </if>
        <if test="age != null and age != '' ">
            age = #{age} AND
        </if>
        <if test="sex == 1 or sex == 0">
            sex = #{sex}
        </if>
    </trim>
</select>

choose(when|otherwise)

choose 主要是用于分支判断,类似于 Java 中的 switch-case,只会满足所有分支中的一个

<!-- void insertEmp(Emp emp); [1|0 → 男|女] -->
<insert id="insertEmp">
    INSERT INTO emp(eid, ename, age, sex) VALUES (
        null, #{ename}, #{age},
        <choose>
            <when test="sex == 1">'男'</when>
            <when test="sex == 0">'女'</when>
            <otherwise>'不详'</otherwise>
        </choose>
    )
</insert>

set

set 主要是用于解决修改操作中 SQL 语句中可能多出逗号的问题

<update id="updateEmpByConditionSet">
    UPDATE emp
    <set>
        <if test="ename != null and ename != ''">
            ename = #{ename},
        </if>
        <if test="age != null and age != ''">
            age = #{age},
        </if>
        <if test="sex == 1 or sex == 0">
            sex = #{sex}
        </if>
    </set>
    WHERE eid = #{eid}
</update>

foreach

foreach 主要用于循环迭代。

  • collection:要迭代的集合
  • item:当前从集合中迭代出的元素
  • open:循环体的开始字符
  • close:循环体的结束字符
  • separator:元素与元素之间的分隔符
  • index:迭代的是 List 集合,index 表示的当前元素的下标;迭代的 Map 集合,index 表示的当前元素的 key

  • 批量删除员工
    • 方式一
      <!-- void deleteEmps(String eids); -->
      <delete id="deleteEmps">
          DELETE FROM emp WHERE eid IN (${value})
      </delete>
      ===============================================
      Preparing: DELETE FROM emp WHERE eid IN (10, 12)
      Parameters:
      Updates: 2
      
    • 方式二
      <!-- void deleteEmpsByList(List<Integer> eids); -->
      <delete id="deleteEmpsByList">
          DELETE FROM emp WHERE eid IN
          <foreach collection="list" item="eid" separator="," open="(" close=")">
              #{eid}
          </foreach>
      </delete>
      =============================================================
      Preparing: DELETE FROM emp WHERE eid IN ( ? , ? )
      Parameters: 9(Integer), 6(Integer)
      Updates: 2
      
  • 批量插入
    <!-- void batchInsertEmps(@Param("emps")Emp[] emps); -->
    <insert id="batchInsertEmps">
        INSERT INTO emp(eid, ename, age, sex) VALUES
        <foreach collection="emps" item="emp" separator=",">
            (null, #{emp.ename}, #{emp.age}, #{emp.sex})
        </foreach>
    </insert>
    
  • 批量修改
    <!--
    把每条数据修改为相同的内容
        UPDATE emp SET ... WHERE eid IN (1, 2, 3);
        UPDATE emp SET ... WHERE eid=1 OR eid=2 OR eid=3;
    把每条数据修改为相对应内容 (一次执行多条 SQL 语句要求 jdbc.url 后有参数 allowMultiQueries)
        UPDATE emp SET ... WHERE eid=1;
        UPDATE emp SET ... WHERE eid=2;
        UPDATE emp SET ... WHERE eid=3;
     -->
    <!-- void batchUpdateEmps(@Param("emps")Emp[] emps); -->
    <!-- jdbc.url=jdbc:mysql:///test?allowMultiQueries=true -->
    <update id="batchUpdateEmps">
        <foreach collection="emps" item="emp">
            UPDATE emp SET ename=#{emp.ename}, age=#{emp.age}
                , sex=#{emp.sex} WHERE eid=#{emp.eid};
        </foreach>
    </update>
    

sql

sql 标签是用于抽取可重用的 sql 片段,将相同的,使用频繁的 SQL 片段抽取出来,单独定义,方便多次引用。

  • 抽取 SQL
    <sql id="empColumns">SELECT eid, ename, age, sex FROM emp</sql>
    
  • 引用 SQL
    <include refid="empColumns"/>
    

MyBatis 缓存机制

缓存机制简介

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率
  • MyBatis系统中默认定义了两级缓存:一级缓存、二级缓存
  • 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启
  • 二级缓存需要手动开启和配置,它是基于 namespace 级别的缓存
  • 为了提高扩展性。MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存

一级缓存

一级缓存的使用

  • 一级缓存(local cache),即本地缓存,作用域默认为 SqlSession。当 Session flush 或 close 后,该 Session 中的所有 Cache 将被清空
  • 本地缓存不能被关闭,但可以调用 clearCache() 来清空本地缓存,或者改变缓存的作用域
  • 在 MyBatis3.1 之后,可以配置本地缓存的作用域。在 mybatis.xml 中配置
  • 一级缓存的工作机制
    • 同一次会话期间只要查询过的数据都会保存在当前 SqlSession 的一个 Map 中
    • key:hashCode + 查询的 SqlId + 编写的 sql 查询语句 + 参数
@Test
public void testFirstCache() throws IOException {
    SqlSession session = getSqlSessionFactory().openSession(true);
    EmpMapper mapper = session.getMapper(EmpMapper.class);
    Emp emp1 = mapper.getEmpByEid("3");
    Emp emp2 = mapper.getEmpByEid("3");
    System.out.println(emp1);
    System.out.println("+++++++++++++++++++++");
    System.out.println(emp2);
}

======================= 打印控制台 =======================
Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
Parameters: 3(String)
Total: 1
Emp [eid=3, ename=root, age=30, sex=0]
+++++++++++++++++++++
Emp [eid=3, ename=root, age=30, sex=0]

一级缓存失效的几种情况

  • 不同的 SqlSession 对应不同的一级缓存
  • 同一个 SqlSession 但是查询条件不同
  • 同一个 SqlSession 两次查询期间执行了任何一次增删改操作(无论成功与否)
  • 同一个 SqlSession 两次查询期间手动清空了缓存
@Test
public void testFirstCache() throws IOException {
    SqlSession session = getSqlSessionFactory().openSession(true);
    EmpMapper mapper = session.getMapper(EmpMapper.class);
    Emp emp1 = mapper.getEmpByEid("13");
    System.out.println(emp1);
    System.out.println("+++++++++++++++++++++");
    mapper.deleteEmpsByString("3"); // 不存在该记录
    System.out.println("+++++++++++++++++++++");
    Emp emp2 = mapper.getEmpByEid("13");
    System.out.println(emp2);
}
======================= 打印控制台 =======================
Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
Parameters: 13(String)
Total: 1
Emp [eid=13, ename=AAA, age=22, sex=0]
+++++++++++++++++++++
Preparing: DELETE FROM emp WHERE eid IN (3)
Parameters:
Updates: 0
+++++++++++++++++++++
Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
Parameters: 13(String)
Total: 1
Emp [eid=13, ename=AAA, age=22, sex=0]

二级缓存

二级缓存的使用

  • 简述
    • 二级缓存(second level cache),全局作用域缓存
    • 二级缓存默认不开启,需要手动配置
    • MyBatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现 Serializable 接口
    • 一级缓存是 SqlSession 级别的,二级缓存是映射文件级别的。二级缓存在 SqlSession 关闭或提交之后才会生效
  • 二级缓存使用的步骤
    • 全局配置文件中开启二级缓存 <setting name="cacheEnabled" value="true"/>
    • 需要使用二级缓存的映射文件处使用 cache 配置缓存 <cache />
    • 注意:POJO 需要实现 Serializable<I>
  • 二级缓存相关的属性
    • eviction:缓存回收策略(默认的是 LRU)
      • LRU – 最近最少使用的:移除最长时间不被使用的对象
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们
      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
    • flushInterval:刷新间隔,单位毫秒。默认情况不设置,也就是没有刷新间隔,代表永不刷新,缓存仅在调用语句时刷新
    • size:引用数目,正整数。代表缓存最多可以存储多少个对象,太大容易导致内存溢出
    • readOnly:只读
      • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势
      • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false
  • 代码测试1
    @Test
    public void testSecondCache() throws IOException {
        SqlSession session = getSqlSessionFactory().openSession(true);
        EmpMapper mapper1 = session.getMapper(EmpMapper.class);
        Emp emp1 = mapper1.getEmpByEid("13");
        System.out.println(emp1);
        System.out.println("++++++++++++++++++++++++");
        EmpMapper mapper2 = session.getMapper(EmpMapper.class);
        Emp emp2 = mapper2.getEmpByEid("13");
        System.out.println(emp2);
    }
    ======================= 打印控制台 =======================
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.0 ← 命中率
    Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
    Parameters: 13(String)
    Total: 1
    Emp [eid=13, ename=AAA, age=22, sex=0]
    ++++++++++++++++++++++++
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.0 → 二级缓存未生效
    Emp [eid=13, ename=AAA, age=22, sex=0]
    
  • 代码测试2
    @Test
    public void testSecondCache() throws IOException {
        SqlSession session = getSqlSessionFactory().openSession(true);
        EmpMapper mapper1 = session.getMapper(EmpMapper.class);
        Emp emp1 = mapper1.getEmpByEid("13");
        System.out.println(emp1);
        EmpMapper mapper2 = session.getMapper(EmpMapper.class);
        Emp emp2 = mapper2.getEmpByEid("13");
        System.out.println(emp2);
        // >>> 二级缓存在 SqlSession 关闭或提交之后才会生效 <<<
        session.commit();
        EmpMapper mapper3 = session.getMapper(EmpMapper.class);
        Emp emp3 = mapper3.getEmpByEid("13");
        System.out.println(emp3);
        session.commit();
        EmpMapper mapper4 = session.getMapper(EmpMapper.class);
        Emp emp4 = mapper4.getEmpByEid("13");
        System.out.println(emp4);
    }
    ======================= 打印控制台 =======================
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.0
    Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
    Parameters: 13(String)
    Total: 1
    Emp [eid=13, ename=AAA, age=22, sex=0]
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.0
    Emp [eid=13, ename=AAA, age=22, sex=0]
    put added 0 on heap
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.3333333333333333 [1/3]
    Emp [eid=13, ename=AAA, age=22, sex=0]
    Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.5 [2/4]
    Emp [eid=13, ename=AAA, age=22, sex=0]
    

缓存的相关配置

  • 全局 setting 的 cacheEnable:配置二级缓存的开关,一级缓存一直是打开的
  • <select> 标签的 useCache 属性:配置这个 select 是否使用二级缓存(一级缓存是一直使用的)
  • sqlSession.clearCache():只是用来清除一级缓存
  • SQL 标签的 flushCache 属性:
    • 增删改默认 flushCache=true;查询默认 flushCache=false
    • 增删改 SQL 执行以后,会同时清空一级和二级缓存
      @Test
      public void testSecondCache() throws IOException {
          SqlSession session = getSqlSessionFactory().openSession(true);
          EmpMapper mapper1 = session.getMapper(EmpMapper.class);
          Emp emp1 = mapper1.getEmpByEid("13");
          System.out.println(emp1);
          session.commit();
          System.out.println("++++++++++++++++");
          EmpMapper mapper2 = session.getMapper(EmpMapper.class);
          Emp emp2 = mapper2.getEmpByEid("13");
          System.out.println(emp2);
          System.out.println("++++++++++++++++");
          mapper2.deleteEmpsByString("3"); // 删除操作
          System.out.println("++++++++++++++++");
          EmpMapper mapper3 = session.getMapper(EmpMapper.class);
          Emp emp3 = mapper3.getEmpByEid("13");
          System.out.println(emp3);
      }
      ======================= 打印控制台 =======================
      Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.0
      Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
      Parameters: 13(String)
      Total: 1
      Emp [eid=13, ename=AAA, age=22, sex=0]
      put added 0 on heap
      ++++++++++++++++
      Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.5
      Emp [eid=13, ename=AAA, age=22, sex=0]
      ++++++++++++++++
      fault removed 0 from heap
      fault added 0 on disk
      Preparing: DELETE FROM emp WHERE eid IN (3)
      Parameters:
      Updates: 0
      ++++++++++++++++
      Cache Hit Ratio [cn.edu.nuist.mapper.EmpMapper]: 0.6666666666666666
      Preparing: SELECT eid, ename, age, sex FROM emp WHERE eid=?
      Parameters: 13(String)
      Total: 1
      Emp [eid=13, ename=AAA, age=22, sex=0]
      

整合第三方缓存

  • 为了提高扩展性。MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存
  • EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider
  • 整合 EhCache 缓存的步骤
    • 导入 ehcache 包,以及整合包,日志包:ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar、slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
    • 编写 ehcache.xml 配置文件
      <?xml version="1.0" encoding="UTF-8"?>
      <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
      <!-- 磁盘保存路径 -->
      <diskStore path="D:ehcache" />
      <defaultCache
          maxElementsInMemory="1000"
          maxElementsOnDisk="10000000"
          eternal="false"
          overflowToDisk="true"
          timeToIdleSeconds="120"
          timeToLiveSeconds="120"
          diskExpiryThreadIntervalSeconds="120"
          memoryStoreEvictionPolicy="LRU">
      </defaultCache>
      </ehcache>
      
    • 在映射文件中配置 cache 标签:<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

PageHelper 分页插件

PageHelper 是 MyBatis 中非常方便的第三方分页插件。

使用步骤

  1. 导入相关包 pagehelper-x.x.x.jar 和 jsqlparser-0.9.5.jar
  2. 在 MyBatis 全局配置文件中配置分页插件
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
  3. 使用 PageHelper 提供的方法进行分页
  4. 可以使用更强大的 PageInfo 封装返回结果

Page 对象的使用

在查询之前通过 PageHelper.startPage(pageNum, pageSize) 设置分页信息,该方法返回 Page 对象。

@Test
public void testPageHelper()  throws Exception{
    SqlSessionFactory ssf = getSqlSessionFactory();
    SqlSession session = ssf.openSession();
    try {
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
        //设置分页信息
        Page<Object> page = PageHelper.startPage(9, 1);
        List<Employee> emps = mapper.getAllEmps();
        for (Employee employee : emps) {
            System.out.println(employee);
        }
        System.out.println("====== 获取分页相关的信息 ======");
        System.out.println("当前页: " + page.getPageNum());
        System.out.println("总页码: " + page.getPages());
        System.out.println("总条数: " + page.getTotal());
        System.out.println("每页显示的条数: " + page.getPageSize());
    } finally {
        session.close();
    }
}

PageInfo 对象的使用

在查询完数据后,使用 PageInfo 对象封装查询结果,可以获取更详细的分页信息以及可以完成分页逻辑。

PageUtil:

// 首页 上一页 1 2 3 4 5 下一页 尾页
public class PageUtil {
    public static String getPageInfo(PageInfo<Emp> pageInfo) {
        String path = "/mybatis/";
        StringBuilder builder = new StringBuilder();
        // 1. 拼接首页
        builder.append("<a href='" + path + "emps/1'>首页</a>&nbsp;&nbsp;");
        // 2. 拼接上一页
        if(pageInfo.isHasPreviousPage()) {
            builder.append("<a href='" + path + "emps/"
                    + pageInfo.getPrePage() + "'>上一页</a>&nbsp;&nbsp;");
        } else {
            builder.append("上一页&nbsp;&nbsp;");
        }
        // 3. 拼接页码
        int[] nums = pageInfo.getNavigatepageNums();
        for(int i : nums) {
            if(i == pageInfo.getPageNum()) {
                builder.append(i+"&nbsp;&nbsp;");
            } else {
                builder.append("<a href='" + path
                        + "emps/"+i+"'>"+i+"</a>&nbsp;&nbsp;");
            }
        }
        // 4. 拼接下一页
        if(pageInfo.isHasNextPage()) {
            builder.append("<a href='" + path + "emps/"
                    + pageInfo.getNextPage()+"'></a>&nbsp;&nbsp;");
        } else {
            builder.append("下一页&nbsp;&nbsp;");
        }
        // 5. 拼接尾页
        builder.append("<a href='" + path + "emps/"
                    + pageInfo.getPages() + "'>尾页</a>");

        return builder.toString();
        }
    }

SSM 整合

整合步骤

导入 jar 包

  • Spring
  • SpringMVC
  • MyBatis
  • 第三方支持:log4j,pageHelper,AspectJ,Jackson,Jstl

搭建 SpringMVC

  • web.xml
    • DispatcherServlet
    • HiddenHttpMethodFilter
    • CharacterEncodingFilter
  • springMVC.xml
    • 扫描控制层组件
    • 视图解析器
    • DefaultServlet
    • MVC Driver
    • 可选:MultipartResolver(文件解析器),拦截器

整合 SpringMVC 和 Spring

  • web.xml
    • ContextLoaderListener
    • context-param
  • spring.xml
    • 扫描组件(排除控制层)
    • 事务管理器

搭建 MyBatis

  • 核心配置文件
  • mapper 接口和 mapper 映射文件

Spring 整合 MyBatis

  • spring.xml
    • properties 文件的引入
    • DataSource 数据源的配置
    • 事务管理器
    • 开启事务驱动
    • SqlSessionFactoryBean(管理 SqlSession)
    • MapperScannerConfigurer
  • 不同 MyBatis 版本整合 Spring 时使用的适配包:

配置文件

web.xml

<!-- 配置编码过滤器 -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- REST 请求方式处理 -->
<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- SpringMVC 核心控制器 DispatcherServlet -->
<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 配置 Spring 监听器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 自定义 Spring 配置文件的位置和名称 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>

spring.xml

<!-- 扫描组件 -->
<context:component-scan base-package="cn.edu.nuist.ssm">
    <context:exclude-filter type="annotation"
            expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!-- 引入资源文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<!-- 声明事务管理器 -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 管理 MyBatis 操作数据库的会话对象 SqlSession -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 设置 MyBatis 核心配置文件的路径 -->
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <!-- 设置数据源 -->
    <property name="dataSource" ref="dataSource"></property>
    <!-- 设置类型别名 -->
    <property name="typeAliasesPackage" value="cn.edu.nuist.ssm.bean"></property>
    <!-- 设置映射文件的路径 -->
    <property name="mapperLocations" value="classpath:cn/edu/nuist/ssm/mapper/*.xml"></property>
</bean>

<!-- 在所设置的包下,将所有的接口生成动态代理实现类对象,并由 Spring 容器管理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="cn.edu.nuist.ssm.mapper"></property>
</bean>

springMVC.xml

<!-- 扫描控制层组件 -->
<context:component-scan base-package="cn.edu.nuist.ssm.controller"></context:component-scan>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

<!-- 默认Servlet -->
<mvc:default-servlet-handler/>

<!-- MVC 驱动 -->
<mvc:annotation-driven />

mybatis-config.xml

<configuration>
    <settings>
        <!-- 将下划线映射成驼峰, 例如:user_name → userName -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 是否开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
</configuration>
原文地址:https://www.cnblogs.com/liujiaqi1101/p/13672232.html