SpringBoot整合MyBatis和SpringMVC

我们通过一个案例来演示SpringBoot整合MyBatis和SpringMVC
该案例通过前后端分离来完成,前端使用vue2加JavaScript实现

前端部分

主要代码

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
<template>
<div>
<!-- <input type="button" value="获取远程数据" @click="sendReq()"> -->
<div class="title">学生列表</div>
<div class="thead">
<div class="row bold">
<div class="col">编号</div>
<div class="col">姓名</div>
<div class="col">性别</div>
<div class="col">年龄</div>
</div>
</div>
<div class="tbody">
<div v-if="students.length > 0">
<div class="row" v-for="stu in students" :key="stu.id">
<div class="col">{{stu.id }}</div>
<div class="col">{{stu.name }}</div>
<div class="col">{{stu.sex }}</div>
<div class="col">{{stu.age}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from '../utils/myaxios'
const options = {
mounted: function(){
this.sendReq()
},
data: function() {
return {
students: []
};
},
methods: {
async sendReq() {
const resp = await axios.get("/api/students");
console.log(resp.data);
this.students = resp.data.data;
}
}
};
export default options;
</script>
<style scoped>
div {
font-family: 华文行楷;
font-size: 20px;
}

.title {
margin-bottom: 10px;
font-size: 30px;
color: #333;
text-align: center;
}

.row {
background-color: #fff;
display: flex;
justify-content: center;
}

.col {
border: 1px solid #f0f0f0;
width: 15%;
height: 35px;
text-align: center;
line-height: 35px;
}

.bold .col {
background-color: #f1f1f1;
}
</style>
  • v-if:条件渲染指令,根据表达式的值来动态控制元素的显示或隐藏, 当表达式中的值为true时,其里面的内容才会展现出来

  • v-else:条件渲染指令,当v-if中表达式的值为false时,其里面的内容才会展现出来,v-else需要和v-if搭配使用,不能单独存在

  • v-for:列表渲染指令,类似于Java中的foreach

    • key:key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素
  • import axios from ‘../utils/myaxios’ :这里的myaxios是自定义的axios

  • mounted:生命周期钩子函数,或自动执行其中的内容

    • 在Vue实例被挂载到真实的DOM元素后被调用
    • 仅触发一次
    • 应用场景:执行异步操作、注册事件监听器、调用第三方库或插件、执行其它初始化操作
  • const resp = await axios.get(“/api/students”):像后端发送get请求并获取数据

  • this.students = resp.data.data:把后端返回的对象中的数据获取到

    • resp.data.data:由于后端统一了返回格式,resp.data只能获取到Result对象,resp.data.data才能获取到Result中的data数据

      1
      2
      3
      4
      5
      public class Result<T> implements Serializable {
      private Integer code; //编码:1成功,0和其它数字为失败
      private String msg; //错误信息
      private T data; //数据
      ......

自定义axios.js

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
import axios from 'axios'
const _axios = axios.create({
// baseURL: 'http://localhost:8080',
withCredentials: true
});
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function(response) {
return response;
},
function(error) {
if (error.response.status === 400) {
console.log('请求参数不正确');
return Promise.resolve(400);
} else if (error.response.status === 401) {
console.log('跳转至登录页面');
return Promise.resolve(401);
} else if (error.response.status === 404) {
console.log('资源未找到');
return Promise.resolve(404);
}
return Promise.reject(error);
}
);
export default _axios;

自定义axios.js

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
import axios from 'axios'
const _axios = axios.create({
// baseURL: 'http://localhost:8080',
withCredentials: true
});
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function(response) {
return response;
},
function(error) {
if (error.response.status === 400) {
console.log('请求参数不正确');
return Promise.resolve(400);
} else if (error.response.status === 401) {
console.log('跳转至登录页面');
return Promise.resolve(401);
} else if (error.response.status === 404) {
console.log('资源未找到');
return Promise.resolve(404);
}
return Promise.reject(error);
}
);
export default _axios;

配置文件vue.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})

在这里配置的端口号和统一的URL前缀映射



后端部分

POM文件

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
<?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.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</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.2.0</version>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.7.6</version>
</dependency>

</dependencies>

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

</project>

数据库表

创建一个简单的表即可,输入一些内容


实体类

1
2
3
4
5
6
7
8
9
10
import lombok.Data;
@Data
public class Student {

private int id;
private String name;
private String sex;
private int age;
}

  • lombok插件中的@Data注解可以帮我们生成set、get、构造方法、toString等方法

  • Java中的类的属性名要和数据库表中的对应,数据库表可以用下划线,Java在编译时会把它用驼峰命名法转换,例如Java中的teacherName对应数据库中的teacher_name

  • Java中的类属性要和数据库表中的字段对应,数据类型也要对应

    MySQL字段类型 Java实体类属性
    int、tinyint、smallint、mediumint int
    bigint long
    float float
    double double
    varchar、char、text String
    date java.sql.Date
    time、datetime java.sqlTimestamp
    bit boolean

控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class StudentController {

@Autowired
StudentService studentService;

@GetMapping("/api/students")
public Result<List<Student>> getAllStudent() {
List<Student> students = studentService.getAll();
return Result.success(students);
}

}

业务层

接口

1
2
3
public interface StudentService {
List<Student> getAll();
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class StudentServiceImpl implements StudentService {

@Autowired
StudentMapper studentMapper;

@Override

public List<Student> getAll() {
List<Student> students = studentMapper.getAll();
return students;
}
}
  • 这里的接口不是必须的,但是使用接口能够带来挺多方便
    • 在尚未实现具体Service逻辑的情况下编写上层代码,如Controller对Service的调用
    • Spring默认是基于动态代理实现AOP的,动态代理需要接口
    • 可以对Service进行多实现
  • 由于该案例只是简单的实现一下SpringBoot整合MyBatis和SpringMVC,并未在业务层做一些逻辑处理,只是简单获取数据并返回

数据层

1
2
3
4
5
6
7
@Mapper
@Repository
public interface StudentMapper{

@Select("select * from student")
List<Student> getAll();
}
  • @Mapper和@Repository都是在持久层的接口上添加注解。
  • @Mapper是属于mybatis的注解。在程序中,mybatis需要找到对应的mapper,在编译时候动态生成代理类,实现数据库查询功能
  • 但是如果只是单独的使用@Mapper注解的话,在idea中进行自动装配的时候,会出现警告,提示找不到这个bean。但是这个不影响程序运行,可以直接忽略也可以添加@Repository注解。这样spring会扫描@Repository并识别这个bean,就不会出现这个警告。

统一返回结果类

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
@Data
public class Result<T> implements Serializable {

private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据

public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}

public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}

public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}

}
  • Serializable接口:一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化
    • 序列化:序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据
    • 把对象转换为字节序列的过程称为对象的序列化、把字节序列恢复为对象的过程称为对象的反序列化
  • 这里用的是泛型类,即在声明类时使用泛型

配置文件

application.yml

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/vue?serverTimezone=GMT%2B8&useSSL=true
username: root
password: "020427"
  • 这里配置了数据源

application.properties

1
2
3
4
5
6
7
8
#端口号
server.port=8080
#druid数据库连接池
#type=com.alibaba.druid.pool.DruidDataSource
#清除缓存
spring.thymeleaf.cache=false
#配置mapper
mybatis.mapper-locations=classpath:mapper/*.xml
  • 这里配置了端口号
  • mybatis.mapper-locations:用于将配置路径下的*.xml文件加载到mybatis中,这个案例中mapper文件下没有写xml文件,采用的是注解的方法,所以这里可以不用配置
  • spring.thymeleaf.cache:Spring Thymeleaf模板引擎的配置属性,用于指定是否启用模板缓存
    • true:Thymeleaf会将解析过的模板缓存起来,以提高性能
    • false:每次请求都会重新解析模板

启动项目

启动前端项目
在vue项目下用命令行运行启动指令

1
npm run serve

后端正常在启动类启动就行了

最终正常前端从后端获取到了数据并展示在页面中


SpringBoot整合MyBatis和SpringMVC
https://lzhengjy.github.io/2023/11/09/SpringBoot整合MyBatis和SpringMVC/
作者
Zheng
发布于
2023年11月9日
许可协议