接下来在SpringBoot下,把Spring、SpringMVC、MyBatis整合在一起,来实现一个简单的增删改查。
创建新模块,勾选spring-web(SpringMVC)以及MySQL的驱动,再手动导入MyBatisPlus以及Druid的依赖坐标:
Lombok,一个Java类库,提供了一组注解,用来简化POJO实体类开发。
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
表结构:
实体类:
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
@Data
包括了get/set方法,toString方法,hashCode方法,equals方法等,但不包括有参和无参构造。
导入MyBatisPlus与Druid对应的starter:
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.6version>
dependency>
配置数据源与MyBatisPlus对应的基础配置
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?servierTimezone=UTC
username: root
password: root
mybatis-plus:
global-config:
db-config:
table-prefix: t_ # 表名和实体类相比,表名的前缀,如此就不用再实体类中加注解指定表名了
id-type: auto # id生成策略使用数据库自增策略(和我数据库中设置的保持一致)
# 否则插入数据时MP使用雪花算法生成一个id
继承BaseMapper并指定泛型:
//要么在mapper中加@Mapper,要么去启动类加@MapperScan
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
为方便调试可以开启MyBatisPlus的日志,StdOutImpl即标准输出,即打印到控制台上
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
单元测试看效果:
@SpringBootTest
public class BookDaoTest {
@Autowired
private BookDao bookDao;
@Test
void testSave() {
Book book = new Book();
book.setName("测试数据");
book.setType("测试类型");
book.setDescription("测试描述数据");
bookDao.insert(book);
}
...
}
分页操作需要设定分页对象IPage,其实现类为Page
:
@Test
void testGetPage(){
IPage<Book> page = new Page<>(1,5);
bookDao.selectPage(page,null);
}
IPage对象中封装了分页操作中的所有数据,包括:
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用
MyBatisPlus拦截器
实现。
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
关于条件查询:
在单元测试中分别演示下:
@Test
void testGetByCondition(){
QueryWrapper<Book> qw = new QueryWrapper<>();
qw.like("name","Spring");
bookDao.selectList(qw);
}
@Test
void testGetByCondition(){
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
实际开发,从前端获取的搜索关键字,如果为null而不过滤,则 where name like %null%,因此:
@Test
void testGetByCondition(){
String name = "Spring";
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(name),Book::getName,"Spring"); //判空,为空则不拼SQL
bookDao.selectPage(page,lqw);
}
到此,数据层的开发结束。
写业务层之前,说下Mapper层和Service层接口方法名的命名,Mapper要起的偏数据库,Service则偏业务,Mapper接口的方法名看完就知道大概的SQL咋写的,Service层看方法名就知道是做啥业务的,以登录为例,如:
//Mapper
selectByUserNameAndPassword(String username,String password);
//Service
login(String username,String password);
Service层接口定义:
public interface BookService {
boolean save(Book book);
boolean delete(Integer id);
boolean update(Book book);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getByPage(int currentPage,int pageSize);
}
写实现类:
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
public Book getById(Integer id) {
return bookDao.selectById(id);
}
public List<Book> getAll() {
return bookDao.selectList(null);
}
public IPage<Book> getByPage(int currentPage, int pageSize) {
IPage page = new Page<Book>(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
继承IService接口,泛型传入实体类,即可有上面标准开发的那些方法。另外你业务需要的定制的方法,自己接着写就行。
此时的实现类:注意泛型中的M和T,M传Mapper类型,T是实体类
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
}
除了基础的MP提供的方法,其余你的定制业务方法,自己加就好:
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
@Autowired
private BookDao bookDao;
public Boolean insertSome(Book book) {
return bookDao.insert(book) > 0;
}
}
总结就是:
(ISerivce)与
业务层通用实现类(ServiceImpl)
基于Restful进行表现层接口开发:
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public List<Book> getAll(){
return bookService.list();
}
@PostMapping
public Boolean save(@RequestBody Book book){
return bookService.insert(book);
}
@PutMapping
public Boolean update(@RequestBody Book book){
return bookService.modify(book);
}
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable Integer id){
return bookService.delete(id);
}
@GetMapping("/{currentPage}/{pageSize}")
public List<Book> getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
return bookService.getPage(currentPage,pageSize).getRecords();
}
}
//接口功能实现,但返回结果不统一
//待续...
这里遇到个坑,注入Service层的Bean的时候,发现有两个可用的Bean而启动失败,参考【解决:缩小Mapper扫描范围,避免产生不必要的Bean】
设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议。说白了,找个盛饭的碗,查出来的数据就是饭,找个统一结果类来装一下。
//属性自己决定,平时用msg和data,这个flag看的我别扭
@Data
public class R{
private Boolean flag;
private Object data;
public R(){
}
public R(Boolean flag){
this.flag = flag;
}
public R(Boolean flag,Object data){
this.flag = flag;
this.data = data;
}
//也可以定义个静态方法,里面封装构造方法
public static R success(Object data){
return new R(ture,data)
}
}
改下Controller层的返回类型和逻辑:
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
...
@GetMapping
public R getAll(){
List<Book> bookList = bookService.list();
return new R(true ,bookList);
}
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
return new R(true,page);
}
}