一、SSM整合流程
1、配置完整项目结构及依赖
创建一个Web的Maven项目
补全项目中所需java、resources、test目录
在pom.xml中导入相关依赖
<?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>
<groupId>SpringMVC_08</groupId>
<artifactId>SpringMVC_08</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringMVC_08 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建项目包结构
UserService接口可暂不创建
2、创建相关配置类
在resources下提供jdbc.properties,设置数据库连接四要素
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db?useSSL=false
jdbc.username=root
jdbc.password=1234
在config包下创建如下配置类
SpringConfig配置类
@Configuration
@ComponentScan({"com.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
JdbcConfig配置类
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
MybatisConfig配置类
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean用于产生sqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//设置模型类的别名扫描
ssfb.setTypeAliasesPackage("com.domain");
//设置数据源
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.dao");
return msc;
}
}
SpringMVCConfig配置类
@Configuration
@ComponentScan("com.controller")
@EnableWebMvc
public class SpringMVCConfig {
}
删除web.xml,配置web项目入口配置类ServletConfig
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
3、功能模块开发
数据库名: mybatis_db 表名:user
编写模型类User
public class User {
private Integer id;
private String name;
private String password;
private Integer grade;
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;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getGrade() {
return grade;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", grade=" + grade +
'}';
}
}
编写Dao接口
public interface UserDao {
@Insert("insert into user(name,password,grade) values (#{name},#{password},#{grade})")
int save(User user);
@Delete("delete from user where id = #{id}")
int delete(Integer id);
@Update("update user set name = #{name},password = #{password} where id = #{id}")
int update(User user);
@Select("select * from user")
List<User> getAll();
@Select("select * from user where id = #{id}")
User getById(Integer id);
}
增删改返回值为int,其目的为接收数据库执行行数,为1则执行成功,为0则执行失败
编写Service接口及其实现类
UserService
@Transactional
public interface UserService {
/**
* 保存
*
* @param user
* @return
*/
public boolean save(User user);
/**
* 修改
*
* @param user
* @return
*/
public boolean update(User user);
/**
* 按id删除
*
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 按id查询
*
* @param id
* @return
*/
public User getById(Integer id);
/**
* 全部查询
*
* @return
*/
public List<User> getAll();
}
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public boolean save(User user) {
return userDao.save(user) > 0;
}
@Override
public boolean update(User user) {
return userDao.update(user) > 0;
}
@Override
public boolean delete(Integer id) {
return userDao.delete(id) > 0;
}
@Override
public User getById(Integer id) {
return userDao.getById(id);
}
@Override
public List<User> getAll() {
return userDao.getAll();
}
}
编写Controller类
UserController
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public boolean save(@RequestBody User user) {
return userService.save(user);
}
@PutMapping
public boolean update(@RequestBody User user) {
return userService.update(user);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return userService.delete(id);
}
@GetMapping("/{id}")
public User getById(@PathVariable Integer id) {
return userService.getById(id);
}
@GetMapping
public List<User> getAll() {
return userService.getAll();
}
}
完整项目结构如下
4、单元测试及启动服务器测试
在test下创建一个com\UserServiceTest测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetById(){
User user = userService.getById(1);
System.out.println(user);
}
@Test
public void testGetAll(){
List<User> all = userService.getAll();
System.out.println(all);
}
}
运行两个测试方法
控制台输出
配置tomcat服务器并启动服务器
在apifox中进行增删改查测试
新增操作
数据库变化
修改操作
数据库变化
删除操作
数据库变化
按id查询
查询全部
二、统一结果封装
在我们传输数据给前端时,有Boolean型数据,User类的对象还有List集合对象,这时对于前端在接收这些数据时就显得比较凌乱了,此时我们便需要与前端协议返回一个统一的数据结果,这样前端在解析数据时就可以按照一种方式进行解析,简化开发过程,这就是表现层与前端的数据传输协议
可以通过创建一个结果模型类,来进行对这个传输协议的实现
- 为了封装返回的结果数据:封装数据到data属性中
- 为了封装返回的数据时何种操作及是否操作成功:封装操作结果到code属性中
- 操作失败后为了封装返回的错误消息:封装特殊消息到message(msg)属性中
Result类
public class Result {
//描述统一格式中的数据
private Object data;
//描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
private Integer code;
//描述统一格式中的消息,可选属性
private String msg;
public Result() {
}
public Result(Integer code, Object data) {
this.code = code;
this.data = data;
}
public Result(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Code类
//状态码
public class Code {
//操作成功
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
//操作失败
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
}
以上两个类建议位于controller层
修改Controller类中的返回值为Result
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public Result save(@RequestBody User user) {
boolean flag = userService.save(user);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
@PutMapping
public Result update(@RequestBody User user) {
boolean flag = userService.update(user);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = userService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
User user = userService.getById(id);
Integer code = user != null ? Code.GET_OK : Code.GET_ERR;
String msg = user != null ? "" : "数据查询失败,请重试!";
return new Result(code, user, msg);
}
@GetMapping
public Result getAll() {
List<User> userList = userService.getAll();
Integer code = userList != null ? Code.GET_OK : Code.GET_ERR;
String msg = userList != null ? "" : "数据查询失败,请重试!";
return new Result(code, userList, msg);
}
}
启动服务器,在apifox中进行测试
查询单个测试
查询单个错误测试
对于其他功能测试此处便不一一展示
三、项目异常处理
1、异常分类
当我们在项目搭建与运行时,异常是不可避免的,异常种类及原因也是五花八门
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
通过上面这些异常不难发现,我们开发任何一个位置都有可能出现异常,而且这些异常时不能避免的,所以我们就需要将这些异常进行处理
对于以上异常,我们可以大致分为三类
- 业务异常(BusinessException)
规范的或不规范用户行为产生的异常,如用户在页面输入内容时未安装指定格式进行数据填写,如在年龄框输入的是字符串
- 系统异常(SystemException)
项目运行过程中可预计但无法避免的异常,如数据库或服务器宕机
- 其他异常(Exception)
编程人员未预料到的异常,如用到的文件不存在
2、异常解决方案
对于各个层级的异常,我们都需将其抛到表现层进行处理,对此,SpringMVC提供了异常处理器供我们解决以上问题
自定义异常处理器,用于封装异常信息,对异常进行分类
SystemException
public class SystemException extends RuntimeException {
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
BusinessException
public class BusinessException extends RuntimeException {
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
- 让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try...catch...或throws了
- 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的
- Exception为其他未预期异常,不用自定义异常类
在Code类中新增异常需要属性
//系统异常
public static final Integer SYSTEM_ERR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;
//业务异常
public static final Integer BUSINESS_ERR = 60002;
在UserServiceImpl的getById中模拟异常
@Override
public User getById(Integer id) {
//模拟业务异常产生
if (id == 1) {
throw new BusinessException(Code.BUSINESS_ERR, "请规范操作");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try {
int i = 1 / 0;
} catch (Exception e) {
throw new SystemException(Code.SYSTEM_ERR, "服务器访问超时,请重试!", e);
}
return userDao.getById(id);
}
对于三种异常的基本解决方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作(提示用户名已存在或密码格式不正确)
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户(如系统繁忙,请稍后再试;系统正在维护升级,请稍后再试;系统出问题,请联系系统管理员等)
- 发送特定消息给运维人员,提醒维护(可以发送短信、邮箱或者是公司内部通信软件)
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
在处理器类ProjectExceptionAdvice中处理自定义异常及其他异常
@RestControllerAdvice
public class ProjectExceptionAdvice {
//@ExceptionHandler设置当前处理器对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(), null,ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doBusinessExcetion(BusinessException ex){
return new Result(ex.getCode(), null, ex.getMessage());
}
//除了自定义的异常处理器,定义对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}
- @RestControllerAdvice:为Rest风格开发的控制器类做增强,自带@ResponseBody注解与@Component注解,具备对应的功能
- @ExceptionHandler:设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行
启动服务器,在apifox中测试异常
业务异常
系统异常
其他未预料异常
其他异常实际为业务异常,这里仅演示此为未预料异常,因为未将其加入业务异常处理范围