基于上一篇文章中的项目,有很多问题需要改进,才能更贴近实际的工作情况。比如图书管理系统中,图书是一个实体,具有很多属性,比如唯一编号、书名、描述信息、发版时间等,这些所有信息都需要我们进行存储和管理,所以需要引入数据库技术(此处使用MySql)。
整合mybatis(使用代码生成器)
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录(摘自百度百科)。
1、以图书管理系统为例,首先预备一个book数据库和一张book表,如下:
2、pom.xml中添加mysql jdbc驱动和mybatis相关依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
3、源码resources目录下新建generatorConfig.xml文件,作为mybatis代码生成器的配置文件,配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--mysql的jdbc驱动包所在路径-->
<classPathEntry
location="D:\apache-maven-3.6.3-bin\apache-maven-3.6.3\repository\mysql\mysql-connector-java\8.0.27\mysql-connector-java-8.0.27.jar"/>
<context id="tables" targetRuntime="MyBatis3">
<!--jdbc连接配置-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/book"
userId="root"
password="root">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--Model生成器,生成实体类以及对应的Example类-->
<javaModelGenerator targetPackage="org.example.domain" targetProject="src\main\java">
<property name="exampleTargetPackage" value="org.example.domain.example"/>
<property name="exampleTargetProject" value="src\main\java"/>
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--xxxMapper.xml映射文件配置-->
<sqlMapGenerator targetPackage="org.example.dao" targetProject="src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--数据访问层接口配置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="org.example.dao" targetProject="src\main\java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--数据表与生成的实体类的匹配关系-->
<table schema="book" tableName="book" domainObjectName="Book">
</table>
</context>
</generatorConfiguration>
4、在pom.xml中添加mybatis-generator-maven-plugin插件配置,然后执行插件,自动生成代码。
配置如下:
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<configuration>
<configurationFile>src\main\resources\generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
执行该maven插件后,工程目录下生成了相应的文件,如下图:
这里生成了四个文件:
1)作为数据访问入口的BookMapper接口,提供操作数据的方法。
2)Book实体类,与数据表信息对应。
3)BookExample类,其内部包含一个静态内部类Criteria,通过Criteria对象的方法组装sql语句的查询条件,然后包装在BookExample对象中,作为参数传递给BookMapper接口中的方法。
4)BookMapper.xml文件,封装了BookMapper接口中方法对应的sql语句映射;再结合BookExample中的查询条件,最终被解析成可执行的sql语句。
5、开始写代码,首先确保在application.yml配置文件中添加了数据库相关的配置,因为我们的服务需要访问数据库:
1)在启动类上添加@MapperScan(value = “org.example.dao”)注解,使spring能够沿着路径扫描并完成Bean的装配;
2)在controller层统计目录下添加service层,添加BookService接口及其实现类,用于处理book相关的业务逻辑;
其中根据bookId获取Book对象的方法如下:
3)在controller层添加对service层的调用:
到这里,整合mybatis进行数据库的操作就完成了,浏览器访问url可以看到book的信息已经返回:
完善和改进
1、BookController中添加 getAll() 方法查询多条数据并返回。
2、BookController中添加 updateBookMessage() 方法,处理post请求,实现图书信息的更新,返回操作成功或失败的消息。
完整的BookController代码:
package org.example.controller;
import org.example.domain.Book;
import org.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired
private BookService bookService;
/**
* 根据id获取图书
*
* @param id
* @return
*/
@GetMapping(value = "/getBook/{id}")
public Book getBookById(@PathVariable(value = "id") Integer id) {
Book book = bookService.getBookById(id);
return book;
}
/**
* 根据id、name获取图书
*
* @param id
* @param name
* @return
*/
@GetMapping(value = "/getBook")
public Book getBook(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name", required = false, defaultValue = "Offer来了") String name) {
Book book = bookService.getBook(id, name);
return book;
}
/**
* 查询所有图书
*
* @return
*/
@GetMapping(value = "/getAll")
public List<Book> getAll() {
List<Book> books = bookService.getAllBooks();
return books;
}
/**
* 更新单个图书信息
* @param book
* @return
*/
@PostMapping(value = "/update")
public boolean updateBookMessage(@RequestBody Book book) {
return bookService.updateBookMessage(book);
}
}
完整的BookServiceImpl代码:
package org.example.service.impl;
import org.example.dao.BookMapper;
import org.example.domain.Book;
import org.example.domain.example.BookExample;
import org.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookMapper;
@Override
public Book getBookById(Integer id) {
BookExample bookExample = new BookExample();
BookExample.Criteria criteria = bookExample.createCriteria();
criteria.andBookIdEqualTo(id);
List<Book> books = bookMapper.selectByExample(bookExample);
return books.isEmpty() ? null : books.get(0);
}
@Override
public Book getBook(Integer id, String name) {
BookExample bookExample = new BookExample();
BookExample.Criteria criteria = bookExample.createCriteria();
criteria.andBookIdEqualTo(id);
criteria.andBookNameEqualTo(name);
List<Book> books = bookMapper.selectByExample(bookExample);
return books.isEmpty() ? null : books.get(0);
}
@Override
public List<Book> getAllBooks() {
List<Book> books = new ArrayList<>();
books = bookMapper.selectByExample(null);
return books;
}
@Override
public boolean updateBookMessage(Book book) {
BookExample bookExample = new BookExample();
BookExample.Criteria criteria = bookExample.createCriteria();
criteria.andBookIdEqualTo(book.getBookId());
List<Book> books = bookMapper.selectByExample(bookExample);
if (books.isEmpty()) {
return false;
}
return bookMapper.updateByExample(book, bookExample) > 0;
}
}
3、将返回数据按照统一格式封装给调用方使用。
新建vo层,封装与前端交互的视图对象,编写ResultVo类,封装接口返回信息。这里使用lombok插件的@Data注解省去手动编写setter和getter方法的工作(需添加lombok依赖)。ResultVo类代码如下:
package org.example.vo;
import lombok.Data;
@Data
public class ResultVo {
private Object data;
private String msg;
private Integer code;
public ResultVo success() {
this.code = 0;
this.msg = "操作成功";
return this;
}
public ResultVo fail() {
this.code = -1;
this.msg = "操作失败";
return this;
}
public ResultVo data(Object data) {
this.data = data;
return this;
}
}
修改BookController类,在各方法中封装返回数据:
package org.example.controller;
import org.example.domain.Book;
import org.example.service.BookService;
import org.example.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired
private BookService bookService;
/**
* 根据id获取图书
*
* @param id
* @return
*/
@GetMapping(value = "/getBook/{id}")
public ResultVo getBookById(@PathVariable(value = "id") Integer id) {
Book book = bookService.getBookById(id);
return new ResultVo().success().data(book);
}
/**
* 根据id、name获取图书
*
* @param id
* @param name
* @return
*/
@GetMapping(value = "/getBook")
public ResultVo getBook(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name", required = false, defaultValue = "Offer来了") String name) {
Book book = bookService.getBook(id, name);
return new ResultVo().success().data(book);
}
/**
* 查询所有图书
*
* @return
*/
@GetMapping(value = "/getAll")
public ResultVo getAll() {
List<Book> books = bookService.getAllBooks();
return new ResultVo().success().data(books);
}
/**
* 更新单个图书信息
*
* @param book
* @return
*/
@PostMapping(value = "/update")
public ResultVo updateBookMessage(@RequestBody Book book) {
Boolean result = bookService.updateBookMessage(book);
ResultVo resultVo = new ResultVo().data(result);
return result ? resultVo.success() : resultVo.fail();
}
}
至此返回数据的简单封装完成,使用postman调试接口,返回信息展示如下:
不使用代码生成器怎么玩
MyBatis Generator为我们生成了 POJO类、Mapper接口、Example类 和 mapper.xml文件,仔细看看这些文件,只有Example类难以仿写,而且相比不是那么核心,而其他的三个文件都重要且可以手动实现。下面就以数据库中的 book_orders 表为基础手写这几个文件来整合mybatis功能。
POJO类
编写一个BookOrder类,关联订单和书籍。
package org.example.domain;
import lombok.Data;
@Data
public class BookOrder {
private Integer orderId;
private Float orderPrice;
private Integer bookId;
}
Mapper接口
package org.example.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.example.domain.BookOrder;
import java.util.List;
@Mapper
public interface BookOrderMapper {
@Select(value = "select * from book_orders where orderId = #{orderId}")
BookOrder getOrderById(Integer orderId);
@Select(value = "select * from book_orders inner join book on book_orders.bookId = book.book_id")
List<BookOrder> getAll();
@Update(value = "update book_orders set orderPrice = #{orderPrice}, bookId = #{bookId} where orderId = #{orderId}")
Integer updateOrder(BookOrder bookOrder);
}
这里直接使用@Select、@Update这类注解的话,就不需要再写mapper.xml文件了,因为在注解中已经表明了要执行的sql语句,但是这种硬编码的方式很low,不可取,后续可以直接注释掉,继续用xml文件去做映射。
mapper.xml映射文件
为了图方便,直接将之前代码生成器创建的mapper.xml文件复制一份,重命名为 BookOrderMapper.xml,删减掉 Example以及动态sql相关的语句,可以看到文件的三大核心标签:mapper标签,映射与之绑定的mapper接口;resultMap标签,映射查询结果对应的实体类;select、update等sql标签,映射具体的sql语句。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.dao.BookOrderMapper">
<resultMap id="BaseResultMap" type="org.example.domain.BookOrder">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
<id column="orderId" jdbcType="INTEGER" property="orderId" />
<result column="orderPrice" jdbcType="FLOAT" property="orderPrice" />
<result column="bookId" jdbcType="INTEGER" property="bookId" />
</resultMap>
<select id="getOrderById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
select *
from book_orders
where orderId = #{orderId,jdbcType=INTEGER}
</select>
<select id="getAll" resultMap="BaseResultMap">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
select *
from book_orders
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
delete from book_orders
where orderId = #{orderId,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="org.example.domain.BookOrder">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
insert into book_orders (orderId, orderPrice, bookId)
values (#{orderId,jdbcType=INTEGER}, #{orderPrice,jdbcType=FLOAT}, #{bookId,jdbcType=INTEGER})
</insert>
<update id="updateOrder" parameterType="org.example.domain.BookOrder">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Tue Feb 21 14:26:38 CST 2023.
-->
update book_orders
set orderPrice = #{orderPrice,jdbcType=FLOAT},
bookId = #{bookId,jdbcType=INTEGER}
where orderId = #{orderId,jdbcType=INTEGER}
</update>
</mapper>
到这里,功能就完成了,后续可以编写一个测试类,结合junit对进行单元测试:
package org.example;
import org.example.dao.BookOrderMapper;
import org.example.domain.BookOrder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class MyTest {
@Autowired
private BookOrderMapper bookOrderMapper;
@Test
public void test1() {
BookOrder bookOrder = bookOrderMapper.getOrderById(10001);
System.out.println(bookOrder);
}
@Test
public void test2() {
List<BookOrder> bookOrders = bookOrderMapper.getAll();
bookOrders.forEach(System.out::println);
}
@Test
public void test3() {
BookOrder bookOrder = new BookOrder();
bookOrder.setOrderId(10002);
bookOrder.setOrderPrice(99.9f);
bookOrder.setBookId(1002);
Integer res = bookOrderMapper.updateOrder(bookOrder);
System.out.println(res);
}
}
PageHelper分页查询
PageHelper是一个mybatis插件,通过实现mybatis的拦截器接口,对sql进行拦截并修改(拼接limit子句),实现分页查询。
使用步骤:
1、添加pagehelper依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
2、假设现在要分页查询所有BookOrder记录,由于mapper中已经有了查询所有记录的方法getAll,这里只在service层添加一个分页查询的功能即可。
@Override
public PageInfo<BookOrder> pageListOrder(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<BookOrder> list = bookOrderMapper.getAll();
PageInfo<BookOrder> pageInfo = new PageInfo<>(list);
return pageInfo;
}
到这里简单的分页功能就已经实现了,最后在测试类中测试pagehelper:
@Test
public void test4() {
PageInfo<BookOrder> pageInfo = bookOrderService.pageListOrder(2, 5);
List<BookOrder> list = pageInfo.getList();
list.forEach(System.out::println);
}
运行结果如下:
参考文档:
http://mybatis.org/generator/configreference/xmlconfig.html
http://www.tutorialspoint.com/mybatis/mybatis_annotations.htm