框架 | Java EE 之 SSM 框架配置与使用

序言

本文章主要围绕 J2EE 中 SSM ( Spring、Spring MVC、MyBatis ) 框架的配置以及使用问题展开学习的,最终目的是输出可复用的版本,以供后续的项目复用。当然,学习和配置的过程难免有不恰当或错误之处,还望朋友指出、斧正。

更新进度

  • 2018.08.13:完成初稿,梳理 SSM 框架初识章节;
  • 2018.08.20:框架内容,梳理 SSM 框架基本内容;
  • 2018.08.24:框架内容,修正 SSM 框架部分内容;
  • 2018.08.26:框架内容,整合 Spring 和 MyBatis;

教学资源

IDE 搭建

IDE 搭建以 Java EE 之 SSH 框架配置与使用 为参考。

版本信息

  • 框架版本:
    • Spring MVC:4.3.x
    • Spring:4.3.x
    • MyBatis:3.4.x
  • 其他组件:
    • MySQL:5.7.x / SQL Server:2008 R2
    • Tomcat:9.0.x
    • Maven:3.3.9
    • JDK:1.8
  • 构建框架,还需要相关依赖库 ( Jar 包 ),为便于你下载 Jar 包或校对依赖是否齐全,具体地,以下列举了 SSM 框架所需要的依赖库。
Spring MyBatis
spring-core mybatis
spring-beans mybatis-spring ( Spring 整合 Mybatis )
spring-context pagehelper ( 分页助手 )
spring-webmvc  
spring-web  
spring-aop ( 整合 Aop )  
aopalliance ( 整合 Aop )  
spring-aspect ( 整合 Aop )  
aspectjweaver ( 整合 Aop )  
spring-tx ( 整合事务 )  
spring-jdbc  
其他依赖库 ( Jar 包 ) 备注
log4j 日志支持
log4j-core、log4j-api 日志支持
slf4j-api、slf4j-log4j12 日志支持
junit Junit 单元测试
c3p0 c3p0 数据库连接池
mysql-connector-java 添加 MySQL 数据库支持
jackson-databind Json 数据转化为类对象
jsp-api JSP
javax.servlet-api Servlet
jstl JSTL 标签库
taglibs-standard-impl JSP 标准标签库

框架初识

  • SSM 框架集由 Spring、SpringMVC、MyBatis 三个开源框架整合而成,是继 SSH (Spring、Struts2、Hibernate ) 之后,目前比较主流的 Java EE 企业级框架,适用于搭建各种大型的企业级应用系统。

    SSM 与 SSH 都有各自的优缺点,而根据你的项目特点而选择合适的框架即可。关于 SSH 框架的配置与使用,感兴趣的可参考本博客的文章:Java EE 之 SSH 框架配置与使用

ORM 框架

  • 对象关系映射(Object Relational Mapping,O/R Mapping,ORM)是通过使用描述对象和数据库之间映射的 元数据,将面向对象语言程序中的 对象 自动 持久化关系数据库 中。本质上就是将数据从一种形式转换到另外一种形式。
  • 实体类数据库表 进行一一对应关系 (映射关系),实体类属性表里面的字段 对应。操作表对应实体类对象,而不需操作数据库表。

SSM 框架

MyBatis

原生 Jdbc 操作数据库

在引入 MyBatis ( 或 Hibernate ) 前,一般通过原生 Jdbc 来操作数据库,而这种方式存在很多问题 $^{[2]}$:

  • 程序创建数据库连接,即需求时创建,用完后关闭。若频繁的创建、关闭数据库连接,显然存在问题。 ( 可以通过数据库连接池来处理这个问题 )

  • 硬编码的地方太多了。例如,数据库连接相关信息,SQL相关信息等。( 可通过使用 XML 配置文件,来避免这个问题 )

  • 实质上,我们编写JDBC是有步骤可循的,即先得到数据库连接对象,传入SQL、输入参数、设置参数,再去执行SQL,然后遍历结果集将数据库 SQL 执行的结果对象转化为 JAVA 对象,然后再去业务处理,最后释放资源。

    那么这个过程,实际上是个 模板方法,能不能抽离出来,更好的去完成这个过程呢?

框架比较
  • Hibernate
    • 优点:
      1) Hibernate 这个纯粹的 ORM 框架,以面向对象的方式来完成数据库的操作。
      2) Hibernate 不需要编写 SQL 即可完全映射,且可通过 HQL (Hibernate Query Language) 语言对 POJO 操作。
      3) Hibernate 提供了日志、缓存、级联等特性。
    • 缺点:
      1) Hibernate 可自由编写 SQL,但非常繁琐,则优化 SQL 实现高性能数据库操作有限制,在互联网项目快速迭代开发中显得过于笨重。
      2) Hibernate 的 级联会造成太多性能的丢失
      3) Hibernate 不支持存储过程。
  • MyBatis
    • 优点:自由书写 SQL、支持动态 SQL、处理列表、动态生成表名、支持存储过程。
    • 缺点:需要编写 SQL 和映射规则,工作量相对较大。
基本介绍
  • MyBatis 前身是 Apache 的开源项目 iBatisiBatis 一词源于 internet 和 abatis 的组合,是一个基于 Java 的持久层框架。

  • MyBatis 是一款持久层框架,它支持定制化 SQL (不屏蔽 SQL)、存储过程以及高级映射。

  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  • MyBatis 可使用 XML配置文件形式或注解形式来配置和映射原生信息,将接口和 POJOs ( Plain Ordinary Java Object,普通 Java 对象 / 实体类 ) 映射成数据库中的记录。

    我们把 POJO 对象和数据库表相互映射的框架称为对象关系映射框架 ( Object Relational )。

架构原理

图6-1SSM架构原理图

图 6-1 SSM架构原理图
  • SqlMapConfig.xml:MyBatis 全局配置文件,配置数据源、事务等运行环境相关信息;SQL文件即是 Mapper.xml

  • SqlSessionFactory:会话工厂,用于创建 SqlSession。

  • SqlSession:即操作数据库的接口,其内部借助 Executor 执行器完成对数据库的操作。

  • MappedStatement:底层封装对象,对操作数据库储存封装,其中包括 SQL 语句 ( Mapper.xml )、输入对象和输出结果类型。

配置文件
  • 全局配置文件:在 Src 根目录下建立并配置 SqlMapConfig.xml

    与 Spring 整合 Hibernate 一样,后期 Spring 整合 MyBatis 后此配置文件可省略。当然,学习阶段代码还是得提供。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>

    <!-- 加载 Java 的配置文件或者声明属性信息 ( 详细见本项目源码 ) -->
    <properties resource="c3p0.properties"></properties>

    <!-- 全局参数配置 -->
    <!-- <settings></settings> -->

    <!-- 自定义别名 -->
    <typeAliases>
    <!-- 单个别名定义
    <typeAlias type="cn.kofes.ssm.pojo.Sample" alias="Sample"/>
    -->

    <!-- 批量别名定义 ( 推荐 )
    | package:指定包名称来为该包下的 pojo 类声明别名,默认的别名就是类名 ( 首字母大小写都可 )
    -->
    <package name="cn.kofes.pojo"/>
    </typeAliases>

    <!-- 配置 MyBatis 的环境信息,与 Spring 整合,该信息由spring来管理 -->
    <environments default="development">
    <environment id="development">
    <!-- 配置 Jdbc 事务控制,由 MyBatis 进行管理 -->
    <transactionManager type="JDBC"></transactionManager>
    <!-- 配置数据源,采用 MyBatis 连接池 -->
    <dataSource type="POOLED">
    <property name="driver" value="${datasource.driverClass}"/>
    <property name="url" value="${datasource.jdbcUrl}"/>
    <property name="username" value="${datasource.user}"/>
    <property name="password" value="${datasource.password}"/>
    </dataSource>
    </environment>
    </environments>

    <!-- 加载映射文件 -->
    <mappers>
    <!-- 单个映射文件添加 -->
    <mapper resource="mapper/SampleMapper.xml"/>

    <!-- 批量加载映射文件:mapper.java 与 mapper.xml 同名,且在同一目录下
    <package name="cn.kofes.ssm.mapper" />
    -->
    </mappers>
    </configuration>
  • Mapper 映射文件:例如 SampleMapper.xml

    • 创建实体类对象 ( POJOs ):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // cn.kofes.pojo.Sample.java
      public class Sample {
      private Integer id;
      private String name;

      public Integer getId() { return id; }
      public void setId(Integer id) { this.id = id; }
      public String getName() { return name; }
      public void setName(String name) { this.name = name; }
      }
    • 在映射文件中配置 SQL 语句,如建立 SampleMapper.xml ( 便于管理,把映射文件统一放置 mapper 文件夹下 ):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      <!-- mapper/SampleMapper.xml -->

      <?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" >

      <!-- namespace 用于绑定 Mapper 代理开发 -->
      <mapper namespace="cn.kofes.mapper.SampleMapper">
      <!--
      | 配置 SQL 语句:例如查找某一元组
      | - id 属性:唯一标识映射文件中的 SQL
      | - parameterType 属性:指定输入参数的类型
      | - resultType 属性:指定输出参数的类型
      | SQL 语句会封装到 MappedStatement 对象中,故 ID 又称为 Statement 的 ID
      | - #{}:表示一个占位符号
      | - #{id}:id 表示接收输入的参数,参数名称就是 id
      -->
      <select id="findCertianTupleById" parameterType="int" resultType="cn.kofes.bean.Sample">
      SELECT * FROM t_sample WHERE id = #{id}
      </select>

      <!-- 插入元组 -->
      <insert id="insertOneTuple" parameterType="cn.kofes.pojo.Sample">
      <!-- 用于自增 ID 的情况,在完成插入后将 ID 返回 user 对象中
      <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
      SELECT LAST_INSERT_ID()
      </selectKey>
      -->

      <!-- 用于非自增 ID 的情况,先产生 ID 放入 user 对象中,再执行保存
      <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
      SELECT UUID()
      </selectKey>
      INSERT INTO t_sample (id, name) VALUES (#{id}, #{name})
      -->
      INSERT TO t_sample(id, name) value(#{id}, #{name})
      </insert>

      <!-- 删除一条元组 -->
      <delete id="deleteOneTupleById" parameterType="java.lang.Integer">
      DELETE FROM t_sample WHERE id = #{id}
      </delete>

      <!-- 更新一条元组 -->
      <update id="updateOneTupleById" parameterType="cn.kofes.pojo.Sample">
      UPDATE t_sample SET id = #{id}, name = #{name}
      WHERE id = #{id}
      </update>
      </mapper>
案例演示
  • 单独使用 MyBatis 框架,根据 id 查询指定用户:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // 便于检验效果,这里以单元测试类实现
    public class DBOperatorTest {
    @Test
    public void testDoSomethingInDB() {
    // 加载 MyBatis 配置文件
    String resource = "SqlMapConfig.xml";
    // 得到配置文件流
    InputStream inputStream = Resource.getResourceAsStream(resource);
    // 创建会话工厂
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
    .build(inputStream);

    // 通过会话工厂,得到 SqlSession 对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通过 SqlSession 操作数据库
    // 第一个参数:namespace + statement id
    // 第二个参数:指定和映射文件中所匹配的 parameterType 相同属性类型的参数

    /* 根据 ID 查询元组
    Sample sample = sqlSession.selectOne(
    "cn.kofes.mapper.SampleMapper.findCertainTupleById", 1);
    */

    /* 插入一条元组
    Sample sample = new Sample();
    sample.setName("邯郸");
    sqlSession.insert(
    "cn.kofes.mapper.SampleMapper.insertOneTuple", sample);
    */


    /* 根据 ID 修改元组
    Sample sample = new Sample();
    sample.setId(14);
    sample.setName("阿斗");
    sqlSession.update(
    "cn.kofes.mapper.SampleMapper.updateOneTupleById", sample);
    */

    sqlSession.commit();
    sqlSession.close();
    }
    }
  • 案例总结:

    • namespace:命名空间,作用是隔离 SQL。在 MyBatis 和 Spring 结合使用时具有特殊的意义,这里暂且使用全限定类名。
    • <select> 等 SQL Command 标签需要一个 ID,还需要输入参数 parameterType,输出参数映射 resultType 等。在 MyBatis 底层封装成了一个 MappedStatement 对象,使用时以 namespace.id 的方式引用即可。
    • #{}${}
      • #{}:表示一个占位符号,用于接收输入参数,类型可以是简单类型,也可是 POJO、HashMap 等。( 通过 OGNL 表达式 读取对象的属性值 )
      • ${}:表示一个拼接符号,会引入 SQL注入,故不建议使用。
  • 思考问题:

    • 我们重点关注的是 SqlSession,它其实是一个 interface,定义了很多操作数据库的接口,其中实现了 Closeable 接口,很明确是使用完毕后需要 close() 的。
    • 它的实现类 DefaultSqlSession 中有一些数据域,比如说 autoCommit,在默认情况下是不开启自动提交的;且方法也并不是 Synchronized 的,这说明 SqlSession 并不是线程安全的,因此我们应该是局部使用 SqlSession,且在使用完毕后 close() 关闭 sqlSession。
MyBatis 原始方法

这种方式,重复的代码太多,现阶段基本弃用,目前使用最多的就是 Mapper 代理开发。

  • 在开始 Mapper 代理开发前,可了解一种 MyBatis 的原始 Dao 开发方法:
    • Step.01.提供 Dao 接口,有增、删、改、查的方法。
    • Step.02.提供 Dao 的实现类,在实现类中利用 Spring 注入 SqlSessionFactory,然后在各个方法中得到 SqlSession,进行操作后,关闭 SqlSession 即可。
  • 便于理解,放上实现代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    // DAO 层接口
    public interface BaseDao {
    public void insertOneTuple(User user) throw Exception;
    public void deleteOneTupleById(int id) throw Exception;
    public void updateOneTupleById(User user) throw Exception;
    public User findCertainById(int id) throw Exception;
    }

    // DAO 层实现类
    public class SampleDaoImpl implements BaseDao {
    private SqlSessionFactory sqlSessionFactory;

    public SampleDaoImpl(SqlSessionFactory sqlSessionFactory)
    throw Exception {
    this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public User findCertainById(int id) throw Exception {
    // 省略实现逻辑...
    }
    @Override
    public void deleteOneTupleById(int id) throw Exception {
    // 省略实现逻辑...
    }
    @Override
    public void updateOneTupleById(User user) throw Exception {
    // 省略实现逻辑...
    }
    @Override
    public User findCertainById(int id) throw Exception {
    // 省略实现逻辑...
    }
    }

    // 单元测试类
    public class SampleDaoTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void setUp() {
    InputStream inputStream =
    Resource.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory =
    new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testFindCertainById() {
    BaseDao sampleDao = new SampleDaoImpl(sqlSessionFactory);
    User user = sampleDao.findCertainById(5);
    System.out.println( user.toString() );
    }
    }
Mapper 代理开发
  • Mapper 映射文件:上一节配置的映射文件 SampleMapper.xml

  • Mapper 接口:

    • 在 SampleMapper.xml 中 namespace 等于 Mapper 的接口地址 ( 全路径地址 );
    • 在 Mapper 接口中的 方法名 和 SampleMapper.xml 中 Statement 的 ID 名称一致;
    • 在 Mapper 接口中的方法 输入参数类型 和 SampleMapper.xml 中 Statement 的 parameterType 指定的类型一致。
    • 在 Mapper 接口中的 方法返回值类型 和 SampleMapper.xml 中 Statement 的 resultType 指定的类型一致。
  • 关于规范的启示:故我们进行 Mapper 的开发应该遵循一些规范,这样 MyBatis 方可自动生成 XXXMapper 类的代理实现类。

    • 保证 XXXMapper.xml 中的 namespace 同 XXXMapper.java 的 全限定名称 一致;
    • 保证 XXXMapper.xml 中的 Statement ID 同 XXXMapper.java 的 方法名称 一致;
    • 保证 XXXMapper.xml 中的 Statement 的输入参数的类型 ( parameterType )、输出参数的类型 ( resultType ) 同 XXXMapper.java 的保持一致.

      图6-2Mapper代理开发规范

      图6-2 Mapper 代理开发规范
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      // Mapper 接口:cn.kofes.ssm.mapper.SampleMapper.java
      public interface SampleMapper {
      public void insertOneTuple(Sample sample);
      public void deleteOneTupleById(Integer id);
      public void updateOneTupleById(Sample sample);
      public Sample findCertainTupleById(Integer id);
      }

      // 单元测试类
      public class SampleMapperTest {

      private SqlSessionFactory sqlSessionFactory;
      private SqlSession sqlSession;

      @Before
      public void setUp() throws IOException {
      // 加载 MyBatis 配置文件,得到配置文件流
      InputStream inputStream =
      Resources.getResourceAsStream("SqlMapConfig.xml");
      // 创建会话工厂
      SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream);
      // 通过会话工厂,得到 SqlSession 对象
      sqlSession = sqlSessionFactory.openSession();
      }

      @Test
      public void testFindCertainTupleById() {
      SampleMapper sampleMapper = sqlSession.getMapper(SampleMapper.class);
      System.out.println( sampleMapper.findCertainTupleById(15).toString() );
      sqlSession.close();
      }
      }
  • 当然不要忘记在全局配置文件 SqlMapConfig.xml 中加载映射文件 ( 参考上述配置文件 )。

POJO 包装类型查询
输入映射
  • 输入映射:通过 parameterType 指定输入参数的类型,类型可以是 简单类型,也可以是 POJOHashMap 类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 定义包装类型 POJO:自定义所需要的查询条件,实现多表查询
    public class POJOCollection {
    /**
    * 为更加形象、理解,这里引入用户和部门的实体类
    * 一个用户对应一个部门,一个部门包含多个用户
    */
    private User user; // User 实体类
    private Department department; // Department 实体类

    // 构造函数初始化
    public POJOCollection() {
    user = new User();
    department = new Department();
    }

    // 生成 Setter 和 Getter 方法
    public void setUser(User user) { this.user = user; }
    public User getUser() { return user; }
    public void setDepartment(Department department) { this.department = department; }
    public Department getDepartment() { return department; }
    }
  • 在映射文件 SampleMapper.xml 中配置 SQL 语句:

    1
    2
    3
    4
    5
    6
    7
    <!--  自定义所需要的查询条件,实现多表查询 -->
    <select id="findPOJOList"
    parameterType="cn.kofes.ssm.pojo.POJOCollection"
    resultType="cn.kofes.ssm.pojo.User">
    SELECT * FROM t_user as u, t_department as d
    WHERE u.uid = #{user.uid} and d.depart_id = #{department.depart_id}
    </select>
  • Mapper 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // Mapper 接口:cn.kofes.ssm.mapper.SampleMapper.java
    public interface SampleMapper {
    public List<Sample> findPOJOList(POJOCollection pojoCollection);
    }

    // 单元测试类
    public class SampleMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws IOException {
    // 同上
    }

    @Test
    public void testFindPOJOList() {
    // Mapper 接口代理
    SampleMapper sampleMapper =
    sqlSession.getMapper(SampleMapper.class);

    POJOCollection pojoCollection = new POJOCollection();
    pojoCollection.getUser().setUid(50);
    pojoCollection.getDepartment().setDepart_id(1);

    List<User> list =
    sampleMapper.findPOJOList(pojoCollection);
    System.out.println( list.get(0).toString() );

    sqlSession.close();
    }
    }
输出映射
  • 输出映射:使用 resultType 进行输出映射,查询列名和 POJO 中的属性名一致,该列才会成功映射。

    若查询出来的列名 ( 通过 AS 自定义的列名 ) 和 POJO 的属性名不一致,通过定义一个 resultMap 对列名和属性名之间作一个映射关系。

  • 定义 resultMap:在映射文件 SampleMapper.xml 中定义;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!--
    | id:resultMap 唯一标识
    | type:
    -->
    <resultMap id="sampleResultMap" type="cn.kofes.ssm.pojo.Sample">
    <!--
    | id 表示查询结果集中的唯一标识
    | colum:查询出来的列名 ( AS 自定义列名 )
    | property:type 指定的 POJO 类型中的属性名
    -->
    <id column="id_" property="id" />
    <!--
    | result 对非 ID 的属性进行映射定义
    -->
    <result column="name_" property="name" />
    </resultMap>

    <!-- 使用 resultMap 作为 Statement 的输出映射类型 -->
    <select id="listCustomView"
    parameterType="int" resultMap="sampleResultMap">
    SELECT id as id_, name as name_
    FROM t_sample
    WHERE id = #{id}
    </select>
  • Mapper 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // Mapper 接口:cn.kofes.ssm.mapper.SampleMapper.java
    public interface SampleMapper {
    public Sample listCustomView(Integer id);
    }

    // 单元测试类
    public class SampleMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws IOException {
    // 同上
    }

    @Test
    public void testListCustomView() {
    // Mapper 接口代理
    SampleMapper sampleMapper =
    sqlSession.getMapper(SampleMapper.class);

    System.out.println( sampleMapper.listCustomView(15) );
    }
    }
映射对比
  • resultType 查询出来的列名 ( 通过 AS 自定义的列名 ) 和 POJO 的属性名需一致,该列才会成功映射。
    resultMap 可根据查询出来的列名指定 POJO 类型中的应的属性名。
  • resultType 返回的是 User 对象,适应较简单的输出结果映射,MyBatis 其实还提供了resultMap 作为复杂输出结果映射。在 高级映射 章节将对比阐述。
动态 SQL

MyBatis 核心是对 SQL 语句进行灵活操作,通过表达式进行判断,对 SQL 进行灵活拼接、组装。

  • SQL 语句 拼接,例如 <where><if> 标签的组合使用;
  • SLQ 语句 抽离,例如 <sql><include> 标签的组合使用;
  • Foreach:向 SQL 传递数组或 List 集合,MyBatis 使用 <foreach> 标签解析。例如,我们需要查询多个 ID 值;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!-- 将通用的 SQL 语句抽离,例如:属性名、表名等 -->
    <sql id="t_sample">t_sample</sql>

    <select id="findPOJOList"
    parameterType="cn.kofes.ssm.pojo.POJOCollection"
    resultType="cn.kofes.ssm.pojo.Sample">
    SELECT * FROM <include refid="t_sample" />

    <!-- 自动去掉条件中第一个 AND 或者 OR -->
    <where>
    <!-- Case.01.注意下面这种写法只适用于 id 类型为 String -->
    <if test=" sample.id != null and sample.id != '' ">
    AND sample.id = #{sample.id}
    </if>
    <if test=" other.id != null and other.id != '' ">
    AND other.id = #{other.id}
    </if>

    <!-- Case.02.若 id 类型为 Integer 需要以下写法
    <if test="_parameter!=null and _parameter > 0">
    AND id = #{id}
    </if>
    -->

    <!-- Case.03.查询多个 ID 值 -->

    <!-- 拼接效果:AND(id = ? OR id = ? OR id = ?)
    <if test="ids != null">
    <foreach collection="ids" item="id"
    open="AND (" close=")" separator="OR">
    id = #{id}
    </foreach>
    </if>
    -->

    <!-- 拼接效果:IN(1, 3, 5)
    <if test="ids != null">
    <foreach collection="ids" item="id"
    open="IN (" close=")" separator=",">
    id = #{id}
    </foreach>
    </if>
    -->
    </where>
    </select>
高级映射

为便于后续章节的学习,这里以用户表、商品表、订单表和订单明细表的实例来阐述问题:

图6-3用户购物清单ER图

图 6-3 用户购物清单 ER 图
  • 用户表 ( User ):购买商品的用户信息;
  • 商品表 ( Item ):商品的明细信息;
  • 订单表 ( Order ):用户所创建的订单;
  • 订单明细表 ( OrderDetail ):每一张订单记录购买的商品信息;

    为便于理解,数据表与 POJO ( 实体类 ) 的名称统一命名为相同名称。

一对一映射

便于理解,我们可以确立一需求:查询订单表,关联查询 创建订单的用户信息

User 和 Order 的关联查询,关键是查询出来的结果如何映射?是用 resultType,还是用 resultMap?

1) resultType

显然我们的实体类 ( User 仅仅包含用户信息,Order 仅仅包含订单信息,Order 中没有 User 的引用 ) 并不能接受关联查询的结果集,那么我们可以考虑使用 OrderVoUser 映射类,即让它 extends Orders,然后在加上一些 User 的属性作为输出结果类型。

OrdersVoUser 映射类的创建原理与上述的 POJOCollection 映射类一样。

2) resultMap

可满足复杂输出结果映射,例如数据库字段名称与查询的字段名称 ( 或通过 AS 声明了别名 ) 不一致的映射;延迟加载;一对一,一对多,多对多等高级映射特性。

这里引用实例说明,即使用 resultMap 将查询结果中的订单信息映射到 Other 对象中,在 Order 类中添加 User 属性,将关联查询出来的用户信息映射到 Other 对象中的 User 属性中。

  • 配置映射文件 Mapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <!-- 映射文件 Mapper.xml -->

    <!-- 订单查询映关联查询用户信息的 resultMap -->
    <resultMap id="OrderUserResultMap" type="cn.kofes.ssm.pojo.Order">
    <!--
    | 配置映射的订单信息
    | colum:订单信息的列名
    | property:订单信息的列名所映射到 POJO 中的属性名
    -->
    <id column="order_id" property="order_id" />
    <result column="createtime" property="createtime" />

    <!--
    | 配置映射的关联用户信息
    | association 标签中 javaType 必须要明确指明类型
    -->
    <association property="user" javaType="cn.kofes.ssh.pojo.User">
    <id column="uid" property="uid" />
    <result column="username" property="username" />
    <result column="age" property="age" />
    <result column="phone" property="phone" />
    </association>
    </resultMap>

    <!-- SQL 语句:查询订单表,关联查询创建订单的用户信息 -->
    <select id="findOrderUserResultMap" resultMap="OrderUserResultMap">
    SELECT
    u.uid, u,username, u.age, u.phone,
    o.order_id
    FROM
    Order AS o, User AS u
    WHERE
    o.id = u.uid
    </select>
  • Mapper 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // SampleMapper 接口
    public interface SampleMapper {
    public List<Order> findOrderUserResultMap();
    }

    // 单元测试类
    public class SampleMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws IOException {
    // 同上
    }

    @Test
    public void testFindOrderUserResultMap() {
    // Mapper 接口代理
    SampleMapper sampleMapper =
    sqlSession.getMapper(SampleMapper.class);

    List<Order> list = sampleMapper.findOrderUserResultMap();
    }
    }
一对多映射

确立一需求:查询订单及订单明细的信息。

  • 实体类配置

    为了满足二级缓存需求,所有实体类实现 Serializablle 接口,实现序列化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class User implements Serializable {

    private Integer uid;
    private String username;
    private Integer age;
    private String phone;

    // Getter 和 Setter 方法要实现
    }

    public class Order implements Serializable {
    private Integer order_id;
    private Date createtime;

    // 把订单所对应的订单明细映射到 orderDetail 属性中
    private List<OrderDetail> orderDetail;

    // Getter 和 Setter 方法要实现
    }

    public class OrderDetail implements Serializable {
    private Integer orderdetail_id;
    private Integer item_id;
    private Integer order_id;
    private Integer amount;

    // Getter 和 Setter 方法要实现
    }

    public class Item implements Serializable {
    private Integer item_id;
    private String itemname;
    private String itemprice;

    // Getter 和 Setter 方法要实现
    }
  • 配置映射文件 Mapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <!-- 映射文件 Mapper.xml -->

    <!-- 查询订单及关联查询订单明细的 resultMap -->
    <resultMap
    id="OrderAndOrderDetailResultMap"
    type="cn.kofes.ssm.pojo.Order"
    extend="OrderUserResultMap">

    <!-- 配置映射的订单信息 -->
    <!-- 配置映射的用户信息 -->
    <!-- 通过继承免去重复配置:extend="Objective ResultMap ID" -->

    <!--
    | 配置映射的订单明细信息:使用 Collection 对关联查询的多条记录
    | 映射到一个 List 集合属性中
    | ofType:指定映射到集合属性中 POJO 的类型
    -->
    <collection property="orderDetail" ofType="cn.kofe.ssm.pojo.OrderDetail">
    <id column="orderdetail_id" property="id">
    <result column="item_id" property="username" />
    <result column="order_id" property="username" />
    <result column="amount" property="amount" />
    </collection>
    </resultMap>

    <!-- SQL:查询订单及订单明细的信息 -->
    <select
    id="findOrderAndOrderDetailResultMap"
    resultMap="OrderAndOrderDetailResultMap">
    SELECT
    o.order_id, o.createtime,
    od.item_id, od.amount,
    i.itemname
    FROM
    Order AS o, Item AS i, OrderDetail As od
    WHERE
    od.order_id = o.order_id AND
    od.item_id = i.item_id
    </select>
  • Mapper 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // SampleMapper 接口
    public interface SampleMapper {
    public List<Order> findOrderAndOrderDetailResultMap();
    }

    // 单元测试类
    public class SampleMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws IOException {
    // 同上
    }

    @Test
    public void testFindOrderAndOrderDetailResultMap() {
    // Mapper 接口代理
    SampleMapper sampleMapper =
    sqlSession.getMapper(SampleMapper.class);

    List<Order> list = sampleMapper.findOrderAndOrderDetailResultMap();
    }
    }
多对多映射

确立一需求:查询用户及用户购买的商品信息。

在多对多映射中,具体的映射思路是:
1) 在 User 实体类中添加订单列表属性 List<Order> orderList,将用户创建的订单映射到 orderList。
2) 在 Order 实体类中添加订单明细列表属性 List<OrderDetail> orderDetail,将订单的明细映射到 orderDetail。
3) 在 OrderDetail 实体类中添加 Item 属性,将订单明细所对应的商品映射到 item。

  • 实体类配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class User implements Serializable {
    private Integer uid;
    private String username;
    private Integer age;
    private String phone;

    // 将用户创建的订单映射到 orderList
    private List<Order> orderList;

    // Getter 和 Setter 方法要实现
    }

    public class Order implements Serializable {
    private Integer order_id;
    private Date createtime;

    // 将订单所对应的订单明细映射到 orderDetail 属性中
    private List<OrderDetail> orderDetail;

    // Getter 和 Setter 方法要实现
    }

    public class OrderDetail implements Serializable {
    private Integer orderdetail_id;
    private Integer item_id;
    private Integer order_id;
    private Integer amount;

    private Item item;

    // Getter 和 Setter 方法要实现
    }

    public class Item implements Serializable {
    private Integer item_id;
    private String itemname;
    private String itemprice;

    // Getter 和 Setter 方法要实现
    }
  • 配置映射文件 Mapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <!-- 映射文件 Mapper.xml -->

    <!-- 查询用户及关联查询用户购买的商品信息的 resultMap -->
    <resultMap
    id="UserAndItemResultMap" type="cn.kofes.ssm.pojo.User">

    <!-- 配置用户信息 -->
    <id column="uid" property="uid">
    <result column="username" property="username" />
    <result column="age" property="age" />
    <result column="phone" property="phone" />

    <!--
    | 配置映射的订单信息:使用 Collection 对关联查询的多条记录
    | 映射到一个 List 集合属性中
    | ofType:指定映射到集合属性中 POJO 的类型
    -->
    <collection property="orderList" ofType="cn.kofe.ssm.pojo.Order">

    <id column="order_id" property="order_id">
    <result column="createtime" property="createtime" />

    <!-- 配置映射的订单明细信息:一个订单包含多个订单明细 -->
    <collection property="orderDetail" ofType="cn.kofe.ssm.pojo.OrderDetail">

    <id column="orderdetail_id" property="id">
    <result column="item_id" property="username" />
    <result column="order_id" property="username" />
    <result column="amount" property="amount" />

    <!-- 配置映射的商品信息:一个订单明细对应一个商品 -->
    <association property="item" javaType="cn.kofes.ssm.pojo.Item">
    <id column="item_id" property="item_id">
    <result column="itemname" property="itemname" />
    <result column="itemprice" property="itemprice" />
    </association>

    </collection>

    </collection>

    </resultMap>

    <!-- SQL:查询用户信息和用户购买过的商品信息 -->
    <select id="findUserAndItemResultMap" resultMap="UserAndItemResultMap">
    SELECT
    u.uid, u.username,
    o.order_id,
    od.item_id, od.amount
    i.itemname, i.itemprice
    FROM
    Order AS o, User AS u, Item AS i, OrderDetail As od
    WHERE
    o.user_id = u.uid AND
    od.order_id = o.order_id AND
    od.item_id = i.item_id
    </select>
  • Mapper 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // SampleMapper 接口
    public interface SampleMapper {
    public List<User> findUserAndItemResultMap();
    }

    // 单元测试类
    public class SampleMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws IOException {
    // 同上
    }

    @Test
    public void testFindUserAndItemResultMap() {
    // Mapper 接口代理
    SampleMapper sampleMapper =
    sqlSession.getMapper(SampleMapper.class);

    List<User> list = sampleMapper.findUserAndItemResultMap();
    }
    }
高级映射总结
  • 一对一映射:在 Order 实体对象中引入 User 属性,且在配置文件 Mapper.xml 中,通过 <association> 标签关联用户信息。
  • 一对多映射:在 Order 实体对象中添加订单明细列表属性 List<OrderDetail> orderDetails,且在配置文件 Mapper.xml 中,通过 <collection> 标签关联订单明细信息。
  • 多对多映射:
    • 在 User 实体对象中添加订单列表属性 List<Order> orderList,将用户创建的订单映射到 orderList。
    • 在 Order 实体对象中添加订单明细列表属性 List<OrderDetail> orderDetail,将订单的明细映射到 orderDetail。
    • 在 OrderDetail 实体对象中添加 Item 属性,将订单明细所对应的商品映射到 item。
延迟加载
  • 延迟加载,即需要时才发出 SQL 查询请求。Hibernate 中有延迟加载,MyBatis 同样提供了这个功能,具体以 <resultMap> 标签的属性完成配置。当然,我们可具体分析 MyBatis 的延迟加载。

  • MyBatis是默认开启延迟加载的么?如果不是,那么显然应该进行延迟加载配置。

    在 MyBatis 的全局核心配置文件 SqlMapConfig.xml 的 <settings> 标签中可设置 lazyLoadingEnabled 以及 aggressiveLazyLoading 属性值。

    • lazyLoadingEnabled:全局性设置懒加载,默认值为 flase,即所有相关联的都会被初始化加载。

    • aggressiveLazyLoading:默认值为 ture,懒加载的对象可能被任何懒属性全部加载。否者,每个属性按需加载。

  • 要实现延迟加载,就得进行 SQL 拆分 ( 若我们的SQL都写在一起,DB要么执行,要么不执行,根本做不到按需查询,所以要延迟加载就得拆分 SQL ) 那么怎么进行拆分呢?

    在 resultMap 中的 <collection> 以及 <association> 标签中有 select 属性,也就是说当使用到了 <collection> 或者 <association> 时才发出 select 属性对应的 SQL。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <!-- 映射文件 Mapper.xml -->

    <!-- 延迟加载的 resultMap -->
    <resultMap
    id="OrderUserLazyLoadingResultMap" type="cn.kofes.ssm.pojo.Order">
    <id column="order_id" property="order_id" />
    <result column="createtime" property="createtime" />

    <!-- 实现对用户信息进行延迟加载
    | select 属性:指定延迟加载需要执行的 Statement 的 ID,
    | 即根据 user_id 查询用户信息的 Statement
    | column 属性:订单信息中关联查询用户信息得到的列,
    -->
    <association
    property="user" javaType="cn.kofes.ssh.pojo.User"
    select="findUserById" column="user_id" />

    </resultMap>

    <!-- Step.01.查询订单管理查询用户信息,用户信息需要延迟加载 -->
    <select
    id="finOrderUserLazyLoading" resultMap="OrderUserLazyLoadingResultMap">
    SELECT * FROM Order
    </select>

    <!-- Step.02.关联查询用户信息:根据订单信息中的 user_id 去关联查询用户信息 -->
    <select
    id="findUserById" parameterType="int" resultType="cn.kofes.ssm.pojo.User">
    SELECT * FROM User WHERE user_id = #{user_id}
    </select>

    <!--
    | 执行顺序:即先执行 finOrderUserLazyLoading,
    | 当需要查询用户时在再执行 findUserById
    -->
  • 我们其实可以借助 MyBatis 去完成延迟加载,也可以自己实现延迟加载。怎么做呢?即有需求时,我们自己调用相应的 Statement 完成即可。
查询缓存

图6-4MyBatis的一级缓存和二级缓存

图 6-4 MyBatis 的一级缓存和二级缓存 $^{[3]}$
  • MyBatis 提供一级缓存和二级缓存:
    • 一级缓存是 sqlSession 级别的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个数据结构 ( HashMap,KEY 主要就是 SqlSession + StatementId 构成 ) 用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域是互不影响的;
    • 二级缓存是 Mapper 级别的缓存 ( 按 Namespace 划分 )。多个 sqlSession 去操作同一个 Mapper 的 SQL 语句,多个 sqlSession 去操作数据库得到数据的数据会存到二级缓存中。二级缓存区域是共享的。
一级缓存
  • 一级缓存的工作原理:
    • 如图 6-3 所示,当 sqlSession 发起 查询 操作,查询结果会 写入 到一级缓存中,待二次 读取 直接从一次缓存中读取即可;若 sqlSession 发起 插入、更新、删除 操作,则会 清空 一级缓存区域中的信息,以避免脏读。
    • 当 sqlSession 关闭时,sqlSession 缓存也随之失效。
  • MyBatis 是默认支持一级缓存的,无需配置开启。
二级缓存
  • 二级缓存是需要配置开启的:

    • 在全局配置文件 SqlMapConfig.xml 中指明,即在 <setting> 标签中的 cacheEnabled 属性,开启全局性缓存开关;
    • 其次在需要开启二级缓存的 XXXMapper.xml 中指明 <cache> 标签。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <mapper namespace="cn.kofes.mapper.SampleMapper">
      <!--
      | 开启本 Mapper namespace 下的二级缓存
      | type 属性:指定 cache 接口的实现类的类型,默认使用 PerpetualCache
      |
      -->
      <cache type="..."/>

      <!-- 当然也可使用分布式缓存:Ehcache
      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
      -->
      </mapper>
  • 所有 POJO 实现序列化接口:若开启二级缓存,其存储介质可在内存、磁盘等,即为了将缓存数据取出执行反序列化操作。

    1
    2
    public class User implements Serializable { /* 省略细节 */ }
    public class Order implements Serializable { /* 省略细节 */ }
  • 二级缓存的局限性:

    例如,我们仅仅更新了其中一个信息,那么意味着二级缓存的清空。而我们真正想要的是刷新该商品的缓存信息而不要影响其他商品的缓存信息。

整合 ehcache
  • ehcache 是一个分布式缓存框架,当然还有 redismemached 等。
  • 分布式缓存,系统为了提高系统并发、性能,一般对系统进行分布式部署 ( 集群部署方式 )。
  • 整合方法:MyBatis 提供的 Cache 接口,实现缓存逻辑即实现 Cache 接口即可。

    • 提供 ehcache 以及 ehcache 与 MyBatis 整合的依赖 ( 或者 Jar 包 );
    • <cache> 标签中,type 属性指明 ehcache 实现 Cache 接口的实现类,既有:
      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    • 提供相关的 ehcache 配置文件.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // MyBatis 提供的 Cache 接口
      public interface Cache {
      String getId();
      void putObject(Object var1, Object var2);
      Object getObject(Object var1);
      Object removeObject(Object var1);
      void clear();
      int getSize();
      ReadWriteLock getReadWriteLock();
      }
逆向工程
  • MyBatis 官方提供逆向工程,可针对单表自动生成 MyBatis 执行所需要的代码 ( mapper.java,mapper.xml,pojo 等 )。
  • 企业实际开发中,常用的逆向工程方式:由数据库表生成 Java 代码。
  • 添加依赖或添加 Jar 包:MyBatis-Generator ( Maven 中央仓库 )
运行逆向工程
  • 参考官方指南 MyBatis Generator,运行逆向工程有如下方式:
    • 使用命令提示符读取 XML 配置文件;
    • 使用 Maven 插件;
    • 使用 Java 程序读取 XML 配置文件 ( 推荐,即不依赖 IDE );
    • 使用 IDE 插件 ( Eclipse );
代码配置文件
  • MyBatis 官方提供了配置文档的范例:MyBatis GeneratorXML Configuration File Reference

    当然,你可以参考下文 $^{[5]}$,搭配注释,互相补充:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    <!-- generatorConfig.xml -->

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
    PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

    <generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">

    <commentGenerator>
    <!-- 是否去除自动生成的注释:true / false -->
    <property name="suppressAllComments" value="true" />
    </commentGenerator>

    <!-- ( 必填 ) 数据库连接的信息:驱动类、连接地址、用户名、密码 -->

    <!-- 连接 MySQL 数据库 -->
    <jdbcConnection driverClass="com.mysql.jdbc.Driver"
    connectionURL="jdbc:mysql://127.0.0.1:3306/db_testdb?characterEncoding=utf-8"
    userId="root" password="123456">
    </jdbcConnection>

    <!-- 连接 Oracle 数据库
    <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
    connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:db_testdb"
    userId="kofe" password="123456">
    </jdbcConnection>
    -->

    <!--
    | false ( 默认 ),把 Jdbc Decimal 和 Numeric 类型解析为 Integer
    | true,把 Jdbc Decimal 和 Numeric 类型解析为 java.math.BigDecimal
    -->
    <javaTypeResolver>
    <property name="forceBigDecimals" value="false" />
    </javaTypeResolver>

    <!-- targetProject:生成 PO 类的位置 -->
    <javaModelGenerator targetPackage="cn.kofes.ssm.pojo"
    targetProject=".\src">
    <!-- enableSubPackages 是否让 schema 作为包的后缀 -->
    <property name="enableSubPackages" value="false" />
    <!-- 从数据库返回的值被清理前后的空格 -->
    <property name="trimStrings" value="true" />
    </javaModelGenerator>

    <!-- targetProject:mapper 映射文件生成的位置 -->
    <sqlMapGenerator targetPackage="cn.kofes.ssm.mapper"
    targetProject=".\src">
    <!-- enableSubPackages 是否让 schema 作为包的后缀 -->
    <property name="enableSubPackages" value="false" />
    </sqlMapGenerator>

    <!-- targetPackage:mapper 接口生成的位置 -->
    <javaClientGenerator type="XMLMAPPER"
    targetPackage="cn.kofes.ssm.mapper"
    targetProject=".\src">
    <!-- enableSubPackages 是否让 schema 作为包的后缀 -->
    <property name="enableSubPackages" value="false" />
    </javaClientGenerator>

    <!-- 指定数据库表 -->
    <table tableName="Item"></table>
    <table tableName="Order"></table>
    <table tableName="Orderdetail"></table>
    <table tableName="User"></table>

    <!--
    <table schema="" tableName="sys_user"></table>
    <table schema="" tableName="sys_role"></table>
    <table schema="" tableName="sys_permission"></table>
    <table schema="" tableName="sys_user_role"></table>
    <table schema="" tableName="sys_role_permission"></table>
    -->

    <!-- 有些表的字段需要指定 Java 类型
    <table schema="" tableName="">
    <columnOverride column="" javaType="" />
    </table>
    -->

    </context>
    </generatorConfiguration>
执行逆向工程生成代码
  • 配置文件配置完成后,执行以下程序即可生成代码,细节如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class GeneratorSqlmap {

    public void generator() throws Exception{

    List<String> warnings = new ArrayList<String>();
    boolean overwrite = true;

    //指定逆向工程配置文件
    File configFile = new File("generatorConfig.xml");

    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
    callback, warnings);
    myBatisGenerator.generate(null);
    }

    // 调用 generator() 执行逆向工程生成代码
    public static void main(String[] args) throws Exception {
    try {
    GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
    generatorSqlmap.generator();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }
  • 值得注意的是,除了生成基本的 POJO 类还多了一个类,就是 xxxExample.java。这个类是给用户自定义 SQL 使用的。到这里就生成好了,下面我们就把生成的代码 拷贝 到自己的工程即可。


Spring

  • Spring 框架是 Java 应用最广的框架,其成功源于它的理念,即 控制反转 ( Inversion of Control,IoC ) 和 面向切面编程 ( Aspect Oriented Programming,AOP )。
  • Spring 框架也可理解为一个轻量级的 IoC 和 AOP 的容器框架。
  • Spring 框架在 SSH 部分 有阐述,在本章节就不具体展开探讨,笔记以补充和整合部分内容为主。

    在 SSH Spring 章节中,要求导入相关 Jar 包,而本文的 SSM 框架要求导入的 Jar 包 ( 或者 Maven 依赖 ) 以 版本信息 的为标准。

Spring 整合 MyBatis

Spring 整合 MyBatis 是通过 MyBatis-Spring 的类库实现的,具体配置和使用信息可参考 MyBatis-Spring 官方使用文档 $^{[4]}$。

原始 DAO 开发

首先要向 DAO 的实现类中注入 SqlSessionFactory ( 交由 Spring 管理,即 Spring 声明式注入 SqlSessionFactory ),然后在各个方法中得到 SqlSession 进行数据库操作。

诉求:在 Spring 运作中,首先加载 Spring 核心配置文件,再创建对象 ( SqlSessionFactory )。而创建对象可通过 New 的方式创建 ( 原始方法 ),但效率太低,则我们可以把加载配置文件和创建对象过程,在服务器启动时完成。故引入 Spring 声明式注入 SqlSessionFactory。

  • 使用封装的方法,即让 Dao 的实现类继承 SqlSessionDaoSupport,而 SqlSessionDaoSupport 类中已经存在 setSqlSessionFactory() 方法,因此我们可以直接向 Dao 的实现类注入 SqlSessionFactory。
  • 另外 SqlSessionDaoSupport 中有 SqlSession,因此使得操作更加简单;而且都交给 Spring 管理,我们自然不必担心忘记关闭 SqlSession。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 下述实现是通过 XML 配置方式加载配置和 Bean ( 注解方式也是可行的,但写法有区别 )

    // Dao 接口
    public interface BaseDao {
    public List<Sample> findSampleById(int id);
    }

    // Dao 层实现类
    public class SampleDaoImpl extends SqlSessionDaoSupport implements BaseDao {

    private SqlSession sqlSession;

    public SampleDaoImpl() throws Expection {
    sqlSession = this.getSqlSession();
    }

    public Sample findSampleById(int id) throws Exception {
    return sqlSession.selectOne(
    "cn.kofes.ssm.mapper.SampleMapper.findSampleById", id);
    }
    }

    // 单元测试类
    public class SampleDaoTest {

    private BaseDao baseDao;

    @Before
    public void setUp() {
    applicationContext =
    new ClassPathXmlApplicationContext("classpath:spring.xml");
    baseDao = (BaseDao) applicationContext.getBean("baseDao");
    }

    @Test
    public void testFindCertainById() {
    System.out.println( baseDao.findSampleById(15).toString() );
    }
    }
Mapper 代理开发
  • Mapper 代理开发的具体思路:

    • SqlSessionFactory 交给 Spring 管理 ( 单例模式 )。

      注意到 SqlSessionFactory 的创建显然需要数据库连接相关的信息,因此需要 数据库连接池 (c3p0);除此之外还需要 MyBatis 的主配置文件 SqlMapConfig.xml

    • Spring 和 MyBatis 整合生成代理对象,使用 SqlSessionFactory 创建 SqlSession ( Spring 和 MyBatis 整合后自动完成此过程 )。

    • 若采用 Mapper 代理的方式开发,我们需要 Spring 管理 Mapper 动态代理实现。

  • 在 Spring 配置文件下配置数据库信息,并整合 MyBatis。后者将 SessionFacotry 交由 Spring 管理:

    c3p0.properties 配置文件位于 Src 根目录下,键值分离以便修改配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    <!-- spring.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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 扫描包下注解,并注册为 Bean -->
    <context:component-scan base-package="cn.kofes.ssm">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller">
    </context:exclude-filter>
    </context:component-scan>

    <!-- 在根目录下新建文件 c3p0.properties,存储数据连接信息 -->
    <context:property-placeholder location="classpath:c3p0.properties" />

    <!-- Dao 层的配置,即 Mybatis 的配置 ( 分模块开发思想 ) -->
    <import resource="spring/mybatis-spring.xml"/>
    </beans>

    <!-- mybatis-spring.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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1. 数据源 : DriverManagerDataSource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <!-- 引用 c3p0.properties 的键值对即可,格式如 ${key.value} -->
    <property name="driverClass" value="${datasource.driverClass}"/>
    <property name="jdbcUrl" value="${datasource.jdbcUrl}"/>
    <property name="user" value="${datasource.user}"/>
    <property name="password" value="${datasource.password}"/>
    <!-- 设置数据库连接池的最大连接数 -->
    <property name="maxPoolSize" value="${datasource.maxPoolSize}"/>
    <!-- 设置数据库连接池的最小连接数 -->
    <property name="minPoolSize" value="${datasource.minPoolSize}"/>
    <!-- 设置数据库连接池的初始化连接数 -->
    <property name="initialPoolSize" value="${datasource.initialPoolSize}"/>
    <!-- 设置数据库连接池的连接最大空闲时间 -->
    <property name="maxIdleTime" value="${datasource.maxIdleTime}"/>
    <!-- c3p0缓存Statement的数量数 -->
    <property name="maxStatements" value="${datasource.maxStatements}"/>
    <!-- 当连接池的连接用完的,从 C3p0 下获取新的连接数 -->
    <property name="acquireIncrement" value="${datasource.acquireIncrement}"/>
    <property name="checkoutTimeout" value="${datasource.checkoutTimeout}"/>
    <property name="idleConnectionTestPeriod" value="${datasource.idleConnectionTestPeriod}"/>
    </bean>

    <!-- 2. 配置和 MyBatis 的整合 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 3. 配置一个可以执行批量的 SqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <constructor-arg name="executorType" value="BATCH"/>
    </bean>

    <!--
    | 4. MyBatis 自动扫描加载 Sql 映射文件: MapperScannerConfigurer
    | 自动扫描出的 Mapper Bean 的 ID 名称为首字母小写的类名
    | 且效果与 SqlMapConfig.xml 中配置批量加载映射文件相同,即 <package name="cn.kofes.ssm.mapper"/>
    | 1) 批量加载映射文件:mapper.java 与 mapper.xml 同名,且在同一目录下
    | 2) 当然,你可以建立同样的文件夹目录 (cn/kofes/ssm/mapper),把 mapper.xml 单独放置资源目录下
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    <property name="basePackage" value="cn.kofes.ssm.mapper" />
    </bean>

    </beans>
  • 代码实现:注解方式实现 Mapper 类的调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 下述实现是通过注解方式加载配置和 Bean ( XML 方式也是可行的,但写法有区别 )

    // Mapper 接口
    public interface SampleMapper {
    public Sample findCertainTupleById(Integer id);
    }

    // 单元测试类
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:spring.xml"})
    public class SampleMapperTest {

    @Autowired
    private SampleMapper sampleMapper;

    @Test
    public void testFindCertainTupleById() {
    System.out.println(
    sampleMapper.findCertainTupleById(15).toString() );
    }
    }

Spring MVC

  • MVC ( Model-View-Controller ) 模式把应用程序 ( 输入逻辑、业务逻辑和 UI 逻辑 )分成不同的方面,同时提供这些元素间的松耦合。
    • Model:模型,封装了应用程序的 数据 和由它们组成的 POJO
    • View:视图,负责把模型数据 渲染到视图 上,将数据以一定形式展现给用户。
    • Controller:负责 处理用户请求,并建立适当的模型把它传递给视图渲染。
  • Spring MVC 把 模型视图控制器 分层,组合成一个有机灵活的系统。
  • Spring MVC 中可定义逻辑视图,通过其提供的解析器找到对应的视图渲染;或在 Controller 的方法内加入注解 ( @ResponseBody ),通过消息转换系统将数据转换为 JSON,提供给前端 Ajax 请求使用。

参考资料