使用IntelliJ IDEA开发SpringMVC网站(四)集成MyBatis

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(三)视图使用Freemarker》文章中我们使用FreeMarker替换了默认的JSP作为视图的实现,本文我们看看如何集成MyBatis。

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。MyBatis是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

关于MyBatis的更多介绍请参考查阅官方文档:http://www.mybatis.org/mybatis-3/zh/index.html

要在Spring中集成MyBatis,我们还需要用到MyBatis-Spring

MyBatis-Spring 是什么

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 使用这个类库中的类, Spring 将会加载必要的 MyBatis 工厂类和 session 类。 这个类库也提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中。 而且它也会处理事务, 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常,译者注)中。最终,它并 不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。

产生背景

正如第二版那样,Spring 3.0 也仅支持 iBatis2。那么,我们就想将 MyBatis3 的支持添加 到 Spring3.0(参考 Spring Jira 中的问
题)中。而不幸的是,Spring 3.0 的开发在 MyBatis 3.0 官方发布前就结束了。 因为 Spring 开发团队不想发布一个基于非发布版的
MyBatis 的整合支 持,那么 Spring 官方的支持就不得不继续等待了。要在 Spring 中支持 MyBatis,MyBatis 社 区认为现在应该是
自己团结贡献者和有兴趣的人一起来开始将 Spring 的整合作为 MyBatis 社 区的子项目的时候了。

以上内容摘自MyBatis-Spring官网,MyBatis-Spring详细请参考官网:http://www.mybatis.org/spring/zh/index.html

下面我们来集成MyBatis,首先引入相关的Jar包,修改pom.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<properties>
...
<mybatis.version>3.2.8</mybatis.version>
<mybatis_spring.version>1.2.2</mybatis_spring.version>
</properties>
<dependencies>
...
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis_spring.version}</version>
</dependency>
</dependencies>

在MyBatis-Spring入门
http://www.mybatis.org/spring/zh/getting-started.html 里面说到,要和Spring一起使用MyBatis,你需要在Spring应用上下文中定义至少两样东西:一个SqlSessionFactory和至少一个数据映射器类。

在 MyBatis-Spring 中,SqlSessionFactoryBean 是用于创建 SqlSessionFactory 的。要 配置这个工厂 bean,放置下面的代码在 Spring 的 XML 配置文件中:

1
2
3
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

这里我们希望将Spring配置分成多个XML文件,尽管不是严格要求,但是将Spring配置文件组织到多个文件中是很好的主意,基于这样的理念,我们在/WEB-INF目录下面新创建一个名为spring-mybatis.xml的文件,将MyBatis与Spring集成的相关配置都放在spring-mybatis.xml这个文件中。另外还有以下两处可以改进的地方:

  1. 原来的DispatcherServlet会自动装配的springmvc-servlet.xml文件名称觉得修改为springmvc.xml文件更合适。
  2. 新建一个名为spring.xml的Spring配置文件,用于配置一些其他的配置。

在前面的文章中我们提到,DispatcherServlet会根据一个XML文件来加载其Spring应用上下文,而这个文件的名字是基于servlet-name属性来确定的,在这里就是springmvc-servlet.xml文件,那我们新增的spring-mybatis.xml和spring.xml文件如何加载了?

这就是ContextLoaderListener能够发挥作用的地方了。Context-LoaderListener是一个SErvlet监听器,除了DispatcherServlet创建的应用上下文之外,它能够加载其他的配置文件到一个Spring上下文中。为了使用ContextLoaderListener,需要在web.xml文件中添加如下的listener声明:

1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

我们必须告诉ContextLoaderListener需要加载那些配置文件。如果没有指定,上下文加载器会查找/WEB-INF/applicationContext.xml这个Spring配置文件。为了给ContextLoaderListener指定一个或多个Spring配置文件,需要在servlet上下文中配置contextConfigLocation参数:

1
2
3
4
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring.xml,/WEB-INF/spring-mybatis.xml</param-value>
</context-param>

关于将springmvc-servlet.xml 改名为 spring-mvc.xml的问题,修改DispactherServlet相关配置,增加contextConfigLocation配置,如下所示:

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>springmvc config</description>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

下面我们来看看spring-mybatis.xml文件的配置:
首先配置SqlSessionFactory:

1
2
3
4
5
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>

要注意 SqlSessionFactory需要一个DataSource(数据源),这可以是任意的DataSource,配置它就和配置其它的Spring数据库连接一样。关于DataSource的配置我们下面再介绍,这里还有一个配置mapperLocations,mapperLocations属性使用一个资源位置的list。这个属性可以用来指定MyBatis的XML映射器文件的位置,value="classpath:mapper/*.xml" 表示会从类路径下加载mapper包中的所有的MyBatis映射器XML文件。我们在src/main/resources 目录下新建一个名为mapper的目录。

下面我们来增加DataSource,这里我们使用MySQL数据库作为示例,执行下面的sql语句创建一个DataBase和一个表t_user,并向这个表初始插入两条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE DATABASE IF NOT EXISTS mybatis;

USE mybatis;


create table t_user
(
user_id int(11) NOT NULL AUTO_INCREMENT,
user_name varchar(20) not null,
user_age varchar(20) not null,
PRIMARY KEY (user_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;



INSERT INTO t_user(user_id,user_name,user_age) VALUES(1,"Jason","28");
INSERT INTO t_user(user_id,user_name,user_age) VALUES(2,"Toni","24");

Spring中配置数据源可以使用Spring提供的数据源对象DriverManagerDataSource,DriverManagerDataSource在每个请求时都会返回一个新建的连接,从性能上面考虑我们一般会使用数据源连接池,常用的数据源连接池有:DBCP,c3p0,Druid,这里我们选择使用阿里巴巴的Druid。

首先引入相应的jar包:

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
<properties>
...
<mysql_connector_java.version>5.1.38</mysql_connector_java.version>
<druid.version>1.0.13</druid.version>
</properties>
<dependencies>
...
<!-- spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql_connector_java.version}</version>
</dependency>

<!-- alibaba druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>

接下来在spring-mybatis.xml文件里面增加DataSource的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize"><value>1</value></property>
<property name="maxActive"><value>20</value></property>
<property name="minIdle"><value>1</value></property>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait"><value>60000</value></property>
<!-- 配置监控统计拦截的filters -->
<property name="filters"><value>stat</value></property>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis"><value>60000</value></property>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis"><value>300000</value></property>
</bean>

对于大多数情况,可以将整个应用程序配置在一个Bean装配文件中。但有时候,你会发现将一个部分配置抽取到单独的属性文件中会大有益处,在上面的 dataSource这个Bean声明中,连接数据库所需要的所有配置都在这里。这意味着有两件事情:

  • 如果你需要修改数据库的URL或者用户名和密码的话,你需要编辑这个Spring配置文件,然后重新编译和部署应用程序;
  • 用户名和密码是敏感信息,你不希望它落到不合适的人手上。
    像这种场景,最好不要直接在Spring应用上下文中配置这些信息。Spring自带了好几个选项,可以借助它们将Spring配置细节外部化到属性文件中,这样就能在部署的应用之外进行管理,这里我们使用属性占位符配置
  • 属性占位符配置(Property placeholder configurer) 会将占位符内的变量替换为外部属性文件的值
    在2.5版本之前的Spring中,属性占位符配置需要在Spring应用上下文中将PropertyPlaceholderConfigurer声明为bean,尽管并不特别复杂,但是Spring 2.5 借助 context 配置上下文中的 context:property-placeholder元素,将它变得更简单了,我们在spring.xml进行配置,如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?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">

    <context:property-placeholder location="classpath:db.properties" />

    </beans>

占位符配置将从名为db.properties的文件中获取属性值,这个文件位于类路径的根目录下。在src/main/resources目录下新建一个名为db.properties的文件,内容如下:

1
2
3
jdbc.url=jdbc\:mysql\://192.168.8.103\:3306/mybatis?useUnicode\=true&characterEncoding\=UTF-8
jdbc.username=root
jdbc.password=123456

现在,可以将spring-batis.xml配置中的硬编码值替换为基于db.properties属性的占位符变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize"><value>1</value></property>
<property name="maxActive"><value>20</value></property>
<property name="minIdle"><value>1</value></property>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait"><value>60000</value></property>
<!-- 配置监控统计拦截的filters -->
<property name="filters"><value>stat</value></property>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis"><value>60000</value></property>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis"><value>300000</value></property>
</bean>

DataSource创建好后,接下来看看如何利用MyBatis从t_user表中查询数据了,按照MVC思想来创建相应的Model,Service,Dao。

  • Model: 新建一个com.springmvcdemo.model的package,在里面新建一个名为User.java的文件,如下所示:

    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
    package com.springmvcdemo.model;

    public class User {

    private int id;
    private String age;
    private String userName;
    public User(){
    super();
    }
    public int getId() {
    return id;
    }
    public void setId(int id) {
    this.id = id;
    }
    public String getAge() {
    return age;
    }
    public void setAge(String age) {
    this.age = age;
    }
    public String getUserName() {
    return userName;
    }
    public void setUserName(String userName) {
    this.userName = userName;
    }
    public User(int id, String age, String userName) {
    super();
    this.id = id;
    this.age = age;
    this.userName = userName;
    }
    }
  • Dao:新建一个com.springmvcdemo.mapper的package(MyBatis里面使用了Mapper这个概率,这里的包名用mapper),在里面新建一个名为UserMapper的接口。(要注意,所指定的映射器类必须是一个接口,而不是具体的实现类。)

    1
    2
    3
    4
    @Repository
    public interface UserMapper {
    User findById(@Param("userId") int userId);
    }

然后我们用映射器 XML 文件来指定SQL语句,在src/main/resources/mapper目录下面创建一个名为UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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.springmvcdemo.mapper.UserMapper">

<resultMap id="userMap" type="com.springmvcdemo.model.User">
<result property="id" column="id" jdbcType="INTEGER" />
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="age" column="user_age" jdbcType="VARCHAR" />
</resultMap>

<select id="findById" resultMap="userMap">
SELECT user_id id,user_name userName,user_age age FROM t_user WHERE user_id = #{userId}
</select>

</mapper>

然后我们需要在Spring的XML配置文件中注册映射器,可以使用MapperScannerConfigurer,它将会查找类路径下的映射器并自动将它们创建为MapperFactoryBean,在spring-mybatis.xml文件中增加如下代码:

1
2
3
4
5
<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.springmvcdemo.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

  • Service: 新建一个com.springmvcdemo.service的package,在里面新建一个名为UserService的类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.springmvcdemo.service;

    import com.springmvcdemo.mapper.UserMapper;
    import com.springmvcdemo.model.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    @Service
    public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User findById(int userId) {
    return userMapper.findById(userId);
    }
    }

这里使用了@Autowired注解来自动装配userMapper Bean, 我们在spring-mvc.xml配置文件里面使用了

1
2
<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

context:component-scan 元素来让Spring自动检测和定义Bean,那么context:component-scan 又是如何知道哪些类需要注册为Spring Bean呢?默认情况下context:component-scan 查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下所示:

@Component -- 通用的构造型注解,标识该类为Spring组件。

@Controller -- 标识将该类定义为Spring MVC controller。

@Repository -- 标识将该类定义为数据仓库(一般用于Dao类上)

@Service -- 标识将该类定义为服务

使用@Component标注的任意自定义注解

Model, Dao, Service 完成后,我们在Controller里面调用Service来从数据库中获取数据,修改后的HomeController代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class HomeController {

@Autowired
private UserService userService;

@RequestMapping({"/","/home"})
public String homePage(@RequestParam(required = false, defaultValue = "1") int userId, Model model) {
User user = userService.findById(userId);
model.addAttribute("user",user);
model.addAttribute("message","Hello!");
return "home";
}
}

@RequestParam(required = false, defaultValue = “1”) int userId 定义了传入的参数userId,并且设置默认值为1,User user = userService.findById(userId);调用service获取数据。

修改下home.ftl,利用${user.userName}输出userName值:

1
2
3
<body>
this is the home page(ftl). ${message} ${user.userName}
</body>

启动Tomcat服务,启动成功后自动弹出首页,如下图所示:

image

我们可以看出userName已经获取出来了,这里没有传递userId参数,userId将使用默认设定的值1。

为了在控制台查看执行的SQL,我们这里使用Log4j来输出MyBatis执行的SQL语句。

关于MyBatis使用日志请参考官方文档:http://www.mybatis.org/mybatis-3/zh/logging.html。

首先引入所需的jar:

1
2
3
4
5
6
7
8
9
10
11
12
<properties>
...
<slf4j.version>1.7.12</slf4j.version>
</properties>
<dependencies>
...
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>

添加SLF4J依赖,用于提供日志API,使用Log4J作为实现。下面就是创建log4j.properties文件了,在src/main/resources目录下面创建一个名为log4j.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
log4j.rootLogger=INFO,Console,File
#定义日志输出目的地为控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以灵活地指定日志输出格式,下面一行是指定具体的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n

# Mybatis Logging
log4j.logger.com.springmvcdemo.mapper=TRACE

#文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.File = org.apache.log4j.RollingFileAppender
#指定输出目录
log4j.appender.File.File = logs/ssm.log
#定义文件最大大小
log4j.appender.File.MaxFileSize = 10MB
# 输出所有日志,如果换成DEBUG表示输出DEBUG以上级别日志
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n

请注意 log4j.logger.com.springmvcdemo.mapper=TRACE 添加这段配置后,告诉Log4J把 com.springmvcdemo.mapper 所在包下面的mapper接口的详细执行日志记录下来。

Log4J 添加完成后,重启Tomcat服务器,重新访问首页:

从控制台中我们可以看出已经输出执行的SQL语句了。

传入userId参数,如下所示:

image

可以发现页面上输出的userName从Jason变成了Toni,控制台也输出了执行的SQL语句:

image

至此,SpringMVC集成MyBatis基本完成,接下来会继续完善框架,增加事务处理功能。

(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)