Spring + SpringMVC + MyBatis 集成

SSM框架

现在 Java Web 开发中比较热门的框架组合就是 SSM(Spring + SpringMVC + MyBatis)了。使用框架的好处不言而喻,节约了我们的开发时间,框架强制使用公共的约定,团队配合更密切。

使用框架便利的同时,需要学习其中的思想。对提高我们的编程思想很有帮助。可以先实践,再思想,实践出真知!

Spring 框架

Spring 是一个开源的轻量级的 Java 企业应用开发框架,其初衷是为了替代当时非常笨重的 Java EE (当时还称为J2EE)组件技术 EJB(Enterprice Java Beans),让 Java EE 开发更加简单灵活。

Spring 最重要的两个核心功能是依赖注入(DI,Dependency Injection)和面向切面编程(AOP,Aspect Oriented Programming)。

Spring 是一个极其优秀的一站式的 Full-Stack 集成框架,与 Spring 整合非常的方便。

更多的介绍请点击 官网 spring.io/

也可以点击我写的关于 Spring 标签

SpringMVC

Spring MVC 属于 SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。

MyBatis

MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生 Map 使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

MyBatis 有个非常出色的地方就是可以自由书写 SQL 语句,发挥 SQL 技巧,所以很受欢迎

MyBatis 官网是有中文的,而且还不错。官网 www.mybatis.org

也可以点击我写的关于 MyBatis 标签

项目结构图

Spring 4.1.7 + MyBatis 3.3.0

SSM 项目结构图

Maven 方式构建项目

pom.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
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<groupId>seckill_Gid</groupId>
<artifactId>seckill_Aid</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<finalName>seckill_Aid</finalName>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.tomcat.uriEncoding>UTF-8</maven.tomcat.uriEncoding>
</properties>

<build>
<finalName>seckill_Aid</finalName>
</build>

<dependencies>
<dependency>
<!--junit 4 以后支持注解方式-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!--java日志 slf4j、log4j、logback、common-logging
slf4j 是规范/接口,日志实现:log4j、logback、common-logging
本次采用:slf4j + loback
-->

<!--1. 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.1</version>
</dependency>
<!--实现 slf4j 接口并整合-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>

<!--2. 数据库相关依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency>

<!--数据库连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<!--DAO 框架 :MyBatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!--MyBatis 自身实现的 Spring 整合依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>

<!-- 3. Servlet Web 相关依赖-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>


<!--4. Spirng 依赖-->
<!--4.1 Spring 核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--4.2 Spring Dao层依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--4.3 Spring web 相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--4.4 Spring test 相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
</dependencies>
</project>

DAO 层整合过程

Spring Dao 配置

1
/resources/spring/spring-dao.xml

在上述文件中配置 Spring 与 MyBatis 整合。
主要有四步:

  1. 配置数据库相关参数
  2. 配置连接池属性(采用 c3p0 数据连接池)
  3. 配置SqlSessionFactory对象
  4. 配置扫描Dao接口包,动态实现DAO接口,注入到spring容器

代码如下:

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


<!--配置整合mybatis过程-->
<!--1.配置数据库相关参数-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--2.配置连接池属性-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />

<!-- c3p0 连接池的私有属性-->
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<!--关闭连接后不自动 commit-->
<property name="autoCommitOnClose" value="false" />
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="1000" />
<!--重置次数-->
<property name="acquireRetryAttempts" value="2" />
</bean>

<!--约定大于配置-->
<!--3.配置SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
<!--配置mybatis全局配置文件:mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--扫描entity包,并使用别名,多个用;隔开-->
<property name="typeAliasesPackage" value="org.seckill.entity"/>
<!--扫描sql配置文件:mapper需要的xml文件-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

<!--4:配置扫描Dao接口包,动态实现DAO接口,注入到spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描的Dao接口-->
<property name="basePackage" value="org.seckill.dao"/>
</bean>
</beans>

jdbc.properties 数据库的配置

1
/resources/jdbc.properties

在上述文件中配置数据库的信息,账号密码等。

1
2
3
4
5
6
7
# 如果在 windows 系统系报错, username 需要改个名字,之后就不会报错了。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=UTF8
jdbc.ddd=com.mysql.jdbc.Driver
jdbc.aa=root

MyBatis 全局配置

1
/resources/mybatis-config.xml

在上述文件中设定 MyBatis 全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>
<!--配置全局属性-->
<settings>
<!--使用 jdbc 的 getGeneratekeys 获取自增主键值-->
<setting name="useGeneratedKeys" value="true"/>

<!--使用列别名替换别名  默认true
select name as title form table;
-->

<setting name="useColumnLabel" value="true"/>

<!--开启驼峰命名转换Table:create_time到 Entity(createTime)-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

DAO 层的接口

接下来在 org.seckill.dao 创建接口就好,当然了数据库的表和实体类都需要创建。

接口代码如下

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
package org.seckill.dao;

import org.apache.ibatis.annotations.Param;
import org.seckill.entity.Seckill;

import java.util.Date;
import java.util.List;

/**
* Created by ARNO.
*/

public interface SeckillDao {

/**
* 减库存
*
* @param seckillId
* @param killTime
* @return 如果更新行数大于1,表示更新的行数
*/

int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

/**
* 根据ID查询秒杀对象
*
* @param seckillId
* @return
*/

Seckill queryById(long seckillId);

/**
* 根据偏移量查询秒杀商品列表
*
* @param offset
* @param limit
* @return
*/

List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
}

对应的 SeckillDao.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
<?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命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址-->

<mapper namespace="org.seckill.dao.SeckillDao">

<!--mapper作用:为DAO接口方法提供sql语句配置-->
<!-- int reduceNumber(long seckillId, Date killTime);-->
<update id="reduceNumber">
UPDATE seckill set number = number -1
where seckill_id = #{seckillId}
AND start_time <![CDATA[ <= ]]> #{killTime}
AND end_time >= #{killTime}
AND number >0;
</update>

<!-- Seckill queryById(long seckillId);-->
<select id="queryById" resultType="Seckill" parameterType="long">
SELECT seckill_id,name,number,start_time,end_time,create_time
FROM seckill
WHERE seckill_id = #{seckillId}
</select>

<!-- List<Seckill> queryAll(int offset,int limit);-->
<select id="queryAll" resultType="Seckill">
SELECT seckill_id,name,number,start_time,end_time,create_time
FROM seckill
ORDER BY create_time DESC
limit #{offset},#{limit}
</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
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
package org.seckill.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.entity.Seckill;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;

/**
* Created by ARNO on 2016/6/3/003.
* 配置 Spring 和 junit 整合, junit 启动时加载 Spring IoC 容器
* spring-test ,junit
*/


@RunWith(SpringJUnit4ClassRunner.class)
// 告诉 junit,Spring 配置文件路径
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {

// 注入Dao实现类
@Resource
private SeckillDao seckillDao;

@Test
public void reduceNumber() throws Exception {
long id = 1000;
Seckill seckill = seckillDao.queryById(id);
System.out.println(seckill.getName());
System.out.println(seckill);
/** 测试结果
* 1000元秒杀iphone6
Seckill{
seckillId=1000,
name='1000元秒杀iphone6', number=100,
startTime=Fri Jan 01 00:00:00 CST 2016,
endTime=Sat Jan 02 00:00:00 CST 2016,
createTime=Fri Jun 03 11:04:48 CST 2016}
*/

}

@Test
public void queryById() throws Exception {

List<Seckill> seckillList = seckillDao.queryAll(0, 100);
for (Seckill seckill : seckillList) {
System.out.println(seckill);
}
// Parameter 'offset' not found . Available parameter are [0, 1, parm1, parm2 ]
// Java 没有报错形参的记录
}

@Test
public void queryAll() throws Exception {
Date killTime = new Date();
int updateCount = seckillDao.reduceNumber(1000L, killTime);
System.out.println("updateCount= " + updateCount);
}
}

如果绿色,则测试通过。红色的则具体原因具体分析。
比如遇到:

1
2
# Error querying database.
# Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection;

将 jdbc.username 修改个名字就好了,这是个坑。

参考资料