7

【项目实战】从零到一搭建Spring Boot整合Mybatis-plus - 大象只为你

 1 year ago
source link: https://www.cnblogs.com/huangxiufen/p/17061756.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2023年想搭建一套属于自己的框架,做一个属于自己想法的项目。这些年工作中一直用公司已有的框架,以前有跟着学习视频搭建过,但自己真正动手搭建时发现问题还是很多,比如没有引入Mybatis-plus包之前,项目api test是成功的,引入Mybatis-plus包后就一直启动不成功,而且异常信息也不抛出,后引入actuator应用健康监测才抛异常信息排查解决。我会下面文中说明为什么引入这个pom作用是什么,pom引入的每个包都有其作用,而不是照搬别人的框架过来,引入不必要的包。

看该文章前需要了解maven pom结构,idea创建一个项目的步骤,spring boot知识,往下阅读默认都具备了。

环境说明:idea2022.3 ,jdk17 ,maven 3.6.3,mysql-8.0.31

一、创建一个maven空项目

项目创建步骤不截图了,默认大家懂了。创建一个空maven项目目的是作为父级引入公用的一些,一般企业级的项目都是这样的结构,当然如果想简单一点,该步骤也是可以省略掉的。

创建出来把一些不要的包,文件都删除掉,目录结构如下图

338385-20230119163736001-106199556.png

1、确定spring boot版本号

因为我后面是要实现微服务框架项目,所以在选择Spring Boot版本号是与Spring Cloud是对应的。(最开始我是选择Spring Boot最新版本号3.0.0,但发现在整合Mybatis-Plus 3.5.2出现很多问题,主要是spring boot 3.0自动注入方式改变,原因说明可参考博客:Spring Boot3.0升级,踩坑之旅,附解决方案 :https://www.cnblogs.com/wayn111/p/16950025.html)

访问spring官网https://spring.io,选择projects下 Spring Cloud 右侧Learn ,目前最新版本是2022.0.0GA(上面已有说明不选择最新版),往下找第一个GA版本2021.0.5。

338385-20230119163803037-1327079986.png
338385-20230119163809288-285506745.png

2 、pom整理

关键Spring Boot pom定义如下,完整pom定义详见示例源码。

方便后面Api定义的声明,增加引入Swagger

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.13</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>  
    <!-- Spring Cloud -->
    <spring.cloud.version>2021.0.5</spring.cloud.version>
    <!-- Spring Boot -->
    <spring.boot.version>2.6.13</spring.boot.version>   
    <!-- Swagger -->
    <knife4j.spring.boot.version>2.0.8</knife4j.spring.boot.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring.cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        
        <!--Swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-micro-spring-boot-starter</artifactId>
            <version>${knife4j.spring.boot.version}</version>
        </dependency>

    </dependencies>
</dependencyManagement>

二、添加子模块demo-service

338385-20230119163826244-734237507.png

1、Spring Boot Api Test

关键pom引入,就可以进行api test,完整pom定义详见示例源码。

添加spring-boot-starter-web是web应用需要的包,Controller等相关,spring-boot-starter-actuator 是Spring Boot应用健康监测,如果应用有异常可以捕获到,供我们排查。

<dependencies>        
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--actuator-应用健康监测 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    <!--Swagger-->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-micro-spring-boot-starter</artifactId>
    </dependency>

</dependencies>

代码层面自己手动加package: com.elephant.demo , 创建启动类DemoApplication,代码如下:

package com.elephant.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author xiufen.huang
 * @description:
 * @date 2023-01-08-18:38
 */
@Slf4j
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        log.info("========================= elephant-demo-启动成功 ==========================");
    }

}

点击试运行,从控制台的日志来看已成功。端口暂时没有设置默认8080。

338385-20230119163931507-1434371900.png

添加application.yml,设置端口8071

添加测试Controller类:TestController,代码如下:

package com.elephant.demo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author xiufen.huang
 * @description:
 * @date 2023-01-08-18:47
 */
@Api(value = "TestController", tags = "测试Controller")
@Slf4j
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {

    @ApiOperation(value = "测试接口")
    @GetMapping("/index")
    public String test() {
        return "ok";
    }

}

api test访问 http://127.0.0.1:8071/test/index ,响应成功ok,

338385-20230119163952976-1844422903.png

2、整合Mybatis-plus

2.1、正常的pom引入说明

数据库采用mysql,mybatis-plus选择版本号3.5.2 ,只需要引入两个包即可。

<properties>  
    <mybatis.plus.version>3.5.2</mybatis.plus.version>
    <mysql.connector.version>8.0.31</mysql.connector.version> 
</properties>

<!--Mybatis-plus-start-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis.plus.version}</version>
</dependency>
<!--Mybatis-plus-end-->

<!-- MySql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.connector.version}</version>
</dependency>

2.2、正常的yaml配置

mybatis-plus配置,mapper.xml,实体扫描,打印sql

#mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:com/elephant/demo/**/mapper/*Mapper.xml
  #实体扫描,多个package用逗号或者分号分隔
  type-aliases-package: com.elephant.demo.**.entity
  configuration:
    # 驼峰转换 从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 打印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

数据库连接配置

spring:
  application:
    # 应用名称
    name: elephant-demo
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 换成自己的配置
    url: jdbc:mysql://127.0.0.1:3306/test
    username: root
    password: 123456

2.3、测试的建表脚本和数据

我的测试表是订单表,仅针对功能测试使用,不一定按我的,我把建表脚本提供和初始化脚本提供出来。方便如果下载我的demo代码可以初始化后,修改下配置即可运行。在resources/ sql下。

# 创建表语句
CREATE TABLE `ft_order` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `create_user` varchar(32) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user` varchar(32) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `status` int DEFAULT NULL COMMENT '业务状态: 0-正常, 1-已删除',
  `is_deleted` bit(1) DEFAULT b'0' COMMENT '是否删除',
  `order_no` varchar(50) DEFAULT NULL COMMENT '订单编号',
  `customer_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
  `customer_email` varchar(100) DEFAULT NULL COMMENT '客户邮箱',
  `product_status` int DEFAULT NULL COMMENT '货品状态: 1-备车中,2-出口手续办理中,3-转移待出口(手续办理完成),4-报关完成,5-车辆达到指定港口,6-运输中,7-已抵达,8-确认收货',
  `remark` varchar(256) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_customer_name` (`customer_name`) USING BTREE,
  KEY `idx_order_no_email_name` (`order_no`,`customer_email`,`customer_name`) USING BTREE COMMENT 'PC后台查询组合索引',
  KEY `idx_customer_email_name` (`customer_email`,`customer_name`) USING BTREE
)COMMENT='外贸订单信息';

测试初始化数据

INSERT INTO `ft_order`(`id`, `create_user`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`, `order_no`, `customer_name`, `customer_email`, `product_status`, `remark`) VALUES ('1001', NULL, '2023-01-03 15:18:46', NULL, '2023-01-03 15:18:49', 0, b'0', 'TEST20230103001', 'Jack', '[email protected]', NULL, NULL);
INSERT INTO `ft_order`(`id`, `create_user`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`, `order_no`, `customer_name`, `customer_email`, `product_status`, `remark`) VALUES ('1002', NULL, '2023-01-04 16:18:49', NULL, '2023-01-04 16:18:49', 0, b'0', 'TEST20230104001', 'Jack', '[email protected]', NULL, NULL);
INSERT INTO `ft_order`(`id`, `create_user`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`, `order_no`, `customer_name`, `customer_email`, `product_status`, `remark`) VALUES ('1003', NULL, '2023-01-04 17:18:49', NULL, '2023-01-04 17:18:49', 0, b'0', 'TEST20230104002', 'Jack', '[email protected]', NULL, NULL);
INSERT INTO `ft_order`(`id`, `create_user`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`, `order_no`, `customer_name`, `customer_email`, `product_status`, `remark`) VALUES ('1004', NULL, '2023-01-04 18:18:49', NULL, '2023-01-04 18:18:49', 0, b'0', 'TEST20230104003', 'Jack', '[email protected]', NULL, NULL);
INSERT INTO `ft_order`(`id`, `create_user`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`, `order_no`, `customer_name`, `customer_email`, `product_status`, `remark`) VALUES ('1005', NULL, '2023-01-04 19:18:49', NULL, '2023-01-04 19:18:49', 0, b'0', 'TEST20230104004', 'Jack', '[email protected]', NULL, NULL);

2.4、动态数据源的pom引入说明

如果想采用动态数据源,再引入这个pom包

<properties>  
    <mybatis.plus.dynamic.version>3.5.2</mybatis.plus.dynamic.version>
</properties>

<!-- 动态数据源-start -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>${mybatis.plus.dynamic.version}</version>
</dependency>
<!-- 动态数据源-end -->

2.5、动态数据源的yaml配置

# 动态数据源
# pom 引入 dynamic-datasource-spring-boot-starter
spring:
  application:
    # 应用名称
    name: elephant-demo
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test
          username: root
          password: 123456
        slave:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test
          username: root
          password: 123456

3、示例代码说明

由于代码比较多不一一贴出来,主要代码贴出来做说明。代码结构做一下说明,是采用4层结构,多了一层业务实现层是为了避免后面业务复杂了,出现循环依赖的情况。具体是用3层还是4层可以按需。

338385-20230119164022116-896642883.png

本次示例给出3个:根据主键查询,分页列表查询,保存订单数据。

3.1.Dao层代码

接口继承IService ,实现类继承 ServiceImpl<OrderMapper, Order>。IOrderRepository 代码如下:

/**
 * @author xiufen.huang
 * @description:
 * @date 2022-12-21-15:38
 */
public interface IOrderRepository extends IService<Order> {
    
        /**
     * 获取订单信息
     * @param orderId 订单id
     * @return com.elephant.demo.model.entity.Order
     * @author xiufen.huang
     * @date 2023-01-19 11:01
     */
    Order getById(String orderId);

    /**
     * 订单分页列表
     * @param param 查询参数
     * @return com.baomidou.mybatisplus.core.metadata.IPage<com.elephant.demo.model.vo.OrderPageVo>
     * @author xiufen.huang
     * @date 2023-01-19 11:01
     */
    IPage<OrderPageVo> getOrderPage(OrderPageParam param);

    /**
     * 保存订单数据
     * @param param
     * @return java.lang.Boolean
     * @author xiufen.huang
     * @date 2022-12-16 15:50
     */
    Boolean saveOrder(OrderCreateParam param);

}    

OrderRepositoryImpl 代码如下:

@Slf4j
@Service
public class OrderRepositoryImpl extends ServiceImpl<OrderMapper, Order>  implements IOrderRepository {
    // 后面分开讲解
}

3.2.单个查询

比如根据主键查询,可以用Mybatis提供封装好的方法selectById() 。具体代码实现如下

@Override
public Order getById(String orderId) {
    Order order = this.baseMapper.selectById(orderId);
    return order;
}

Service,Controller的代码只需要调用一下即可,实际业务场景再根据需要做一些处理。

3.3.订单分页列表

实现思路:构造分页参数,具体查询实现在Mapper.xml用原生的sql结合动态参数。比如我的场景:根据关键词(订单号/客户名称/客户邮箱)查询订单数据。

OrderMapper 代码如下

public interface OrderMapper extends BaseMapper<Order> {
    IPage<OrderPageVo> getOrderPage(Page page, @Param("param") OrderPageParam param);
}

OrderMapper .xml代码如下

<?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="com.elephant.demo.repository.mapper.OrderMapper">

    <select id="getOrderPage" resultType="com.elephant.demo.model.vo.OrderPageVo">
        select fto.id,
        fto.create_time,
        fto.update_time,
        fto.`status`,
        fto.order_no,
        fto.customer_name,
        fto.customer_email,
        fto.product_status
        from ft_order fto
        where fto.is_deleted=0
        <if test="param.searchWord != null and param.searchWord !=''">
            and (
            fto.order_no like concat('%',#{param.searchWord},'%')
            or fto.customer_email like concat('%',#{param.searchWord},'%')
            or fto.customer_name like concat('%',#{param.searchWord},'%')
            )
        </if>
        order by fto.create_time desc
    </select>

</mapper>

OrderRepositoryImpl的代码实现就比较简单了,调整下即可,代码如下

@Override
public IPage<OrderPageVo> getOrderPage(OrderPageParam param) {
    Page page = new Page(param.getPage().getCurrent(), param.getPage().getSize());
    return baseMapper.getOrderPage(page, param);
}

3.4.保存订单数据

实现思路:把提交参数做业务校验后,把对应的字段赋给实体类,然后调用this.save(T entity)方法进行保存,id会自动赋值。OrderRepositoryImpl的代码实现如下:

@Override
public Boolean saveOrder(OrderCreateParam param) {
    // 参数赋值
    Order order = new Order();
    order.setOrderNo(param.getOrderNo());
    order.setCustomerName(param.getCustomerName());
    order.setCustomerEmail(param.getCustomerEmail());
    order.setCreateTime(new Date());
    order.setUpdateTime(new Date());
    // 订单创建时,默认是0-正常
    order.setStatus(0);
    // 持久化数据
    return this.save(order);
}

4.添加日志打印

实现说明:在src/main/resources下添加日志配置logback-spring.xml,然后application.yml添加配置。

logback-spring.xml各项说明可以阅读博客:https://blog.csdn.net/weixin_43790613/article/details/109428318

4.1.logback-spring.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="true">
    <!--
     参考博客:https://blog.csdn.net/weixin_43790613/article/details/109428318
     1. scan:程序运行时配置文件被修改,是否重新加载。true=重新加载;false=不重新加载;默认为true;
     2. scanPeriod:监测配置文件被修改的时间间隔,scan属性必须设置为true才可生效;默认为1分钟,默认单位是毫秒;
     3. debug:是否打印logback程序运行的日志信息。true=打印;false=不打印;默认为false;
    -->
    <contextName>logback</contextName>
    <!-- 路径变量,当前项目所在目录下 -->
    <property name="log.path" value="/log/demo/logback.log" />
    <!-- 日志格式变量 -->
    <property name="logPattern" value="%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36}  %file:%line - %msg%n" />
    <!--
    1. %d{HH:mm:ss.SSS} 显示的时间
    2. [%thread]打印线程号,log4j2使用%t]也可以
    3. %-5level日志级别,并且使用5个字符靠左对齐
    4. %logger{36}——日志输出者的名字,即类的类名
    5. %file	打印类名,也可用%class,打印的全限定类名
    6. %line	打印日志所在代码行数
    7. %msg——日志消息
    8. %n——平台的换行符-->
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>ERROR</level>
         </filter>-->
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <!--输出到文件路径一种滚动策略:根据时间制定日志文件的滚动策略,如:按天、按小时、按分钟生成日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间 -->
            <fileNamePattern>${log.path}/%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <!-- 日志在磁盘上保留天数 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 另一种滚动策略:表示根据日志文件大小,超过制定大小会触发日志滚动; -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>5MB</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
            <!--
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                 <level>ERROR</level>
            </filter>
            ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~
             -->
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 把日志异步输出到磁盘文件中,避免每次都进行磁盘IO操作 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>10000</queueSize>
        <appender-ref ref="file" />
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="ASYNC" />
    </root>

</configuration>

4.2.application.yml添加配置

# 日志配置
logging:
  config: classpath:logback-spring.xml

三、源码地址

上面步骤说明还是不够全面,如果是自己刚开始搭建,可能还是没什么头绪。

因此我把demo分享出来。https://gitee.com/wuqixiufen2/elephant-demo

338385-20230119164110468-1621118067.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK