简单springboot项目搭建(二)-引入数据库(整合mybatis)


基于上一篇文章中的项目,有很多问题需要改进,才能更贴近实际的工作情况。比如图书管理系统中,图书是一个实体,具有很多属性,比如唯一编号、书名、描述信息、发版时间等,这些所有信息都需要我们进行存储和管理,所以需要引入数据库技术(此处使用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值