使用springmvc+mybatis创建Web应用(二)—— 数据库、配置和测试

引言

上一篇文章简单地介绍了如何从零开始构建一个Spring MVC应用,如果我们仅仅需要做一些静态页面或者数据不变化的Web应用,那么其实这样就足够了。这当然是不现实的,我们的页面中的数据需要不断地变化,不同的用户登录进来之后应用只能看到属于自己的数据,诸如此类的需求告诉我们原本的代码框架是不够的。

对于一个功能相对齐整的Web应用,除了上一篇文章中介绍的代码框架,还需要:

  1. 连接和操作数据库
  2. 配置文件
  3. 单元测试(虽然可以省略,但还是建议保留)

接下来的内容就介绍上面所列举的内容,以期能创建一个比较完整的Web应用框架。

使用Mybatis操作数据库

上一篇文章中已经简单地介绍过了Mybatis中的基本概念,下面直接进入实战环节。由于要依赖第三方的工具包,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
<!-- database -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- test end -->

JDBC连接池

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

在项目中使用连接池是非常有必要,主要有以下好处:

  • 减少连接创建时间
  • 简化的编程模式
  • 受控的资源使用

Java中有以下常用的开源连接池:

  • C3P0
  • DBCP
  • Proxool

在本文中将使用C3P0来作为数据库连接池组件,并且用Spring来管理C3P0。为了使用Spring来管理应用中的所有组件,我们先在Web.xml文件中配置下面的代码,使得Spring的上下文(context)文件能够随着应用一块启动。

1
2
3
4
5
6
7
8
9
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

插个题外话,注意Spring的上下文配置的路径为:classpath:applicationContext.xml。在笔者刚开始学习Java的很长一段时间里,都不太明白什么是classpath,以致于常常取不到资源文件。

classpath表示一个绝对路径,但不是一个固定值。它是编译完毕后存放xxx.class文件的最顶层目录所对应的路径。譬如使用Maven编译之后通常(如果没有手动修改Maven配置)会将编译后的class文件放置在target\classes目录下,于是classpath表示的绝对路径为:工程根路径\target\classes\。所以,本文中的Spring的上下文配置文件的绝对路径为:工程根路径\target\classes\applicationContext.xml

src/main/resources资源文件夹下创建一个applicationContext.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置数据连接池(C3P0) -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8" />
<property name="user" value="test" /> <!-- 数据库用户名 -->
<property name="password" value="test" /> <!-- 数据库密码 -->
<property name="maxPoolSize" value="10" />
<property name="minPoolSize" value="1" />
<property name="initialPoolSize" value="2" />
<property name="maxIdleTime" value="20" />
<property name="acquireIncrement" value="3" />
<property name="idleConnectionTestPeriod" value="60" />
<property name="unreturnedConnectionTimeout" value="190" />
<property name="checkoutTimeout" value="20000" />
<property name="acquireRetryAttempts" value="30" />
</bean>
<!-- 配置数据库事务 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

数据库表

Mybatis的主要功能是将Mapper配置映射成对应的sql语句并执行。在介绍Mybatis的配置之前,先创建一个简单的数据表作为数据示例。

数据表User的结构如下:

字段 类型 索引 字段含义
username varchar(50) 主键 用户名
password varchar(50) 密码
role int 用户角色

笔者使用的是mysql数据库,sql语句如下:

1
2
3
4
5
6
7
8
9
10
11
CREATE DATABASE test;
USE test;
CREATE TABLE `test`.`User`(
`username` VARCHAR(50) COMMENT '用户名',
`password` VARCHAR(50) COMMENT '密码',
`role` INT COMMENT '角色'
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO User VALUES("test", "test", 1); # 插入一条记录

Mybatis配置

src/main/resources资源文件夹中创建一个spring-mybatis.xml文件,这个取名可以任意,只不过是方便记忆而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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-3.0.xsd">
<!-- mybatis SqlSessionFactory配置 -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:/mapper/*.xml" />
</bean>
<!-- Mapper 扫描配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory" />
<property name="basePackage" value="demo.dao" />
</bean>
</beans>

在【mybatis SqlSessionFactory配置】配置项中,指定了mybatis的Mapper文件所在的位置(classpath:/mapper/*.xml)。于是,在src/main/resources资源文件夹创建一个mapper子目录,将所有的Mapper文件放置在这个文件夹下。

applicationContext.xml文件中引入spring-mybatis.xml,使其加入spring的管理容器中。

1
<import resource="classpath:spring-mybatis.xml"/>

Dao接口和Mapper配置

首先创建一个demo.model包,在这个包下创建一个名为User的类。这个类的字段与上面设计的数据表里面的字段是一一对应的。

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 demo.model;
/**
* 数据模型,与数据库表里面的字段对应。
* @author xialei
*
*/
public class User {
private String username;
private String password;
private Integer role;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getRole() {
return role;
}
public void setRole(Integer role) {
this.role = role;
}
}

接着创建一个demo.dao包,在这个包下创建一个名为UserMapper的类。这个类的作用是定义一系列数据操作方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package demo.dao;
import org.apache.ibatis.annotations.Param;
import demo.model.User;
public interface UserMapper {
/**
* 访问数据库,检查用户名和密码输入是否正确。
* @param username
* @param password
* @return
*/
public User check(@Param("username") String username, @Param("password") String password);
}

最后创建UserMapper所对应的Mapper文件(UserMapper.xml),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
<?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" >
<!-- demo.dao.UserMapper与UserMapper定义路径一致 -->
<mapper namespace="demo.dao.UserMapper" >
<!-- demo.model.User与User的定义路径一致,下面是Java对象与数据库字段的映射关系。 -->
<resultMap id="BaseResultMap" type="demo.model.User" >
<id column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="role" property="role" jdbcType="TINYINT" />
</resultMap>
<sql id="Base_Column_List" >
username, password, role
</sql>
<!-- id必须与UserMapper中定义的方法名一致。 -->
<select id="check" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select>
</mapper>

示例

为了测试整个框架是否已经打好,我们写一个测试页面来测试一下,就用最常用的登录功能来验证。

首先,创建一个User控制器(UserController.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
package demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import demo.dao.UserMapper;
import demo.model.User;
@Controller
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/login")
public String login() {
return "login";
}
@RequestMapping("/dologin")
public ModelAndView doLogin(String username, String password) {
User user = this.userMapper.check(username, password);
if (user == null) {
return new ModelAndView("error");
} else {
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("username", username);
return modelAndView;
}
}
}

然后,创建对应的jsp页面来显示结果。

登录页面:login.jsp,文件路径为:src/main/webapp/WEB-INF/jsp/login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>登录</title>
</head>
<body>
<form action="${pageContext.servletContext.contextPath}/dologin" method="post">
用户名:<input name="username" type="text" >
密码:<input name="password" type="password">
<button type="submit">登录</button>
</form>
</body>
</html>

登录成功页面:success.jsp,文件路径为:src/main/webapp/WEB-INF/jsp/success.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>登录成功</title>
</head>
<body>
登录成功!您的用户名为:<%=request.getParameter("username")%>
</body>
</html>

登录失败页面:error.jsp,文件路径为:src/main/webapp/WEB-INF/jsp/error.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>登录失败</title>
</head>
<body>
登录失败,用户名或密码错误。
</body>
</html>

使用Jetty启动应用,在浏览器中输入http://localhost:8080/login进入登录界面。如果输入正确的用户名和密码(test/test),则跳入success.jsp对应的页面,输入其他信息,则跳入error.jsp对应的页面。

使用配置文件

通常系统中存在常量(数据库连接URL、用户名、密码等),这些常量如果直接嵌入到Java代码中,则一旦代码编译完毕后就无法更改,如需更改则只能在源代码中改完后重新编译。当然,如果像上面一样,写在诸如applicationContext.xml的xml文件中也是可以的。但是如果这样的文件很多,找起来就会比较困难。

比较推荐的方法是将这些常量集中写到一个常量配置文件中。Java中默认的配置文件后缀是.properties,下面就将常量移入配置文件中。

首先,在applicationContext.xml中加入下面的代码,表示用配置文件中的值来替换掉applicationContext.xml中的变量引用表达式。

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

src/main/resources文件夹中创建config.properties配置文件,加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ------------------ Data source ----------------
dataSource.pool.driverClass=com.mysql.jdbc.Driver
dataSource.pool.jdbcUrl=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
dataSource.pool.user=test
dataSource.pool.password=test
dataSource.pool.maxPoolSize=10
dataSource.pool.minPoolSize=1
dataSource.pool.initialPoolSize=2
dataSource.pool.maxIdleTime=20
dataSource.pool.acquireIncrement=3
dataSource.pool.idleConnectionTestPeriod=60
dataSource.pool.unreturnedConnectionTimeout=190
dataSource.pool.checkoutTimeout=20000
dataSource.pool.acquireRetryAttempts=30

同时,修改applicationContext.xml文件代码,使用EL表达式来引用这些配置项的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 配置数据连接池(C3P0) -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${dataSource.pool.driverClass}" />
<property name="jdbcUrl" value="${dataSource.pool.jdbcUrl}" />
<property name="user" value="${dataSource.pool.user}" />
<property name="password" value="${dataSource.pool.password}" />
<property name="maxPoolSize" value="${dataSource.pool.maxPoolSize}" />
<property name="minPoolSize" value="${dataSource.pool.minPoolSize}" />
<property name="initialPoolSize" value="${dataSource.pool.initialPoolSize}" />
<property name="maxIdleTime" value="${dataSource.pool.maxIdleTime}" />
<property name="acquireIncrement" value="${dataSource.pool.acquireIncrement}" />
<property name="idleConnectionTestPeriod" value="${dataSource.pool.idleConnectionTestPeriod}" />
<property name="unreturnedConnectionTimeout" value="${dataSource.pool.unreturnedConnectionTimeout}" />
<property name="checkoutTimeout" value="${dataSource.pool.checkoutTimeout}" />
<property name="acquireRetryAttempts" value="${dataSource.pool.acquireRetryAttempts}" />
</bean>

这样在spring容器启动时,会用配置文件中的值替换掉对应的表达式。

单元测试

在Java体系中,JUnit无疑是最强大的单元测试工具,这里就不介绍JUnit的使用了。Spring管理的应用中,依赖关系比较复杂,而且有时会有隐蔽性,单纯的JUnit恐怕很难进行测试。下文介绍专门用于Spring应用单元测试的spring-test组件,以及用于Web应用测试的mock组件的简单使用。

简单的测试

Maven中,通常将所有的测试用例写在src/test/java源文件夹下。下面的代码测试UserMapper这个类,这仅仅是一个样例,介绍如何使用spring-test进行测试而已,测试用例并具有可参考性。

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
package demo.test.dao;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import demo.dao.UserMapper;
import demo.model.User;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testDoLoginWrong() {
User user = this.userMapper.check("eroor", "error");
Assert.assertNull(user);
}
@Test
public void testDoLoginRight() {
User user = this.userMapper.check("test", "test");
Assert.assertNotNull(user);
}
}

web测试

进行web测试时,需要模拟HTTP请求。使用spring-test无需将Web应用部署到服务器就可以进行测试,非常方便实用。下面是一个示例,更多的关于spring-test的知识可以看这篇博文

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
package demo.test.controller;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
*
* @author xialei
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration("src/main/webapp")
@ContextConfiguration(locations = {"classpath:applicationContext.xml", "file:src/main/webapp/WEB-INF/dispatch-servlet.xml"})
@EnableWebMvc
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
protected MockMvc mockMvc;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void testDoLogin() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/dologin").param("username", "test").param("password", "test"))
.andDo(MockMvcResultHandlers.print()).andReturn();
String viewName = mvcResult.getModelAndView().getViewName();
Assert.assertEquals("success", viewName);
String value = (String)mvcResult.getModelAndView().getModelMap().get("username");
Assert.assertEquals("test", value);
}
@Test
public void testDoLoginIllegalInput() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/dologin").param("username", "error").param("password", "error"))
.andDo(MockMvcResultHandlers.print()).andReturn();
String viewName = mvcResult.getModelAndView().getViewName();
Assert.assertEquals("error", viewName);
}
}

总结

关于使用SpringMVC+Mybatis来创建一个Web应用系列已经写完了。文章从无到有地创建了一个Web应用,知识点讲得不太细,旨在梳理创建整个应用过程中涉及到的概念、组件。阅读本系列文章可以了解到创建一个基于SpringMVC+Mybatis的Web应用是如何一步一步地搭建起来的,虽然示例比较简单,基本停留在Demo级别,但还是比较完整的。在今后创建类似Web应用时,可以直接拿来做代码骨架。


使用springmvc+mybatis创建Web应用(一)—— 相关概念,工具,搭建Web应用

Github地址:https://github.com/xialei199023/springmvc-mybati-webapp


本文由xialei原创,转载请说明出处http://hinylover.space/2016/04/11/springmvc-mybatis-createproject-demo-2/