Spring Boot增删改查案例

9 阅读7分钟

简介

把之前的增删改查案例移植到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即完成

image.png

image.png

测试启动项目

找到启动类:DemoApplication.java 点击类旁绿三角→选择Run'DemoApplication'

访问浏览器http://localhost:8080看到白页 / 404 都正常,因为还没写接口

导入项目

导入后的效果图

image.png

结构讲解

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
  • 作用:静态资源目录,存放不需要编译的静态文件
    • 可存放: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的键值对,层级更清晰,可读性更强

完整的请求流程

  1. 前端发起请求:GET/product/list
  2. ProductController接收请求,调用productService的list()方法
  3. ProductService执行业务逻辑(分页、过滤),调用ProductMapper的selectList()方法
  4. ProductMapper接口触发MyBatis,执行ProductMapper.xml中对应的SQL语句
  5. MyBatis执行SQL,从数据库查询商品数据,封装成product实体类集合
  6. 数据沿原路返回:Mapper→Service→Controller
  7. ProductController把集合转成JSON,返回给前端
  8. 前端渲染页面,展示商品列表(页面来自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