这篇文章主要讲解对数据库的增删改查,之前讲过数据库的安装及使用,没什么印象的同学可以复习一下 「Vue + Spring Boot 项目实战(四):数据库的引入」。
可以说几乎所有的 Web 项目都要涉及增删改查这一套东西。有很多刚入职的年轻人抱怨“面试造核弹,工作拧螺丝”,本以为工作的内容会多么高大上,结果天天在公司就只学到了增删改查。实际上这里面有很多门道,大的方面如不同应用场景下的技术选型,小的方面如某一条 SQL 语句怎么写,可能看似细微的差别,放在互联网高并发的环境下就会带来截然不同的效果。
考虑到实际情况(作者贼懒),这个教程里并没有深入探讨这些问题,目标就是把功能给实现喽,然后让跟着做的各位也能实现,如果你们有更好的想法,欢迎分享给我,我会不断重构这个项目,争取做成一个有一些实际意义的东西。等我把这个坑填完了,再仔细想想性能优化之类的问题怎么写。
把拍脑袋想字段的过程称为数据库设计也是脸皮够厚,不过反正你们也不能顺着网线来打我。说起来也确实没啥需要认真琢磨的地方,等到之后做后台管理时可以把用户权限管理那一套东西整上,现在嘛,我们的需求大致如下:
所以表结构暂时是下面这个样子:
建表及完整数据的 sql 文件在我的 gayhub github 上:
https://github.com/Antabot/White-Jotter/tree/master/wj
这里我只贴出来建表语句吧:
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 上有一个外键。
还是先看需求:
查询里涉及按关键字查询(图书检索),上传书籍信息里涉及图片上传,我打算单独写一篇,这里先不多说。
我们需要新建两个 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;
}
}
我们需要再添加一个 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 提供的默认方法就够用了。
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);
Sort sort = new Sort(Sort.Direction.DESC, "id");
return bookDAO.findAllByCategory(category);
}
}
这个 Service 提供了四个功能,分别是查出所有书籍、增加或更新书籍、通过 id 删除书籍和通过分类查出书籍。
这里注意一下 save()
方法的作用是,当主键存在时更新数据,当主键不存在时插入数据。
这也就是核心的业务逻辑了。
在 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();
}
}
}
运行项目,测试一下功能。
首先是查询所有书籍,访问 http://localhost:8443/api/books
,结果如下:
尼玛好密集,这些可都是我一个个录的。。。
然后测试分类,访问 http://localhost:8443/api/categories/1/books
,查看所有分类 id 为 1,即分类为“文学”的书籍,结果如下:
增加或修改结合前端测试,这里我还没讲,先给大家看下效果:
我的设计是点击封面是修改,点击那个加号图标是增加。在书籍卡片的右下角有一个回收桶图标,是删除。删除我就不测试了,录了半天删了怪可惜的。
哈哈以前我总把图片右下角自动加的水印给删了,现在有自定义域名了感觉放上去也行,真香真香。欢迎大家通过 https://learner.blog.csdn.net 域名访问我的博客,虽然并没有什么用。
下一篇计划讲解实现按关键字和作者查询图书以及图片上传的接口,再把前端页面完善完善,这个教程就差不多可以收尾啦。
断断续续写了这么久,是我没想到的,自己开发的时候感觉写教程就是复制粘贴写个注释,但真正搞起来特别消耗精力,而且我离一个优秀的开发者还有很大距离,做出的东西会有很多不足,好在这个过程中我还是有很大收获的。有大佬说过,你看过去的自己觉得很傻逼,恰恰说明你进步了。
之前也说过,最近在做后台管理系统,这东西是通用的,将来 Vue3.0 出了我就把那个的前端重写一下然后加到这个项目里面。不过我打算只写一篇文章介绍一下功能以及实现的核心思路,不敢立 flag 做教程了。。。项目还是会完整地上传到 GitHub 上,有兴趣的同学到时候可以下载下来。