任务描述
任务要求
使用IDEA开发工具构建一个项目多模块工程。study-springboot-chapter03学习关于Springboot集成MyBatis plus知识点
- 基于study-springboot工程,新建一个Maven空项目,坐标groupId(com.cbitedu)、artifactId(study-springboot-chapter03),其他默认
- 继承study-springboot工程依赖
- 详细学习Mybatis-plus: baomidou.com/
任务收获
- 如何集成第三方持久化技术Mybatis-Plus
- 如何引入MySQL数据库依赖
- Spring Boot中整合MyBatis plus完成关系型数据库的增删改查操作
- 学会使用JUnit完成单元测试
- 掌握Mybatis-plus在项目中的应用
任务准备
环境要求
- JDK1.8+
- MySQL8.0.27+
- Maven 3.6.1+
- IDEA/VSCode
数据库准备
创建数据库platform,并创建用户表和初始化用户表数据。
/*
Navicat Premium Data Transfer
Source Server : localhost_3306
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : platform
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 20/08/2022 14:13:09
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_sys_userinfo
-- ----------------------------
DROP TABLE IF EXISTS t_sys_userinfo;
CREATE TABLE t_sys_userinfo (
user_id varchar(32) NOT NULL,
username varchar(50) NOT NULL COMMENT '用户名',
password varchar(100) DEFAULT NULL COMMENT '密码',
salt varchar(20) DEFAULT NULL COMMENT '盐',
email varchar(100) DEFAULT NULL COMMENT '邮箱',
mobile varchar(100) DEFAULT NULL COMMENT '手机号',
status tinyint DEFAULT NULL COMMENT '状态 0:禁用 1:正常',
create_user_id varchar(32) NULL DEFAULT NULL COMMENT '创建者ID',
create_time varchar(32) DEFAULT NULL COMMENT '创建时间',
userimg varchar(255) DEFAULT NULL COMMENT '用户头像',
zip varchar(10) DEFAULT NULL COMMENT '邮政编码',
sort_num int DEFAULT NULL COMMENT '排序号',
user_type varchar(10) DEFAULT NULL COMMENT '用户类型',
post_id varchar(32) DEFAULT NULL COMMENT '所属岗位',
sex varchar(4) DEFAULT NULL COMMENT '性别',
USER_REALNAME varchar(50) DEFAULT NULL COMMENT '真实姓名',
user_theme varchar(255) DEFAULT NULL COMMENT '用户选择皮肤',
user_card varchar(18) DEFAULT NULL COMMENT '身份证号码',
birthday varchar(20) DEFAULT NULL COMMENT '出生年月',
native_place varchar(255) DEFAULT NULL COMMENT '家庭住址',
nation varchar(255) DEFAULT NULL COMMENT '民族',
update_user_id varchar(32) DEFAULT NULL COMMENT '创建者ID',
update_time varchar(32) DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (user_id) USING BTREE,
UNIQUE INDEX username(username ASC) USING BTREE
) COMMENT = '系统用户' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_sys_userinfo
-- ----------------------------
INSERT INTO t_sys_userinfo VALUES ('1', 'admin', '9ec9750e709431dad22365cabc5c625482e574c74adaebba7dd02f1129e4ce1d', 'YzcmCZNvbXocrsz9dm8e', 'cc@bluefairy.com', '18929423839', 1, '1', '2016-11-11 11:11:11', NULL, NULL, NULL, NULL, NULL, NULL, '系统管理员', 'green', NULL, NULL, NULL, NULL, NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;
工程目录要求
新建一个空的Maven项目:study-springboot-chapter03
MyBatis Plus 介绍
MyBatis Plus 是国内人员开发的 MyBatis 增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
MyBatis Plus 的核心功能有:支持通用的 CRUD、代码生成器与条件构造器。
通用 CRUD:定义好 Mapper 接口后,只需要继承 BaseMapper<T> 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件条件构造器:通过 EntityWrapper<T> (实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的 SQL代码生成器:支持一系列的策略配置与全局配置,比 MyBatis 的代码生成更好用。
任务实施
如何在Spring Boot中整合MyBatis完成关系型数据库的增删改查操作。
模块:study-springboot-chapter03
任务实施步骤如下:
1、基于study-springboot-chapter03模块引入mybatis-plus
2、study-springboot-chapter03模块的pom.xml中引入MyBatis的Starter以及MySQL Connector依赖和lombok第三方框架,具体如下:
如果遇到下载包Jar包不成功:可以删除本地仓库下所有的第三方包或者搜索lastupdate字样,删除后再重新下载
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3、新建application.yml配置mysql的连接配置和mybatis-plus
MyBatisPlus 默认扫描的是类路径下的 resources/mapper目录,所以我们直接将 Mapper 配置文件放在该目录下就没有任何问题,可以省略配置,可如果不是这个目录,我们就需要进行配置
mybatis-plus:
mapper-locations: classpath:xml/*.xml
#服务配置
server:
port: 81
# #设置日志相关打印sql 语句
logging:
level:
top.lrshuai.plus.springbootmybatisplus.test.mapper.ShipmentMapper: debug
#关闭运行日志图标(banner)
spring:
datasource:
url: jdbc:mysql://localhost:3306/platform?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
#mybatis-plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
新建日志文件logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台打印日志的相关配置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/mybatisplus_info.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/mybatisplus_info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/mybatisplus_error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/mybatisplus_error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="file" />
<appender-ref ref="error" />
</root>
</configuration>
4、完成任务准备中的数据库工作,创建表和初始化数据
分别在com.cbitedu.springboot下创建软件包
- 实体类:entity
- 接口层:mapper
- 服务层:service
- 控制台:controller
5、创建t_sys_userinfo表的映射对象TsysUserinfo( com.cbitedu.springboot.entity )
package com.cbitedu.springboot.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName(value = "t_sys_userinfo") //指明数据库表名
public class TsysUserinfo {
/**
* 创建人
*/
private String createUserId;
/**
* 创建时间
*/
private String createTime;
/**
* 修改人
*/
private String updateUserId;
/**
* 修改时间
*/
private String updateTime;
/**
* 用户ID
*/
private String userId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 盐
*/
private String salt;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String mobile;
/**
* 状态 0:禁用 1:正常
*/
private String status;
/**
* 所属机构
*/
private String orgId;
/**
* 用户真实姓名
*/
private String userRealname;
/**
* 所属机构
*/
private String orgnames;
/**
* 用户头像
*/
private String userimg;
/**
* 邮政编码
*/
private String zip;
/**
* 排序号
*/
private Integer sortNum;
/**
* 用户类型
*/
private String userType;
/**
* 性别
*/
private String sex;
private String userTheme;
/**
* 身份证号码 db_column: user_card
*/
private String userCard;
/**
* 出生年月 db_column: birthday
*/
private String birthday;
/**
* 家庭住址 db_column: native_place
*/
private String nativePlace;
/**
* 民族 db_column: nation
*/
private String nation;
}
6、编写Mapper的XML文件:TsysUserinfoMapper.xml(resources/mapper目录)
<?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.cbitedu.springboot.mybatis.mappers.TsysUserinfoMapper">
<select id="selectObjectById" resultType="com.cbitedu.springboot.entity.TsysUserinfo">
select * from
t_sys_userinfo where user_id = #{userid}
</select>
<select id="getAllUserinfo" resultType="com.cbitedu.springboot.entity.TsysUserinfo">
select u.* from
t_sys_userinfo u
</select>
<insert id="insertTsysUserinfo" parameterType="com.cbitedu.springboot.entity.TsysUserinfo">
insert into t_sys_userinfo
(
user_id,
user_realname,
username,
password,
salt,
email,
mobile,
status,
userimg,
zip,
sort_num,
user_type,
sex,
user_theme,
user_card,
birthday,
native_place,
nation,
create_user_id,
create_time,
update_user_id,
update_time
)
values
(
#{userId},
#{userRealname},
#{username},
#{password},
#{salt},
#{email},
#{mobile},
#{status},
#{userimg},
#{zip},
#{sortNum},
#{userType},
#{sex},
#{userTheme},
#{userCard},
#{birthday},
#{nativePlace},
#{nation},
#{createUserId,jdbcType=VARCHAR},
#{createTime,jdbcType=VARCHAR},
#{updateUserId,jdbcType=VARCHAR},
#{updateTime,jdbcType=VARCHAR}
)
</insert>
<update id="update" parameterType="com.cbitedu.springboot.entity.TsysUserinfo">
update t_sys_userinfo
<set>
<if test="userRealname != null">user_realname = #{userRealname}, </if>
<if test="username != null">username = #{username}, </if>
<if test="email != null">email = #{email}, </if>
<if test="mobile != null">mobile = #{mobile}, </if>
<if test="status != null">status = #{status}, </if>
<if test="userimg != null">userimg = #{userimg},</if>
<if test="zip != null">zip = #{zip},</if>
<if test="sortNum != null">sort_num = #{sortNum},</if>
<if test="userType != null">user_type = #{userType},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="userTheme != null and userTheme.trim() != ''">
user_theme = #{userTheme,jdbcType=VARCHAR},
</if>
<if test="userCard != null and userCard.trim() != ''">
user_card = #{userCard,jdbcType=VARCHAR},
</if>
<if test="birthday != null and birthday.trim() != ''">
birthday = #{birthday,jdbcType=VARCHAR},
</if>
<if test="nativePlace != null and nativePlace.trim() != ''">
native_place = #{nativePlace,jdbcType=VARCHAR},
</if>
<if test="createUserId != null and createUserId.trim() != ''">
create_user_id = #{createUserId,jdbcType=VARCHAR},
</if>
<if test="createTime != null and createTime.trim() != ''">
create_time = #{createTime,jdbcType=VARCHAR},
</if>
<if test="updateUserId != null and updateUserId.trim() != ''">
update_user_id = #{updateUserId,jdbcType=VARCHAR},
</if>
<if test="updateTime != null and updateTime.trim() != ''">
update_time = #{updateTime,jdbcType=VARCHAR},
</if>
<if test="nation != null and nation.trim() != ''">
nation = #{nation,jdbcType=VARCHAR}
</if>
</set>
where user_id = #{userId}
</update>
<delete id="deleteUser">
delete from t_sys_userinfo where user_id= #{userid}
</delete>
<delete id="deleteBatch">
delete from t_sys_userinfo where user_id in
<foreach item="userId" collection="array" open="(" separator=","
close=")">
#{userId}
</foreach>
</delete>
</mapper>
7、创建接口:TsysUserinfoMapper。
注意:使用mybatis-plus的公共接口,必须继承BaseMapper,提供了一系列常用的接口方法
package com.cbitedu.springboot.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cbitedu.springboot.entity.TsysUserinfo;
import org.springframework.stereotype.Component;
//未来使用mybatis-plus的公共接口,必须继承BaseMapper
@Component
public interface TsysUserinfoMapper extends BaseMapper<TsysUserinfo> {
}
8、创建服务层接口ITsysUserinfoService 和实现类TsysUserinfoServiceImpl
注意:ITsysUserinfoService继承IService接口
package com.cbitedu.springboot.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cbitedu.springboot.entity.TsysUserinfo;
import java.util.List;
public interface ITsysUserinfoService extends IService<TsysUserinfo> {
List<TsysUserinfo> getAllUserinfo();
public void insertTsysUserinfo(TsysUserinfo tsysUserinfo);
}
ITsysUserinfoService接口实现类TsysUserinfoServiceImpl
package com.cbitedu.springboot.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cbitedu.springboot.entity.TsysUserinfo;
import com.cbitedu.springboot.mapper.TsysUserinfoMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class TsysUserinfoServiceImpl extends ServiceImpl<TsysUserinfoMapper, TsysUserinfo> implements ITsysUserinfoService {
@Resource
private TsysUserinfoMapper userMapper;
@Override
public List<TsysUserinfo> getAllUserinfo() {
//使用mybatis-plus公共查询接口完成列表查询
return userMapper.selectList(null);
}
public void insertTsysUserinfo(TsysUserinfo tsysUserinfo) {
userMapper.insert(tsysUserinfo);
}
}
8、创建Spring Boot主类,并扫描到Mybatis接口
package com.cbitedu.springboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.cbitedu.springboot.mappers")
public class StudySpringbootChapter03Application {
public static void main(String[] args) {
SpringApplication.run(StudySpringbootChapter03Application.class, args);
}
}
9、创建单元测试:MybatisTest,测试新增、修改、删除业务操作。
package com.cbitedu.springboot.web;
import com.cbitedu.springboot.entity.TsysUserinfo;
import com.cbitedu.springboot.service.ITsysUserinfoService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisPlusTest {
@Test
public void contextLoads() {
}
private static final Logger logger = LoggerFactory.getLogger(MybatisPlusTest.class);
@Autowired
ITsysUserinfoService iTsysUserinfoService;
/**
* 测试新增用户信息
*/
@Test
public void testInsert() {
TsysUserinfo tsysUserinfo = new TsysUserinfo();
tsysUserinfo.setUserId("9600");
tsysUserinfo.setUsername("Mybatis003");
tsysUserinfo.setPassword("2222");
iTsysUserinfoService.insertTsysUserinfo(tsysUserinfo);
}
/**
* 测试新增用户信息
*/
@Test
public void testquery() {
iTsysUserinfoService.getAllUserinfo();
logger.info("查询出来的用户列表大小" + iTsysUserinfoService.getAllUserinfo());
}
}
实验实训
- 独立学习Mybatis-plus官方功能:baomidou.com/
- 完成分页功能的实现
- 并发情况下的悲观锁和乐观锁Mybatis-plus解决方案
当程序中出现并发访问时,就需要保证数据的一致性。以商品系统为例,现在有两个管理员均想对同一件售价为 100 元的商品进行修改,A 管理员正准备将商品售价改为 150 元,但此时出现了网络问题,导致 A 管理员的操作陷入了等待状态;此时 B 管理员也进行修改,将商品售价改为了 200 元,修改完成后 B 管理员退出了系统,此时 A 管理员的操作也生效了,这样便使得 A 管理员的操作直接覆盖了 B 管理员的操作,B 管理员后续再进行查询时会发现商品售价变为了 150 元,这样的情况是绝对不允许发生的。
要想解决这一问题,可以给数据表加锁,常见的方式有两种:
- 乐观锁
- 悲观锁
悲观锁认为并发情况一定会发生,所以在某条数据被修改时,为了避免其它人修改,会直接对数据表进行加锁,它依靠的是数据库本身提供的锁机制(表锁、行锁、读锁、写锁)。
而乐观锁则相反,它认为数据产生冲突的情况一般不会发生,所以在修改数据的时候并不会对数据表进行加锁的操作,而是在提交数据时进行校验,判断提交上来的数据是否会发生冲突,如果发生冲突,则提示用户重新进行操作,一般的实现方式为 设置版本号字段 。
就以商品售价为例,在该表中设置一个版本号字段,让其初始为 1,此时 A 管理员和 B 管理员同时需要修改售价,它们会先读取到数据表中的内容,此时两个管理员读取到的版本号都为 1,此时 B 管理员的操作先生效了,它就会将当前数据表中对应数据的版本号与最开始读取到的版本号作一个比对,发现没有变化,于是修改就生效了,此时版本号加 1。
而 A 管理员马上也提交了修改操作,但是此时的版本号为 2,与最开始读取到的版本号并不对应,这就说明数据发生了冲突,此时应该提示 A 管理员操作失败,并让 A 管理员重新查询一次数据。