Vue + Spring Boot 项目实战(八):数据库设计与增删改查

前言

这篇文章主要讲解对数据库的增删改查,之前讲过数据库的安装及使用,没什么印象的同学可以复习一下 「Vue + Spring Boot 项目实战(四):数据库的引入」

可以说几乎所有的 Web 项目都要涉及增删改查这一套东西。有很多刚入职的年轻人抱怨“面试造核弹,工作拧螺丝”,本以为工作的内容会多么高大上,结果天天在公司就只学到了增删改查。实际上这里面有很多门道,大的方面如不同应用场景下的技术选型,小的方面如某一条 SQL 语句怎么写,可能看似细微的差别,放在互联网高并发的环境下就会带来截然不同的效果。

考虑到实际情况(作者贼懒),这个教程里并没有深入探讨这些问题,目标就是把功能给实现喽,然后让跟着做的各位也能实现,如果你们有更好的想法,欢迎分享给我,我会不断重构这个项目,争取做成一个有一些实际意义的东西。等我把这个坑填完了,再仔细想想性能优化之类的问题怎么写。

一、数据库设计

把拍脑袋想字段的过程称为数据库设计也是脸皮够厚,不过反正你们也不能顺着网线来打我。说起来也确实没啥需要认真琢磨的地方,等到之后做后台管理时可以把用户权限管理那一套东西整上,现在嘛,我们的需求大致如下:

  • 展示书籍的信息,包括封面、标题、作者、出版日期、出版社、摘要和分类。
  • 维护分类信息。

所以表结构暂时是下面这个样子:
在这里插入图片描述
包含建表及完整数据的 wj.sql 文件在我的 gayhub github 上:

https://github.com/Antabot/White-Jotter/tree/master/src/main/resources

如果直接 clone 项目,因为我配置了自动注入的代码,直接运行项目就可以生成表结构(数据库需要手动创建)。关闭此功能,可以把 application.properties 中的如下代码注释掉:

# 每次运行时初始化数据库,如不需要可以注释掉
spring.datasource.initialization-mode=always

这里我只贴出来建表语句吧:

user 表:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` char(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

book 表:

DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `cover` varchar(255) DEFAULT '',
  `title` varchar(255) NOT NULL DEFAULT '',
  `author` varchar(255) DEFAULT '',
  `date` varchar(20) DEFAULT '',
  `press` varchar(255) DEFAULT '',
  `abs` varchar(255) DEFAULT NULL,
  `cid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_book_category_on_cid` (`cid`),
  CONSTRAINT `fk_book_category_on_cid` FOREIGN KEY (`cid`) REFERENCES `category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8;

category 表:

DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这里注意在 book 表在 cid 上有一个外键。

二、增删改查

还是先看需求:

  • 查询书籍信息(查)
  • 上传书籍信息(增)
  • 修改书籍信息(改)
  • 删除书籍信息(删)

查询里涉及按关键字查询(图书检索),上传书籍信息里涉及图片上传,我打算单独写一篇,这里先不多说。

1.pojo

我们需要新建两个 pojo,分别是Category 和 Book 。

Category:

package com.evan.wj.pojo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@Table(name = "category")
@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" })

public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    int id;

    String name;

    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;
    }
}

Book:

package com.evan.wj.pojo;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import javax.persistence.*;

@Entity
@Table(name = "book")
@JsonIgnoreProperties({"handler","hibernateLazyInitializer"})
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    int id;

    @ManyToOne
    @JoinColumn(name="cid")
    private Category category;

    String cover;
    String title;
    String author;
    String date;
    String press;
    String abs;

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getPress() {
        return press;
    }

    public void setPress(String press) {
        this.press = press;
    }

    public String getAbs() {
        return abs;
    }

    public void setAbs(String abs) {
        this.abs = abs;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}


2.dao 层

我们需要再添加一个 BookDAO ,一个 CategoryDAO 。

BookDAO :

package com.evan.wj.dao;

import com.evan.wj.pojo.Book;
import com.evan.wj.pojo.Category;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface BookDAO extends JpaRepository<Book,Integer> {
    List<Book> findAllByCategory(Category category);
    List<Book> findAllByTitleLikeOrAuthorLike(String keyword1, String keyword2);
}

这里延续之前 JPA 的写法,findAllByCategory() 之所以能实现,是因为在 Book 类中有如下注解:

    @ManyToOne
    @JoinColumn(name="cid")
    private Category category;

实际上是把 category 对象的 id 属性作为 cid 进行了查询。

CategoryDAO :

package com.evan.wj.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.evan.wj.pojo.Category;

public interface CategoryDAO extends JpaRepository<Category, Integer> {

}

这个 DAO 不需要额外构造的方法,JPA 提供的默认方法就够用了。

3.Service 层

CategoryService:

package com.evan.wj.service;

import com.evan.wj.dao.CategoryDAO;
import com.evan.wj.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CategoryService {
    @Autowired
    CategoryDAO categoryDAO;

    public List<Category> list() {
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        return categoryDAO.findAll(sort);
    }

    public Category get(int id) {
        Category c= categoryDAO.findById(id).orElse(null);
        return c;
    }
}

这里对查询的结果做了个排序以及条件判断。

BookService:

package com.evan.wj.service;

import com.evan.wj.dao.BookDAO;
import com.evan.wj.pojo.Book;
import com.evan.wj.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {
    @Autowired
    BookDAO bookDAO;
    @Autowired
    CategoryService categoryService;

    public List<Book> list() {
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        return bookDAO.findAll(sort);
    }

    public void addOrUpdate(Book book) {
        bookDAO.save(book);
    }

    public void deleteById(int id) {
        bookDAO.deleteById(id);
    }

    public List<Book> listByCategory(int cid) {
        Category category = categoryService.get(cid);
        return bookDAO.findAllByCategory(category);
    }
}

这个 Service 提供了四个功能,分别是查出所有书籍、增加或更新书籍、通过 id 删除书籍和通过分类查出书籍。

这里注意一下 save() 方法的作用是,当主键存在时更新数据,当主键不存在时插入数据。

这也就是核心的业务逻辑了。

4.Controller 层

在 Controller 层里我们继续写需要的 API 。

package com.evan.wj.controller;

import com.evan.wj.pojo.Book;
import com.evan.wj.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class LibraryController {
    @Autowired
    BookService bookService;

    @GetMapping("/api/books")
    public List<Book> list() throws Exception {
        return bookService.list();
    }

    @PostMapping("/api/books")
    public Book addOrUpdate(@RequestBody Book book) throws Exception {
        bookService.addOrUpdate(book);
        return book;
    }

    @PostMapping("/api/delete")
    public void delete(@RequestBody Book book) throws Exception {
        bookService.deleteById(book.getId());
    }
    

    @GetMapping("/api/categories/{cid}/books")
    public List<Book> listByCategory(@PathVariable("cid") int cid) throws Exception {
        if (0 != cid) {
            return bookService.listByCategory(cid);
        } else {
            return list();
        }
    }
}

5.测试接口

运行项目,测试一下功能。

首先是查询所有书籍,访问 http://localhost:8443/api/books,结果如下:
在这里插入图片描述
尼玛好密集,这些可都是我一个个录的。。。

然后测试分类,访问 http://localhost:8443/api/categories/1/books,查看所有分类 id 为 1,即分类为“文学”的书籍,结果如下:
在这里插入图片描述
增加或修改结合前端测试,这里我还没讲,先给大家看下效果:
在这里插入图片描述
我的设计是点击封面是修改,点击那个加号图标是增加。在书籍卡片的右下角有一个回收桶图标,是删除。删除我就不测试了,录了半天删了怪可惜的。

哈哈以前我总把图片右下角自动加的水印给删了,现在有自定义域名了感觉放上去也行,真香真香。欢迎大家通过 https://learner.blog.csdn.net 域名访问我的博客,虽然并没有什么用。

三、下一步

下一篇计划讲解实现按关键字和作者查询图书以及图片上传的接口,再把前端页面完善完善,这个教程就差不多可以收尾啦。

断断续续写了这么久,是我没想到的,自己开发的时候感觉写教程就是复制粘贴写个注释,但真正搞起来特别消耗精力,而且我离一个优秀的开发者还有很大距离,做出的东西会有很多不足,好在这个过程中我还是有很大收获的。有大佬说过,你看过去的自己觉得很傻逼,恰恰说明你进步了。

之前也说过,最近在做后台管理系统,这东西是通用的,将来 Vue3.0 出了我就把那个的前端重写一下然后加到这个项目里面。不过我打算只写一篇文章介绍一下功能以及实现的核心思路,不敢立 flag 做教程了。。。项目还是会完整地上传到 GitHub 上,有兴趣的同学到时候可以下载下来。

查看系列文章目录:
https://learner.blog.csdn.net/article/details/88925013

上一篇: Vue + Spring Boot 项目实战(七):导航栏与图书页面设计

下一篇:Vue + Spring Boot 项目实战(九):核心功能的前端实现

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页