简介
把之前的增删改查案例移植到IDEA并改为Spring Boot。
创建项目
打开IDEA,点击菜单栏File-New-Project
选Spring Initializr,左侧面板选择Spring Initializr
Server URL:
Project SDK:选择你本地的JDK版本
点击Next
填写项目基本信息(Maven 坐标)
- Group:组织域名
- Artifact:项目名
- Name:项目名称
- Description:描述(可选)
- Package name:bao'mi包名
- Language:Java
- Type:Maven Project
- Packaging:jar(Spring Boot推荐)
- Java Version:和你选的JDK一致
- 点击Next
选择Spring Boot版本与依赖
Spring Boot Version:3.2.X(最新,需要JDK17+)
依赖(Dependencies): 常用必选:
- Spring Web(开发Web/接口必备)
- Lombok(简化Get/set/构造器)
- Spring Boot DevTools(热重载)
数据库可选:
- MySQL Driver
- Spring Data JPA/MyBatis Framework
按需勾选,不要一次性选太多
点击Next
目录存放位置
- Project name:确认项目名
- Project location:选本地存放路径
- 点击Finish
等待项目初始化
- IDEA会自动下载依赖、生成标准目录结构
- 右下角看到Dependencies downloaded即完成
测试启动项目
找到启动类:DemoApplication.java 点击类旁绿三角→选择Run'DemoApplication'
访问浏览器http://localhost:8080看到白页 / 404 都正常,因为还没写接口
导入项目
导入后的效果图
结构讲解
Java代码目录(src/main/java下)
1.controller包
- 核心文件:ProductController.java
- 作用:控制层(入口层),是整个项目的【对位接口入口】
-
- 接受前端/客户端发送来的HTTP请求(比如增删改查)
-
- 调用service层的业务逻辑,处理请求参数
-
- 把处理结果封装成响应(JSON/页面)返回给前端
- 典型注解:@RestController、@RequestMapping、@GetMapping/@PostMapping等
- 前端方位/product/list,有ProductController接收请求,调用ProductService查询商品列表,再把列表分会给前端
2.mapper包(MyBatis专用)
- 核心文件:ProductMapper.java
- 作用:数据访问层(DAO层),是Java代码和数据库之间的【桥梁】
-
- 定义操作数据库的方法接口(比如selectById、insert、update、delete)
-
- 不需要写实现类,MyBatis会自动根据ProductMapper.xml生成代理实现
-
- 被service层调用,完成数据库的增删改查(CRUD)
- 典型注解:@Mapper(Spring Boot 中用户扫描MyBatis接口)
- 对应关系:ProductMapper.java必须和resources/mapper/ProductMapper.xml--一一对应
3.pojo包(也叫entity/entity包)
- 核心文件:Product.java
- 作用:实体类(数据模型),是数据库表在Java中的【映射对象】
-
- 类的属性和数据库product表的字段一一对应(比如id、name、price等)
-
- 用户在各层之间传递数据(Controller→Service→Mapper→数据库,反之亦然)
-
- 通常配合Lombok的@Data注解,自动生成getter/setter/toString等方法
- 例如查询商品时,MyBatis会把数据库查询结果封装成Product对象,返回给Service/Controller
4.service包
- 核心文件:ProductService.java
- 作用:业务逻辑层(核心层),是项目的【业务大脑】
- 封装具体的业务逻辑(比如商品新增校验、库存扣减、价格计算、事务控制)
-
- 调用mapper层操控数据库,同时给controller层提供业务方法
-
- 处理复杂的业务流程(比如新增时,校验参数,再调用Mapper插入,再记录日志)
- 典型注解:@Service、@Transactional(事务控制)
- 核心价值:把业务逻辑和Controller、Mapper解耦,让代码更容易维护、复用
5.EasyAddDelChangeSelectAppl(启动类)
- 作用:Spring Boot项目的启动入口
-
- 类上必须标注@SpringBootApplication注解(核心注解)
-
- 运行这个类,就能启动整个Spring Boot项目,加载所有配置、Bean、依赖
-
- 是Spring Boot项目的【总开关】,所有组件(Controller、Service、Mapper)都由它扫描并管理
- 注意:启动类不许放在所有业务包的上级目录
资源目录(src/main/resources下)
1.mapper目录
- 核心文件:ProductMapper.xml
- 作用:MyBatis的SQL映射文件,是Product.java的[SQL实现]
-
- 编写具体的SQL语句(增删改查),和ProductMapper.java中的接口方法一一绑定
-
- 配置参数映射、结果集映射(把数据库字段映射到Product实体类的属性)
-
- 支持复杂SQL(多表联查、分页、动态SQL/等)
- 对应关系:namespace必须等于ProductMapper.java的全类名,方法id必须等于接口方法名
2.static目录
- 核心文件:index.html
- 作用:静态资源目录,存放不需要编译的静态文件
-
- Spring Boot会自动把static目录下的文件映射为根路径访问(比如index.html直接访问http://localhost:8080/index.html)
-
- 可存放:HTML、CSS、JS、图片、前端打包后的静态资源
- 你写的商品管理前端页面,就可以放在static目录下,直接通过浏览器访问
3.templates目录
- 作用:模板引擎目录,存放动态页面模板
-
- 配合Thymeleaf/Freemarker等模板引擎使用,生成动态HTML页面
-
- 你当前项目中这个目录是空的,说明暂时用不到模板引擎(大概率是前后端分离项目,用static方前端资源)
- 注意:templates下的文件不能直接访问,必须通过Controller跳转渲染
4.application.yml(核心配置文件)
- 作用:Spring Boot项目的全局配置文件
-
- 配置项目的所有核心参数:
-
-
- 服务器端口(server.port:8080)
-
-
-
- 数据库连接(MySQL地址、账号、密码、驱动)
-
-
-
- MyBatis配置(mapper-location、type-aliases-package)
-
-
-
- MyBatis配置(mapper-location、type-aliases-package)
-
-
-
- 日志级别、Redis配置、第三方服务配置等
-
-
- 是Spring Boot项目的【配置中心】,所有环境相关的配置都在这里管理
- 格式优势:yml用缩进代替properties的键值对,层级更清晰,可读性更强
完整的请求流程
- 前端发起请求:GET/product/list
- ProductController接收请求,调用productService的list()方法
- ProductService执行业务逻辑(分页、过滤),调用ProductMapper的selectList()方法
- ProductMapper接口触发MyBatis,执行ProductMapper.xml中对应的SQL语句
- MyBatis执行SQL,从数据库查询商品数据,封装成product实体类集合
- 数据沿原路返回:Mapper→Service→Controller
- ProductController把集合转成JSON,返回给前端
- 前端渲染页面,展示商品列表(页面来自static/index.html)
补充:各层的设计原则(规范)
| 层级 | 核心职责 | 绝对不能做的事 |
|---|---|---|
| Controller | 接收请求、参数校验、返回响应 | 写业务逻辑、直接操作数据库 |
| Service | 封装业务逻辑、事务控制 | 写SQL、直接处理HTTP请求 |
| Mapper | 操作数据库、定义数据访问接口 | 写业务逻辑、处理HTTP请求 |
| POJO | 数据载体、表映射 | 写业务逻辑、写SQL |
建议
这是个非常标准的Spring Boot+MyBaits MVC分层结构,符合企业开发标准
- 不要再Controller里写SQL,不要再Service里处理HTTP请求
- 所有业务逻辑都放在Service层,保证代码的可维护性和复用性
- application.yml里的配置要分类管理(数据库、服务器、MyBatis分开写),方便排查问题
代码
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品列表页</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1 align="center">商品列表</h1><br/>
<div align="center">
<form id="searchForm">
名称:<input type="text" id="name" name="name">
品牌:
<select id="brand" name="brand">
</select>
<button type="button" onclick="search()">查询</button>
</form><br/>
<h2>新增商品</h2>
<form id="addForm">
名称:<input type="text" name="name"><br>
品牌:<input type="text" name="brand"><br>
价格:<input type="text" name="price"><br>
日期:<input type="text" name="addtime"><br>
库存:<input type="text" name="inventory"><br>
<button type="button" onclick="addProduct()">新增</button>
</form>
<h4>查询结果:</h4>
<table border="1" id="productTable">
<tr>
<th>编号</th>
<th>商品名称</th>
<th>品牌</th>
<th>价格</th>
<th>进货日期</th>
<th>库存</th>
<th>操作</th>
</tr>
</table>
<div style="margin-top:10px">
<button onclick="prevPage()">上一页</button>
<span id="pageInfo">第 1 页</span>
<button onclick="nextPage()">下一页</button>
</div>
</div>
<script>
let page = 1;
let size = 5;
let totalPage = 1;
$(function(){
loadPageData();
loadBrands();
});
// 查询
function search(){
page = 1;
loadPageData();
}
// 加载分页数据
function loadPageData() {
let name = $("#name").val();
let brand = $("#brand").val();
$.ajax({
url: "/product/page",
type: "get",
data: {
page: page,
size: size,
name: name,
brand: brand
},
success: function(res){
$("#productTable tr:not(:first)").remove();
let list = res.list || [];
for(let i=0; i<list.length; i++){
let p = list[i];
let tr = `
<tr>
<td>${p.id}</td>
<td>${p.name}</td>
<td>${p.brand}</td>
<td>${p.price}</td>
<td>${p.addtime}</td>
<td>${p.inventory}</td>
<td>
<button onclick='updateProduct(${p.id})'>修改</button>
<button onclick='deleteProduct(${p.id})'>删除</button>
</td>
</tr>`;
$("#productTable").append(tr);
}
let total = res.total || 0;
totalPage = Math.ceil(total / size);
$("#pageInfo").text(`第 ${page} 页 / 共 ${totalPage} 页`);
}
});
}
// 加载品牌下拉框
function loadBrands(){
$.get("/product/brands", function(data){
$("#brand").empty();
$("#brand").append("<option value=''>全部品牌</option>");
for(let i=0;i<data.length;i++){
$("#brand").append(`<option value='${data[i]}'>${data[i]}</option>`);
}
});
}
// 新增
function addProduct(){
$.ajax({
url: "/product/add",
type: "post",
contentType: "application/json",
data: JSON.stringify({
name: $("input[name=name]").val(),
brand: $("input[name=brand]").val(),
price: $("input[name=price]").val(),
addtime: $("input[name=addtime]").val(),
inventory: $("input[name=inventory]").val()
}),
success: function(res){
alert("新增成功");
$("#addForm")[0].reset();
loadPageData();
}
});
}
// 删除
function deleteProduct(id){
if(confirm("确定删除?")){
$.get("/product/delete?id="+id, function(){
loadPageData();
});
}
}
// 修改
function updateProduct(id){
let name = prompt("请输入新商品名称");
let brand = prompt("请输入新品牌");
let price = prompt("请输入新价格");
let addtime = prompt("请输入新的进货时间");
let inventory = prompt("请输入新库存");
$.ajax({
url: "/product/update",
type: "post",
contentType: "application/json",
data: JSON.stringify({
id: id,
name: name,
brand: brand,
price: price,
addtime: addtime,
inventory: inventory
}),
success: function(res){
alert("修改成功!");
loadPageData();
}
});
}
// 上一页
function prevPage() {
if(page > 1) {
page--;
loadPageData();
}
}
// 下一页
function nextPage() {
if(page < totalPage) {
page++;
loadPageData();
}
}
</script>
</body>
</html>
service-ProductService
package com.example.easy_add_del_change_select.service;
import com.example.easy_add_del_change_select.mapper.ProductMapper;
import com.example.easy_add_del_change_select.pojo.Product;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ProductService {
@Resource
private ProductMapper productMapper;
public Map<String, Object> findByPage(Integer page, Integer size, String name, String brand) {
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("brand", brand);
map.put("start", (page - 1) * size);
map.put("size", size);
List<Product> list = productMapper.selectByPage(map);
int total = productMapper.selectCount(map);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
return result;
}
public List<Product> selectAll() {
return productMapper.selectAll();
}
public List<String> brandSelect() {
return productMapper.selectBrand();
}
public void add(Product product) {
productMapper.addProduct(product);
}
public void delete(int id) {
productMapper.deleteProduct(id);
}
public Product findById(int id) {
return productMapper.findById(id);
}
public void updateProduct(Product product) {
productMapper.updateProduct(product);
}
}
pojp-Product
package com.example.easy_add_del_change_select.pojo;
public class Product {
private int id;
private String name;
private String brand;
private int price;
private String addtime;
private int inventory;
public Product() {}
public Product(String name, String brand) {
this.name = name;
this.brand = brand;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getAddtime() {
return addtime;
}
public void setAddtime(String addtime) {
this.addtime = addtime;
}
public int getInventory() {
return inventory;
}
public void setInventory(int inventory) {
this.inventory = inventory;
}
@Override
public String toString() {
return "Product [id=" + id + ",name=" + name + ",brand=" + brand + ",price=" + price + ",addtime=" + addtime + ",inventory=" + inventory + "]";
}
}
mapper-ProductMapper
package com.example.easy_add_del_change_select.mapper;
import com.example.easy_add_del_change_select.pojo.Product;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface ProductMapper {
List<Product> selectByPage(Map<String, Object> map);
int selectCount(Map<String, Object> map);
List<Product> selectAll();
void addProduct(Product product);
void deleteProduct(int id);
Product findById(int id);
void updateProduct(Product product);
List<String> selectBrand();
}
controller-ProductController
package com.example.easy_add_del_change_select.controller;
import com.example.easy_add_del_change_select.pojo.Product;
import com.example.easy_add_del_change_select.service.ProductService;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/product")
public class ProductController {
@Resource
private ProductService productService;
@GetMapping("/page")
public Map<String, Object> page(Integer page, Integer size, String name, String brand) {
return productService.findByPage(page, size, name, brand);
}
@GetMapping("/all")
public List<Product> getAll() {
return productService.selectAll();
}
@GetMapping("/brands")
public List<String> getBrands() {
return productService.brandSelect();
}
@PostMapping("/add")
public String add(@RequestBody Product product) {
productService.add(product);
return "success";
}
@GetMapping("/delete")
public String delete(int id) {
productService.delete(id);
return "success";
}
@GetMapping("/find")
public Product find(int id) {
return productService.findById(id);
}
@PostMapping("/update")
public String update(@RequestBody Product product) {
productService.updateProduct(product);
return "success";
}
}
打开启动类Application然后运行,在网页中打开http://localhost:8080/index.html