Spring MVC学习笔记
整合Spring,Spring MVC ,MyBatis
这三个框架的组合能够以标准的MVC的程序架构开发web项目,整个系统分成View层,Controller层,Service层,Dao层,使用SpringMVC进行请求的拦截和转发以及视图的管理,使用MyBatis处理数据的持久化。
登录注册的demo使用前后端分离的思想,前端使用ajax进行数据的通信,后端处理数据之后返回JSON数据。
Spring MVC基本原理及流程
当用户请求到达时,Spring MVC可以配置一个前端控制器DispatchServlet,通过查找配置文件寻找到一个或多个HandlerMapping(映射处理器)将请求转发给用户自定义的控制器,完成这一处理之后返回给DispatchServlet,它寻找到一个或多个ViewResolver(视图解析器),找到ModelAndVIew,通过配置ResourceViewResolver处理模板和数据,产生响应。
其中有以下几个注意点:
- controller 是 handler,但是handler不是controller,handler掌管所有的前端控制器转发来的信息,还包括了静态资源的请求
- 其中用户需要写的自定义Controller只需要完成接受参数,调用实际的业务逻辑,返回结果
SSM架构业务层次说明
持久层:DAO层
DAO层主要做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。 DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的详细操作,完成实体到数据库中字段的映射,例如MyBatis的XML配置文件,并且可以在Spring的配置文件中配置数据库的信息,数据库连接池,例如Druid等,,在Service层中使用时,可以直接通过接口调用方法,而不需要关心具体的实现类。
业务层 Service层
是后端代码中业务逻辑的实现部分,通过DAO层完成对数据的操作,并且通过在配置文件中添加对Service注解的扫描能够实现自动绑定,在 Controller中可以直接调用service中的方法。
控制层 Controller层
业务流程的控制层,通过调用Service层封装的抽象的业务逻辑针对实际的需求完成操作,这一部分最好可以抽象出一些业务流程放入Service层中,减少重复的代码
框架的整合
-
在DAO层将MyBatis和spring相结合,通过Spring管理Mapper接口,通过配置Mapper扫描器,指定所在Mapper包就可以在Spring容器中注册使用
-
Service层通过Spring进行管理,添加Service注解,并且在配置文件中添加扫描器的扫描的包的位置,添加事务管理
-
SpringMVC本身就由spring管理,只需要添加前端控制以及视图控制器的配置即可
项目工程
使用Maven进行包管理
具体的依赖以及版本如下
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<org.springframework.version>4.3.2.RELEASE</org.springframework.version>
<com.alibaba.druid.version>1.0.25</com.alibaba.druid.version>
<com.mybatis.mybatis.version>3.4.1</com.mybatis.mybatis.version>
<com.mybatis.mybatis_spring.version>1.3.0</com.mybatis.mybatis_spring.version>
<mysql.version>5.1.37</mysql.version>
<javax.servlet.version>3.1.0</javax.servlet.version>
<com.jackson.version>2.8.5</com.jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 1.springmvc框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- 2.mybatis以及spring框架整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${com.mybatis.mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${com.mybatis.mybatis_spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- 3.数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- 4.阿里巴巴数据库连接池druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${com.alibaba.druid.version}</version>
</dependency>
<!-- 5.servlet相关包-->
<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>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
</dependency>
<!--responseBody中的json支持-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${com.jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${com.jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${com.jackson.version}</version>
</dependency>
</dependencies>
对于Maven创建的工程,使用IDEA可以将此Module支持SpringMVC:右键项目设置,添加框架支持,选择springMVC即可。
整合DAO层
DAO层需要对mysql数据库的连接信息,druid连接池,以及Mapper对应的实体信息的映射关系
配置mybatis
配置的信息包括自增主键,驼峰命名转换,以及变量类型的别名
<configuration>
<!-- 配置全局属性 -->
<settings>
<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 使用列别名替换列名 默认:true -->
<setting name="useColumnLabel" value="true" />
<!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer"/>
<typeAlias alias="Long" type="java.lang.Long"/>
<typeAlias alias="HashMap" type="java.util.HashMap"/>
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
<typeAlias alias="ArrayList" type="java.util.ArrayList"/>
<typeAlias alias="LinkedList" type="java.util.LinkedList"/>
</typeAliases>
</configuration>
spring关于DAO的配置文件
配置的内容包括:
- dataSource数据源,使用的是阿里巴巴的druid连接池
- sqlSessionFactory,
SqlSessionFactoryBean
实现了 Spring 的FactoryBean
接口,因此Spring 最终创建的Bean是getObject() 方法的返回结果而不是工厂类本身 - Mapper扫描器,需要给出扫描的Dao接口包,接口包的xml文件名和使用的接口名对应
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- 配置连接池属性 -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="10" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20" />
<!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->
<property name="defaultAutoCommit" value="true" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 扫描entity包 使用别名 -->
<property name="typeAliasesPackage" value="com.tommenx.entity"/>
<!-- 扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.tommenx.dao"/>
</bean>
Mapper映射文件
UserDao.xml文件的路径为resources/mapper/下,主要配置了根据条件查找,插入等功能
<?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.tommenx.dao.UserDao">
<sql id="FIELDS">
id, username, password
</sql>
<!--查找-->
<select id="selectById" resultType="User" parameterType="Integer">
SELECT
<include refid="FIELDS"/>
FROM
`user`
WHERE
id = #{id}
</select>
<select id="selectByPhone" resultType="User" parameterType="String">
SELECT
<include refid="FIELDS"/>
FROM
`user`
WHERE
phone = #{phone}
LIMIT 1
</select>
<!--增加语句-->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(
phone,username,password
)VALUES(
#{phone},#{username},#{password}
)
</insert>
</mapper>
UserDao.xml
对应的接口命名为UserDao,具体的内容如下:
package com.tommenx.dao;
import com.tommenx.entity.User;
import org.apache.ibatis.annotations.Param;
public interface UserDao {
int insert(User user) throws Exception;
User selectById(@Param("id") Integer id);
User selectByPhone(@Param("phone") String phone);
}
配置Service层
- 配置扫描的service包路径,并且只扫描带有
service
注解的文件 - 配置事务管理器transactionManager
<context:component-scan base-package="com.tommenx.service">
<!-- 只扫描标记了Service的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置基于注解的声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
对于注册和登录两个模块,service层的具体实现的功能有:1. 新增一个用户;2. 根据手机号查找用户信息
@Service("userService")
public class UserService {
@Autowired
private UserDao userDao;
public void addUser(User user) throws UserAireadyExistException {
int result = 0; //受影响的行数默认为0
try {
result = userDao.insert(user);
} catch (Exception e) {
System.out.println("添加用户失败,用户已经存在");
//其他用户添加失败异常
throw new UserAireadyExistException(e);
}
if (result > 0)
System.out.println("添加用户成功");
}
public User findUser(User user) {
return userDao.selectByPhone(user.getPhone());
}
}
由于在数据库表的设计上,对phone这一属性创建了唯一索引,因此如果插入已经存在的手机号的元组,会造成异常,将这个异常抛出
配置spring-mvc的相关信息
- 静态资源的映射关系,例如js,css等
- 配置视图管理器,解析的jsp路径的前缀地址和后缀名称
- 控制器扫描的包路径以及过滤仅仅扫描带
controller
注解的文件
<mvc:annotation-driven/>
<mvc:resources mapping="/css/**" location="/static/css/" />
<mvc:resources mapping="/images/**" location="/static/images/" />
<mvc:resources mapping="/view/**" location="/static/view/" />
<mvc:resources mapping="/javascripts/**" location="/static/javascripts/"/>
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 4.扫描web相关的bean -->
<context:component-scan base-package="com.tommenx.controller">
<!-- 制定扫包规则 ,只扫描使用@Controller注解的JAVA类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基于已有的前端采用的是前后端分离的思想,前端发起ajax请求,后端代码接受到请求后,将请求的结果包装成JSON格式的数据返回,这里仅仅返回状态码,增加上ResponseBody
后返回Map结构的数据可以封装成JSON格式
Controller有多重获取参数的方法,可以直接在形参中使用相同名字的变量,也已增加@Param注解,或者使用相同结构的结构体,或者使用Map进行解析,详情可以参考SpringMVC Controller请求参数总结
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@ResponseBody
@RequestMapping(value = "/register", method = RequestMethod.POST)
public Map register(@RequestParam Map<String, Object> param) {
System.out.println(param);
Map<String, Object> status = new HashMap<>();
int statusCode = 1;
User user = new User();
user.setUsername((String) param.get("username"));
user.setPhone((String)param.get("phone"));
user.setPassword((String)param.get("password"));
try {
userService.addUser(user);
} catch (UserAireadyExistException e) {
statusCode = 0;
}
status.put("code",statusCode);
return status;
}
@ResponseBody
@RequestMapping(value = "/login",method = RequestMethod.POST)
public Map login(@RequestParam Map<String,Object>param) {
Map<String,Object> status = new HashMap<>();
int statusCode = 1;
User user = new User();
user.setPhone((String)param.get("phone"));
user.setPassword((String)param.get("password"));
User store = userService.findUser(user);
if(store == null) {
statusCode = 0;
}else if (!store.getPassword().equals(user.getPassword())) {
statusCode = 2;
}
status.put("code",statusCode);
return status;
}
}
配置web.xml文件信息
主要配置前端控制器DispatcherServlet,包括初始上下文配置的路径地址,以及拦截url模式
<servlet>
<display-name>SSM</display-name>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>