使用spring boot实现一个简单MVC应用

Spring使用过程中需要大量繁杂的XML文件配置,Spring3之后开始引入“约定大于配置”的理念,Spring Boot就是在这样的理念下抽象出来的框架。它本身并不替代、扩展Spring的特征,而是用于快速、敏捷开发Spring应用,以帮助开发者用少量的配置代码就可以快速上手Spring应用。此外还集成了一些第三方库用于零配置、开箱即用功能,以及大型项目常用的安全、配置等非功能性应用。

1、创建Spring项目

在Intellij IDEA中点击New Project,在其中选择Spring Initializr,可以设置项目的SDK,然后Next。
创建Spring boot项目的原理就是访问https://start.spring.io,从该网站生成并下载SpringBoot项目
在这里插入图片描述
接着设置项目的Group、Artifact、Version等信息,点击Next
在这里插入图片描述
接着设置项目所需依赖以及spring boot的版本号,这里选择Spring Web和Mybatis,点击Next
在这里插入图片描述最后选择项目存放位置并点击Finish,生成Spring Boot项目结构如下所示,
在这里插入图片描述
静态资源的访问:spring boot的自动配置类WebMvcAutoConfiguration将如下五个路径添加到了资源映射,即我们可以直接访问项目下的这几个路径中的静态资源。例如项目名为blog,在html页面访问static/css/bootstrap.css

<link rel="stylesheet" href="/blog/css/bootstrap.css"/>
  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
  • /:当前项目的根路径

2、项目配置

2.1、pom.xml

由于项目使用Maven进行依赖管理,如果本地自己装有maven,则需要设置IDEA使用本地的maven而不是自身集成的,在idea的设置中搜索maven并设置其安装目录和以来仓库如下
在这里插入图片描述
接下来通过pom文件配置项目的依赖,如下所示为自动生成的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tory</groupId>
    <artifactId>springbootdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

<parent>标签中通过spring-boot-starter-parent来对下面的spring相关的依赖版本进行管理,在parent中设置了版本version为2.3.1.RELEASE后,下面的相关依赖就不必再设置version了。

接下来是项目的groupId、artifactId、version、name、description、java版本等信息。

接着在<dependencies>标签中引入项目所需依赖,除了spring-boot-starter-web、mybatis-spring-boot-starter、spring-boot-starter-test之外,这里还使用了用于数据库连接的mysql-connector-java、连接池管理的c3p0

依赖项spring-boot-starter-web包含了spring-webmvc,spring-boot-starter-validation,spring-boot-starter,spring-boot-starter-json,spring-boot-starter-tomcat这5个基础依赖,其中spring-webmvc又包含了Spring开发相关的大部分依赖,如spring-web、spring-beans、spring-aop、spring-core、spring-context、spring-expression,因此不需要再单独引入这些依赖。

最后<build>标签内是项目用到的构建工具

2.2、application.properties

在application.properties文件中对数据库连接信息、mybatis属性进行设置如下

server.port代表项目的端口号,context-path代表项目根路径

# 项目端口、路径设置
server.port=8080
server.servlet.context-path=/SpringBootDemo

# 设置数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop_demo
spring.datasource.username=root
spring.datasource.password=123456

# 设置mybatis
# 自动使用自增主键填充bean
mybatis.configuration.use-generated-keys=true
# 开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
# 允许使用别名替换列名
mybatis.configuration.use-column-label=true
# mapper文件地址
mybatis.mapper-locations=classpath:mapper/*.xml
# 给实体类设置别名
mybatis.type-aliases-package=com.tory.springbootdemo.entity

3、 一个Demo

接下来使用Spring Boot实现一个简单的MVC的例子,实现对数据库区中域类型Area的增删改查请求操作,最后项目结构如下所示
在这里插入图片描述

3.1、创建实体类

在数据库shop_demo下,存储区域信息的表格tb_area数据表结构如下:
在这里插入图片描述
在entity包下创建对应的实体类Area

package com.tory.springbootdemo.entity;

import java.util.Date;

public class Area {
    private Integer areaId;
    private String areaName;
    private Integer priority;
    private Date createTime;
    private Date lastEditTime;

	//getter and setter ......
}

3.2、Dao层

在Dao层实现对数据库的增删改查操作,首先定义方法接口AreaDao

package com.tory.springbootdemo.dao;

import com.tory.springbootdemo.entity.Area;

import java.util.List;

public interface AreaDao {
    //新增区域
    int insertArea(Area area);

    //删除区域
    int deleteArea(int areaId);

    //修改区域
    int updateArea(Area area);

    //查询所有区域
    List<Area> queryArea();

    //根据Id查询区域
    Area queryAreaById(int areaId);
}

接着在resources下新建mapper文件夹用于存放mapper文件,在其中创建AreaDao.xml文件实现上面的接口操作

<?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.tory.springbootdemo.dao.AreaDao">
    <insert id="insertArea" useGeneratedKeys="true" keyProperty="areaId"
            keyColumn="area_id" parameterType="com.tory.springbootdemo.entity.Area">
        INSERT INTO tb_area(area_name, priority, create_time, last_edit_time)
        VALUES (#{areaName}, #{priority}, #{createTime}, #{lastEditTime})
    </insert>

    <delete id="deleteArea">
        DELETE FROM tb_area
        WHERE area_id = #{areaId}
    </delete>

    <update id="updateArea" parameterType="com.tory.springbootdemo.entity.Area">
        update tb_area
        <set>
            <if test="areaName != null">area_name=#{areaName},</if>
            <if test="priority != null">priority=#{priority},</if>
            <if test="lastEditTime != null">last_edit_time=#{lastEditTime}</if>
        </set>
        where area_id=#{areaId}
    </update>

    <select id="queryArea" resultType="com.tory.springbootdemo.entity.Area">
        SELECT area_id, area_name, priority, create_time, last_edit_time
        FROM tb_area
        ORDER BY priority DESC
    </select>
    <select id="queryAreaById" resultType="com.tory.springbootdemo.entity.Area">
        SELECT area_id, area_name, priority, create_time, last_edit_time
        FROM tb_area
        WHERE area_id = #{areaId}
    </select>
</mapper>

最后需要在SpringBoot启动类上添加注解@MapperScan开启mapper的扫描配置,扫描com.tory.springbootdemo.dao目录下的所有dao文件作为Mapper接口类

package com.tory.springbootdemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.tory.springbootdemo.dao")
public class SpringbootdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootdemoApplication.class, args);
    }
}

可以通过Test类测试上面的方法是否操作成功,在AreaDao类中点击Alt+Insert,弹出Generate提示,选择Test自动在test文件夹生成对应测试类AreaDaoTest,在类名上面添加注解@SpringBootTest以获取spring的上下文环境。运行queryArea()输出结果正确

package com.tory.springbootdemo.dao;

import com.tory.springbootdemo.entity.Area;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class AreaDaoTest {
    @Autowired
    private AreaDao areaDao;
    
    @Test
    void queryArea() {
        List<Area> areaList=areaDao.queryArea();
        for (Area area :areaList) {
            System.out.println(area.getAreaName());
        }
    }
}

3.3、Service层

在项目目录com.tory.springbootdemo下新建service包用于存放service文件,在其中创建AreaService接口,定义增删改查方方法:

package com.tory.springbootdemo.service;

import com.tory.springbootdemo.entity.Area;
import java.util.List;

public interface AreaService {
    //创建Area对象
    boolean addArea(Area area);
    //通过Id删除Area
    boolean deleteAreaById(int areaId);
    //更新Area
    Area updateArea(Area area);
    //通过Id查询Area
    Area queryAreaById(int areaId);
    //查询所有Area
    List<Area> queryAll();
}

接着创建impl文件夹用于存放接口的实现类,在其中实现AreaServiceImpl,通过调用Dao层的方法完成具体的增删改查操作。对相关异常不进行处理而是简单抛出RuntimeException,之后再封装相关异常类。

为该类添加@service注解表示这是一个service类。

对于增加、修改、删除需要进行事务管理,在对应的方法上添加注解@Transactional

package com.tory.springbootdemo.service.impl;

import com.tory.springbootdemo.dao.AreaDao;
import com.tory.springbootdemo.entity.Area;
import com.tory.springbootdemo.service.AreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class AreaServiceImpl implements AreaService {
    @Autowired
    private AreaDao areaDao;

    @Override
    @Transactional
    public boolean addArea(Area area) {
        // 判断areaName不为空
        if (area.getAreaName() != null && !"".equals(area.getAreaName())) {
            // 设置时间
            area.setCreateTime(new Date());
            area.setLastEditTime(new Date());
            try {
                int effectedNum = areaDao.insertArea(area);
                if (effectedNum > 0) {
                    return true;
                } else {
                    throw new RuntimeException("添加区域信息失败!");
                }
            } catch (Exception e) {
                throw new RuntimeException("添加区域信息失败:" + e.toString());
            }
        } else {
            throw new RuntimeException("区域信息不能为空!");
        }
    }

    @Override
    @Transactional
    public boolean deleteArea(int areaId) {
        if (areaId > 0) {
            try {
                // 删除区域信息
                int effectedNum = areaDao.deleteArea(areaId);
                if (effectedNum > 0) {
                    return true;
                } else {
                    throw new RuntimeException("删除区域信息失败!");
                }
            } catch (Exception e) {
                throw new RuntimeException("删除区域信息失败:" + e.toString());
            }
        } else {
            throw new RuntimeException("区域Id不能为空!");
        }
    }

    @Override
    @Transactional
    public Area updateArea(Area area) {
        if (area.getAreaId() != null && area.getAreaId() > 0) {
            //如果Id合法
            if ((area.getAreaName() != null && !area.getAreaName().trim().equals("")) || area.getPriority() != null) {
                //更新内容不为空
                area.setLastEditTime(new Date());
                int count = areaDao.updateArea(area);
                if (count < 1) {
                    throw new RuntimeException("更新失败");
                }
            } else {
                throw new RuntimeException("更新内容为空");
            }
        } else {
            throw new RuntimeException("Id不合法");
        }
        return areaDao.queryAreaById(area.getAreaId());
    }

    @Override
    public Area getAreaById(int areaId) {
        return areaDao.queryAreaById(areaId);
    }

    @Override
    public List<Area> getAreaList() {
        return areaDao.queryArea();
    }
}

3.4、Controller层

在项目的web目录下创建AreaController类来对前端的请求进行管理。

为该类添加注解@RestController,它等于@ResponseBody+@Controller,代表这是一个Controller类,并且将返回的数据对象转换为json格式。@RequestMapping代表对指定的请求路径进行响应,value = "area"代表响应“area/"的web请求,produces属性指定响应返回的编码格式防止出现中文乱码。

对于Controller中的具体方法,也需要使用@RequestMapping来指定请求路径,特别地,如果是Get请求,则可以用@GetMapping来代替,对应的Post请求有@PostMapping。例如@GetMapping(“list”),则浏览器访问“项目路径/area/list”就可以得到区域列表。

在具体的实现中通过调用Service层的接口实现对area区域的增删改查请求的响应,并将返回的结果以放在modelMap中,由于之前使用了@RequestMapping,返回结果会自动转换为Json格式。

package com.tory.springbootdemo.web;

import com.tory.springbootdemo.entity.Area;
import com.tory.springbootdemo.service.AreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping(value = "area", produces = "application/json;charset=utf-8")
public class AreaController {
    @Autowired
    private AreaService areaService;

    @PostMapping("add")
    private Map<String, Object> addArea(@RequestBody Area area) {
        Map<String, Object> modelMap = new HashMap<>();
        // 添加区域信息
        modelMap.put("success", areaService.addArea(area));
        return modelMap;
    }

    @GetMapping("remove")
    private Map<String, Object> removeArea(Integer areaId) {
        Map<String, Object> modelMap = new HashMap<String, Object>();
        // 删除区域信息
        modelMap.put("success", areaService.deleteArea(areaId));
        return modelMap;
    }

    @PostMapping("update")
    private Map<String, Object> modifyArea(@RequestBody Area area) {
        Map<String, Object> modelMap = new HashMap<>();
        // 修改区域信息
        modelMap.put("success", areaService.updateArea(area));
        return modelMap;
    }

    @GetMapping("getById")
    private Map<String, Object> getAreaById(Integer areaId) {
        Map<String, Object> modelMap = new HashMap<>();
        // 获取区域信息
        Area area = areaService.getAreaById(areaId);
        modelMap.put("area", area);
        return modelMap;
    }

    @GetMapping("list")
    private Map<String, Object> listArea() {
        Map<String, Object> modelMap = new HashMap<>();
        // 获取区域列表
        List<Area> list = areaService.getAreaList();
        modelMap.put("areaList", list);
        return modelMap;
    }
}

接着在IDEA中启动Spring boot项目,或者在项目根目录下输入./mvnw spring-boot:run
在这里插入图片描述
在浏览器中访问http://localhost:8080/SpringBootDemo/area/list,以Json格式返回区域列表如下:
在这里插入图片描述

3.5、异常处理

之前在Service层抛出的异常类型都是RuntimeException类型,这种异常抛出后程序就会终止执行,使得程序健壮性不好。我们可以自定义异常类型并进行捕获,然后返回结果信息给用户,这样程序并不会崩溃。

例如我们在项目的exception包下自定义异常类型AreaException,简单定义构造方法和getter方法

package com.tory.springbootdemo.exception;

public class AreaException extends Exception{
    private String message;

    public AreaException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

重写Service层的updateArea()方法,将其中的RuntimeException改为AreaException并且向上抛出

    @Override
    @Transactional
    public Area updateArea(Area area) throws AreaException {
        if (area.getAreaId() != null && area.getAreaId() > 0) {
            if ((area.getAreaName() != null && !area.getAreaName().trim().equals("")) || area.getPriority() != null) {
                area.setLastEditTime(new Date());
                int count = areaDao.updateArea(area);
                if (count < 1) {
                    throw new AreaException("数据库更新失败");
                }
            } else {
                throw new AreaException("更新内容为空");
            }
        } else {
            throw new AreaException("Id不合法");		//抛出AreaException类型的异常
        }
        return areaDao.queryAreaById(area.getAreaId());
    }

接着在handler包下定义AreaExceptionHandler类,对AreaException类型的异常进行捕获处理,设置success为false并以json的格式返回异常信息
需要添加@ControllerAdvice表示对Controller中的异常进行捕获,@ExceptionHandler(AreaException.class)表示某个方法只捕获特定类型的异常

package com.tory.springbootdemo.handler;

import com.tory.springbootdemo.exception.AreaException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class AreaExceptionHandler {
    @ExceptionHandler(AreaException.class)
    @ResponseBody
    private Map<String, Object> handleException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        Map<String, Object> modelMap = new HashMap<>();
        modelMap.put("success", false);
        modelMap.put("errMsg", e.getMessage());
        response.setCharacterEncoding("utf-8");     //防止中文乱码
        return modelMap;
    }
}

当我们查询的id不存在时,程序并不会因异常而终止,而是抛出AreaException并被捕获处理,然后返回错误信息如下:

{"success":false,"errMsg":"Id不合法"}

3.6、MockMvc模拟请求测试

在没有前端页面的情况下如果希望对服务器发送post测试请求可以使用MockMvc

在AreaController中点击Alt+Insert,选择Test自动生成对应的测试类AreaControllerTest,为其添加注解@SpringBootTest,并通过classes属性为其指定启动类

首先通过@Autowired获取上下文对象,然后将其作为参数,在测试方法执行之前的@BeforeEach通过MockMvcBuilders创建mockMvc对象

在测试方法testAdd()中,我们创建一个area对象,通过ObjectMapper将其转化为json字符串,并以post请求发送给/area/add以测试添加区域的功能。

mockMvc通过perform()方法模拟请求,在其中通过MockMvcRequestBuilderspost()方法发送post请求,并通过content()方法指定发送的内容,contentType()指定发送的格式
mockMvc通过andExpect()对返回的状态码进行校验,andDo()执行请求回调操作,andReturn()获取返回的内容。

package com.tory.springbootdemo.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tory.springbootdemo.SpringbootdemoApplication;
import com.tory.springbootdemo.entity.Area;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;


@SpringBootTest(classes = SpringbootdemoApplication.class)
class AreaControllerTest {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;  //获取上下文对象

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    public void testAdd() throws Exception {
        Area area = new Area();
        area.setAreaId(6);
        area.setAreaName("南苑");
        area.setPriority(5);
        ObjectMapper mapper = new ObjectMapper();
        String jsonStr = mapper.writeValueAsString(area); //将area对象转换为json字符串

        String response = mockMvc.perform(  //发送请求
                    MockMvcRequestBuilders.post("/area/add")
                    .content(jsonStr).contentType(MediaType.APPLICATION_JSON_UTF8)
                )
                .andExpect(MockMvcResultMatchers.status().isOk())       //校验返回的状态码
                .andDo(MockMvcResultHandlers.print())   //执行回调操作,打印HTTP请求与响应信息
                .andReturn().getResponse().getContentAsString();    //获取返回字符串
        System.out.println(response);
    }
}

测试打印的HTTP请求与相应结果如下所示,并且相应的area信息也添加到了数据库中
在这里插入图片描述
在这里插入图片描述

所有代码文件参见:https://github.com/SuperTory/SpringBootDemo/tree/master/Server

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值