Spring+SpringMVC+Mybatis整合及项目异常处理思想

一、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
  1. 发送对应消息传递给用户,提醒规范操作(提示用户名已存在或密码格式不正确)
  • 系统异常(SystemException
  1. 发送固定消息传递给用户,安抚用户(如系统繁忙,请稍后再试;系统正在维护升级,请稍后再试;系统出问题,请联系系统管理员等)
  2. 发送特定消息给运维人员,提醒维护(可以发送短信、邮箱或者是公司内部通信软件)
  3. 记录日志
  • 其他异常(Exception
  1. 发送固定消息传递给用户,安抚用户
  2. 发送特定消息给编程人员,提醒维护(纳入预期范围内)
  3. 记录日志

在处理器类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中测试异常

业务异常 

系统异常

其他未预料异常 

其他异常实际为业务异常,这里仅演示此为未预料异常,因为未将其加入业务异常处理范围

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值