在前后端分离的开发模式盛行的当下,利用 Spring Boot、Vue 和 MyBatis 组合搭建项目,能有效提升开发效率和系统可维护性。本文将以图书管理系统为实战案例,详细介绍从环境搭建到功能实现的全流程,带你深入掌握这一技术栈的应用。
一、项目概述
图书管理系统旨在实现对图书信息的高效管理,包括图书的增删改查、分类管理、借阅记录跟踪等功能。后端采用 Spring Boot 构建 RESTful API,MyBatis 负责数据库交互;前端基于 Vue 实现友好的用户界面,通过 Axios 与后端进行数据通信。
二、后端开发:Spring Boot + MyBatis
2.1 项目创建与基础配置
通过Spring Initializr创建项目,添加Spring Web、MyBatis Framework、MySQL Driver和Lombok依赖。在application.yml文件中配置数据库连接:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=UTC
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.library.entity
2.2 定义实体类
创建Book实体类,用于映射数据库中的图书表:
import lombok.Data;
@Data
public class Book {
private Long id;
private String title;
private String author;
private String category;
private int quantity;
private boolean isAvailable;
}
2.3 实现 MyBatis 映射层
BookMapper接口继承BaseMapper,利用 MyBatis-Plus 的通用 CRUD 方法简化开发:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.library.entity.Book;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookMapper extends BaseMapper<Book> {
// 可自定义扩展方法,如按类别查询图书
Book[] selectByCategory(String category);
}
同时,在mapper目录下创建BookMapper.xml文件,实现自定义 SQL:
<?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.example.library.mapper.BookMapper">
<select id="selectByCategory" resultType="com.example.library.entity.Book">
SELECT * FROM book WHERE category = #{category}
</select>
</mapper>
2.4 服务层与控制器层开发
服务层BookService接口及其实现类BookServiceImpl:
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.library.entity.Book;
public interface BookService extends IService<Book> {
Book[] getBooksByCategory(String category);
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.library.entity.Book;
import com.example.library.mapper.BookMapper;
import com.example.library.service.BookService;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {
@Override
public Book[] getBooksByCategory(String category) {
return baseMapper.selectByCategory(category);
}
}
控制器层BookController提供对外 API:
import com.example.library.entity.Book;
import com.example.library.service.BookService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping
public List<Book> getAllBooks() {
return bookService.list();
}
@GetMapping("/{id}")
public Book getBookById(@PathVariable Long id) {
return bookService.getById(id);
}
@PostMapping
public Book createBook(@RequestBody Book book) {
bookService.save(book);
return book;
}
@PutMapping("/{id}")
public Book updateBook(@PathVariable Long id, @RequestBody Book book) {
Book existBook = bookService.getById(id);
if (existBook != null) {
existBook.setTitle(book.getTitle());
existBook.setAuthor(book.getAuthor());
existBook.setCategory(book.getCategory());
existBook.setQuantity(book.getQuantity());
existBook.setIsAvailable(book.isAvailable());
bookService.updateById(existBook);
}
return existBook;
}
@DeleteMapping("/{id}")
public void deleteBook(@PathVariable Long id) {
bookService.removeById(id);
}
@GetMapping("/category/{category}")
public Book[] getBooksByCategory(@PathVariable String category) {
return bookService.getBooksByCategory(category);
}
}
三、前端开发:Vue 3
3.1 项目初始化与配置
使用 Vue CLI 创建项目,并安装 Axios:
vue create library-frontend
cd library-frontend
npm install axios
在vue.config.js中配置代理解决跨域问题:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};
3.2 组件开发
图书列表组件BookList.vue
<template>
<div class="book-list">
<h1>图书列表</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>书名</th>
<th>作者</th>
<th>类别</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="book in books" :key="book.id">
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.category }}</td>
<td>{{ book.quantity }}</td>
<td>
<button @click="editBook(book.id)">编辑</button>
<button @click="deleteBook(book.id)" style="margin-left: 10px;">删除</button>
</td>
</tr>
</tbody>
</table>
<button @click="createBook">添加图书</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import request from '../utils/request';
import { useRouter } from 'vue-router';
const router = useRouter();
const books = ref([]);
onMounted(() => {
loadBooks();
});
const loadBooks = async () => {
try {
const res = await request.get('/books');
books.value = res;
} catch (error) {
console.error('加载图书失败:', error);
}
};
const editBook = (id) => {
router.push(`/books/${id}/edit`);
};
const deleteBook = async (id) => {
try {
await request.delete(`/books/${id}`);
loadBooks();
} catch (error) {
console.error('删除图书失败:', error);
}
};
const createBook = () => {
router.push('/books/create');
};
</script>
<style scoped>
/* 样式代码 */
</style>
图书编辑组件BookEdit.vue
<template>
<div class="book-edit">
<h2>{{ isEdit ? '编辑图书' : '添加图书' }}</h2>
<form @submit.prevent="handleSubmit">
<div>
<label>书名:</label>
<input type="text" v-model="form.title" required>
</div>
<div>
<label>作者:</label>
<input type="text" v-model="form.author" required>
</div>
<div>
<label>类别:</label>
<input type="text" v-model="form.category" required>
</div>
<div>
<label>数量:</label>
<input type="number" v-model="form.quantity" min="0">
</div>
<div>
<label>是否可用:</label>
<input type="checkbox" v-model="form.isAvailable">
</div>
<button type="submit">{{ isEdit ? '保存修改' : '添加图书' }}</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import request from '../utils/request';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const isEdit = route.params.id !== undefined;
const form = ref({
title: '',
author: '',
category: '',
quantity: 0,
isAvailable: true
});
onMounted(() => {
if (isEdit) {
loadBook(route.params.id);
}
});
const loadBook = async (id) => {
try {
const res = await request.get(`/books/${id}`);
form.value = res;
} catch (error) {
console.error('加载图书失败:', error);
}
};
const handleSubmit = async () => {
try {
if (isEdit) {
await request.put(`/books/${route.params.id}`, form.value);
} else {
await request.post('/books', form.value);
}
router.push('/books');
} catch (error) {
console.error('提交失败:', error);
}
};
</script>
四、项目联调与常见问题解决
4.1 联调测试
先启动后端 Spring Boot 项目,再启动前端 Vue 项目。在浏览器中访问前端地址,测试图书的增删改查功能。使用浏览器开发者工具的 Network 面板,查看接口请求和响应数据是否正常。
4.2 常见问题及解决方案
- 跨域问题:确保后端配置了 CORS 跨域支持,前端正确配置了代理。若仍有问题,检查请求头和响应头中的跨域相关字段。
- 接口返回 404:确认后端接口路径与前端请求路径一致,检查后端控制器方法是否正确映射。
- 数据格式错误:检查前后端数据传输的 JSON 格式是否匹配,注意字段名称大小写和数据类型。
五、项目扩展方向
- 权限管理:引入 Spring Security 实现用户登录、角色权限控制,前端根据用户权限展示不同功能。
- 借阅功能:新增借阅记录表,实现图书借阅、归还逻辑,记录借阅时间和归还状态。
- 数据可视化:使用 ECharts 等图表库,对图书类别分布、借阅统计等数据进行可视化展示。