SpringBoot+Vue前后端分离实战:从零构建Web应用与数据库查询

📅 2026/8/3 11:48:04
SpringBoot+Vue前后端分离实战:从零构建Web应用与数据库查询
1. 项目概述从单体应用到前后端分离的必然之路刚入行那会儿做Java Web项目还是JSP、Freemarker的天下前端代码和后端逻辑搅在一起改个按钮颜色都得重新打包部署整个应用效率低得让人抓狂。后来Ajax流行起来算是有了点分离的苗头但本质上还是后端控制着视图渲染。直到近几年前端框架像Vue、React的成熟加上后端SpringBoot这种“约定大于配置”的框架普及真正的前后端分离才成了现代Web开发的标准姿势。这个“SpringBootVue入门并实现前后端分离和数据库查询”的项目说白了就是带你亲手搭一个最经典的现代Web应用骨架。它要解决的核心痛点很明确让前端工程师专心写页面交互让后端工程师专注业务逻辑和数据处理双方通过清晰的API接口契约协作互不干扰并行开发提升整体交付效率和质量。这个项目非常适合刚学完Java基础和Vue基础、想看看它们怎么配合的初学者或者是从传统单体应用转型过来的开发者。通过这个项目你能摸清楚一个请求从前端Vue组件发出到后端SpringBoot控制器接收、处理、查询数据库再返回数据给前端渲染的完整闭环。别看流程简单这里面涉及的环境搭建、跨域处理、API设计、联调技巧每一个都是实战中必踩的坑。2. 技术栈选型与项目初始化为什么是它们2.1 后端SpringBoot不仅仅是简化配置选择SpringBoot作为后端框架远不止因为它能帮我们省去一大堆XML配置。对于这个入门项目而言它的核心优势在于“开箱即用”和“快速聚焦”。SpringBoot Starter依赖是精髓。我们不需要再纠结该引入哪些版本的Spring MVC、Jackson、Tomcat。只需要在pom.xml里声明一个spring-boot-starter-web它就会自动帮我们引入一个能直接运行Web应用的所有必要依赖并且这些依赖的版本都是经过兼容性测试的极大避免了依赖冲突这个“新手杀手”。对于数据库操作我们选择spring-boot-starter-data-jpa配合MySQL驱动。JPAJava Persistence API是一种规范Hibernate是其最流行的实现。为什么不用更灵活的MyBatis对于入门项目JPA的Repository接口和方法名派生查询能让我们几乎不写SQL就完成基础的增删改查非常适合快速验证业务模型。你只需要定义一个继承JpaRepository的接口Spring Data JPA就能自动实现常见的数据操作。注意很多新手会在spring-boot-starter-data-jpa和mybatis-spring-boot-starter之间纠结。我的建议是入门期优先用JPA它的“约定大于配置”能让你更快理解ORM对象关系映射的思想。等熟悉了基本操作再根据项目对复杂SQL的需求去学习MyBatis。项目初始化我强烈推荐使用 Spring Initializr IDEA内置了它的界面。在勾选依赖时除了必选的Spring Web和Spring Data JPA记得把MySQL Driver也选上。生成项目后用IDEA打开第一件事就是配置application.yml或application.properties。# application.yml 配置示例 server: port: 8080 # 后端服务端口 spring: datasource: url: jdbc:mysql://localhost:3306/your_database_name?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 启动时根据实体类自动更新表结构生产环境请勿使用 show-sql: true # 在控制台打印SQL语句调试神器 properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect # 指定方言这里有个关键参数ddl-autoupdate模式在开发时非常方便Hibernate会自动检查实体类与数据库表的差异并执行修改。但严禁在生产环境使用因为它可能导致数据丢失。生产环境应该使用validate或none并通过规范的SQL脚本管理表结构。2.2 前端Vue 3 Vite体验现代前端开发流前端选择Vue 3不仅因为其简洁的API和强大的响应式系统更因为其组合式APIComposition API提供了比Vue 2选项式API更好的逻辑复用和组织能力。虽然对于第一个项目你可能感觉不明显但早点接触组合式API对未来开发复杂组件有益无害。脚手架工具我们不用传统的Vue CLI而是用Vite。Vite的优势在于极快的冷启动和热更新。它利用浏览器原生ES模块导入在开发环境下不需要打包瞬间启动。对于新手来说更快的反馈循环意味着更高的学习效率和更少的等待烦躁感。使用npm或yarn初始化Vue项目npm create vuelatest # 或 yarn create vue在创建向导中项目名称如vue-frontend、是否启用TypeScript、JSX、Vue Router、Pinia状态管理、ESLint等可以根据需要选择。对于纯入门项目Vue Router用于页面路由和Pinia用于状态管理建议先不选以减少初始复杂度聚焦在核心的“请求-响应”流程上。ESLint可以选择它有助于保持代码规范。创建完成后进入项目安装依赖并启动cd vue-frontend npm install npm run dev看到localhost:5173Vite默认端口可以访问说明前端环境好了。2.3 前后端分离的目录结构规划在IDEA中一个清晰的目录结构能有效管理前后端代码。我推荐两种方式单仓库并列结构适合个人学习/小项目my-fullstack-demo/ ├── backend/ # SpringBoot项目根目录 │ ├── src/ │ ├── pom.xml │ └── ... └── frontend/ # Vue项目根目录 ├── src/ ├── package.json └── ...将两个项目放在同一个父目录下方便同时打开和切换。IDEA可以打开my-fullstack-demo作为根项目然后通过“添加模块”的方式把backend和frontend都加进来但注意前端模块可能需要配置为JavaScript/TypeScript模块。完全独立仓库推荐更符合工程实践 后端springboot-backend和前端vue-frontend分别是两个独立的Git仓库放在两个独立的IDE窗口或编辑器中开发。这种方式职责清晰部署独立更贴近真实团队协作场景。对于入门我建议先用第一种管理起来简单直观。3. 后端核心实现构建RESTful API与数据层3.1 定义数据实体Entity与Repository假设我们做一个最简单的“用户信息”查询功能。首先在backend/src/main/java/com/example/demo/entity下创建User实体类。package com.example.demo.entity; import jakarta.persistence.*; import lombok.Data; Data Entity Table(name user) // 指定对应数据库表名 public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) // 主键自增 private Long id; Column(nullable false, unique true) // 非空且唯一 private String username; Column(nullable false) private String email; // 省略构造器、getter/setter使用了Lombok的Data注解自动生成 }这里用了Data注解它是Lombok提供的能自动生成getter、setter、toString等方法。需要在pom.xml中引入Lombok依赖并且IDE需要安装Lombok插件才能正常识别。接着创建Repository接口。在backend/src/main/java/com/example.demo/repository下创建UserRepository。package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface UserRepository extends JpaRepositoryUser, Long { // 方法名派生查询根据用户名查找 User findByUsername(String username); // 查找所有邮箱包含特定字符串的用户 ListUser findByEmailContaining(String keyword); }你不需要写这个接口的任何实现类。Spring Data JPA会在运行时动态生成实现。JpaRepository已经提供了save(),findAll(),findById(),deleteById()等基本CRUD方法。上面自定义的findByUsernameJPA会根据方法名自动解析并生成查询SELECT * FROM user WHERE username ?这就是“方法名派生查询”的魅力。3.2 编写服务层Service与控制器Controller虽然对于简单查询可以直接在Controller中调用Repository但引入Service层是一个好习惯它负责业务逻辑使Controller更专注于处理HTTP请求和响应。在service包下创建UserServicepackage com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; Service public class UserService { Autowired private UserRepository userRepository; public ListUser getAllUsers() { return userRepository.findAll(); // 调用JPA内置方法 } public User getUserByUsername(String username) { return userRepository.findByUsername(username); // 调用自定义方法 } public ListUser searchUsersByEmail(String keyword) { return userRepository.findByEmailContaining(keyword); } }最后也是最关键的一环RESTful API控制器。在controller包下创建UserController。package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController // 表明这是一个RESTful控制器返回值直接序列化为JSON RequestMapping(/api/users) // 定义API的基础路径 public class UserController { Autowired private UserService userService; GetMapping // 对应 GET /api/users public ListUser getAllUsers() { return userService.getAllUsers(); } GetMapping(/{username}) // 对应 GET /api/users/{username} public User getUserByUsername(PathVariable String username) { return userService.getUserByUsername(username); } GetMapping(/search) // 对应 GET /api/users/search?emailxxx public ListUser searchUsersByEmail(RequestParam String email) { return userService.searchUsersByEmail(email); } }RestController组合了Controller和ResponseBody意味着每个方法的返回值都会通过Spring的HttpMessageConverter默认是Jackson自动转换成JSON格式写入HTTP响应体。RequestMapping定义类级别的请求映射路径。GetMapping指定处理HTTP GET请求。PathVariable将URL路径中的变量如{username}绑定到方法参数。RequestParam将HTTP请求参数如?emailxxx绑定到方法参数。至此一个简单的查询API就完成了。启动SpringBoot应用运行DemoApplication里的main方法访问http://localhost:8080/api/users如果数据库有数据你应该能看到返回的JSON数组。3.3 跨越“天堑”解决跨域问题CORS当前端项目运行在localhost:5173后端在localhost:8080端口不同浏览器出于安全考虑会阻止这种跨域请求。这是前后端分离遇到的第一个也是最常见的“拦路虎”。解决方式是在SpringBoot后端进行CORS配置。创建一个配置类WebConfigpackage com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig { Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 对哪些API路径启用CORS .allowedOrigins(http://localhost:5173) // 允许的前端源 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) // 允许的HTTP方法 .allowedHeaders(*) // 允许的请求头 .allowCredentials(true); // 是否允许发送Cookie等凭证 } }; } }这个配置告诉浏览器来自http://localhost:5173的对/api/**路径的请求是安全的允许访问。在生产环境中allowedOrigins应该替换为确切的前端部署域名而不是*允许所有源这是重要的安全实践。4. 前端核心实现Vue组件与异步请求4.1 安装Axios并封装请求工具Vue项目本身不处理HTTP请求我们需要一个HTTP客户端库。Axios是基于Promise的、功能丰富的HTTP库是Vue生态中的首选。在前端项目根目录下安装Axiosnpm install axios为了统一管理API请求比如统一处理错误、设置基础URL我们通常不会在每个组件里直接axios.get(...)而是进行一层封装。在src目录下创建utils文件夹然后创建request.js// src/utils/request.js import axios from axios; // 创建一个自定义的Axios实例 const request axios.create({ baseURL: http://localhost:8080, // 后端API的基础地址 timeout: 5000, // 请求超时时间毫秒 }); // 请求拦截器可选在发送请求前做点什么例如添加token request.interceptors.request.use( (config) { // 假设我们把token存在localStorage const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }, (error) { return Promise.reject(error); } ); // 响应拦截器可选在收到响应后做点什么例如统一处理错误 request.interceptors.response.use( (response) { // 如果后端返回的数据结构是 { code: 200, data: ..., message: success } // 可以在这里直接返回 response.data.data return response.data; }, (error) { console.error(API请求错误:, error); // 可以在这里根据HTTP状态码或自定义错误码进行统一错误提示 if (error.response error.response.status 401) { // 未授权跳转到登录页 window.location.href /login; } // 将错误继续抛给具体的请求调用处处理 return Promise.reject(error); } ); export default request;接着创建src/api目录来管理所有API接口。创建userApi.js// src/api/userApi.js import request from /utils/request; // 代表 src 目录需要在vite.config.js中配置 export function getAllUsers() { return request({ url: /api/users, method: get, }); } export function getUserByUsername(username) { return request({ url: /api/users/${username}, method: get, }); } export function searchUsersByEmail(emailKeyword) { return request({ url: /api/users/search, method: get, params: { email: emailKeyword }, // Axios会自动将params拼接到URL上 }); }4.2 构建Vue组件并调用API现在我们来创建一个显示用户列表的组件。在src/components下创建UserList.vue。template div classuser-list h2用户列表/h2 !-- 搜索框 -- div classsearch-box input typetext v-modelsearchKeyword placeholder输入邮箱关键词搜索... inputhandleSearch / /div !-- 加载状态 -- div v-ifloading正在加载用户数据.../div !-- 错误信息 -- div v-iferror classerror{{ error }}/div !-- 用户列表 -- ul v-else-ifusers.length 0 li v-foruser in users :keyuser.id {{ user.username }} - {{ user.email }} /li /ul div v-else暂无用户数据/div /div /template script setup // 使用Vue 3的组合式API import { ref, onMounted } from vue; import { getAllUsers, searchUsersByEmail } from /api/userApi; // 定义响应式数据 const users ref([]); // 用户列表数据 const loading ref(false); // 加载状态 const error ref(); // 错误信息 const searchKeyword ref(); // 搜索关键词 // 获取所有用户的函数 const fetchUsers async () { loading.value true; error.value ; try { const response await getAllUsers(); users.value response; // 根据后端实际返回数据结构调整这里假设直接返回数组 } catch (err) { error.value 获取用户列表失败 (err.message || 未知错误); console.error(err); } finally { loading.value false; } }; // 搜索用户的函数使用防抖优化避免频繁请求 let searchTimer null; const handleSearch () { clearTimeout(searchTimer); searchTimer setTimeout(async () { if (!searchKeyword.value.trim()) { fetchUsers(); // 关键词为空恢复显示全部 return; } loading.value true; try { const response await searchUsersByEmail(searchKeyword.value.trim()); users.value response; } catch (err) { error.value 搜索失败 err.message; } finally { loading.value false; } }, 300); // 延迟300毫秒执行 }; // 组件挂载时加载数据 onMounted(() { fetchUsers(); }); /script style scoped .user-list { padding: 20px; } .search-box { margin-bottom: 15px; } .search-box input { padding: 8px 12px; width: 300px; border: 1px solid #ccc; border-radius: 4px; } .error { color: red; margin: 10px 0; } ul { list-style: none; padding: 0; } li { padding: 8px; border-bottom: 1px solid #eee; } /style这个组件演示了几个关键点组合式API (script setup)逻辑更集中ref定义响应式数据onMounted处理生命周期。异步请求使用async/await配合Axios调用封装的API函数代码更清晰。状态管理用loading、error、users等ref管理UI状态。用户交互通过v-model绑定搜索框input监听输入事件并实现了简单的防抖(setTimeout/clearTimeout)来优化性能。条件渲染使用v-if、v-else根据状态显示不同内容。最后在src/App.vue中引入并使用这个组件template div idapp UserList / /div /template script setup import UserList from ./components/UserList.vue; /script启动前端(npm run dev)和后端访问http://localhost:5173如果一切顺利你应该能看到从后端数据库查询并渲染出来的用户列表并且搜索功能也能正常工作。5. 联调、部署与进阶思考5.1 前后端联调实战技巧与问题排查即使代码都写对了第一次联调也难免遇到问题。这里有几个必查清单网络问题最基础也最易忽略检查服务是否启动分别访问http://localhost:8080后端和http://localhost:5173前端看是否有响应。检查控制台错误打开浏览器开发者工具F12的“网络(Network)”标签页查看API请求是否发出、状态码是什么。常见的404是URL错了500是后端服务器内部错误看后端控制台日志CORS错误是跨域配置问题。CORS问题症状浏览器控制台报错包含“CORS policy”、“Access-Control-Allow-Origin”等字样。解决确认后端WebConfig中allowedOrigins包含了前端地址如http://localhost:5173。注意如果前端用了代理如Vite Proxy则请求不会触发跨域此时CORS配置可能不生效。Vite代理配置开发环境利器 为了避免CORS和端口问题可以在Vite中配置代理让前端开发服务器将所有/api请求转发到后端。// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://localhost:8080, // 后端地址 changeOrigin: true, // 修改请求头中的origin为目标地址 // rewrite: (path) path.replace(/^\/api/, ) // 如果需要重写路径 } } } })配置后前端代码中请求/api/usersVite会将其代理到http://localhost:8080/api/users。此时前端请求的baseURL可以设为空或因为走的是同源代理。数据格式问题请求格式axios默认发送JSON如果后端接收RequestParam则前端要用params如果后端是RequestBody则前端要用data。响应格式确认后端返回的数据结构是否与前端解析的匹配。例如你的Controller直接返回ListUserSpringBoot会将其序列化为JSON数组。如果后端统一包装了响应体如ResultListUser前端就需要从response.data.data里取数据。5.2 项目打包与简易部署开发完成后需要将项目构建成可部署的产物。后端打包在SpringBoot项目根目录下执行mvn clean packageMaven或使用IDEA的Maven插件。会在target目录下生成一个可执行的JAR文件如demo-0.0.1-SNAPSHOT.jar。使用java -jar demo-0.0.1-SNAPSHOT.jar即可运行。前端打包在Vue项目根目录下执行npm run build。这会在dist目录下生成静态文件HTML, JS, CSS。部署方式完全分离部署推荐将后端JAR包部署到云服务器如使用nohup java -jar ... 。将前端dist目录下的文件部署到Nginx或对象存储如阿里云OSS、腾讯云COS并配置Nginx将API请求反向代理到后端服务。这是生产环境的标准做法。静态文件集成部署简化将前端打包后的dist目录下的所有文件复制到SpringBoot项目的src/main/resources/static目录下。然后打包SpringBoot应用这样JAR包里就同时包含了前端资源和后端代码。访问http://服务器IP:8080就能看到前端页面。这种方式适合个人演示或极简项目但失去了前后端独立部署的优势。5.3 从入门到进阶下一步可以做什么这个项目跑通只是万里长征第一步。要真正用于生产或深化理解还有很多方向可以探索状态管理当组件间需要共享数据如用户登录状态时引入Pinia或Vuex。路由管理构建多页面应用使用Vue Router。API安全为后端API添加认证JWT和授权Spring Security。数据库进阶学习更复杂的JPA查询Query注解、关联映射OneToMany、事务管理Transactional。前端UI库使用Element Plus、Ant Design Vue等组件库快速搭建美观界面。错误处理与日志完善前后端的全局错误处理和日志记录。自动化测试为后端Service层编写单元测试JUnit为前端组件编写测试Vitest。容器化使用Docker将前后端分别容器化用Docker Compose编排实现环境标准化。这个入门项目的价值在于它像一张地图帮你标记出了前后端分离开发中最核心的几个地标环境搭建、API定义、数据流动、跨域处理。当你亲手走通一遍之后再去看那些更复杂的框架和概念心里就有底了。记住所有的复杂架构都是由这些简单的模块组合、演化而来的。先跑通再优化逐步深入这才是最有效的学习路径。