前置知识
Java基础
MySQL JDBC
JavaWeb servlet jsp cookie session filter listener
SSM Spring SpringMVC mybatis
Java高级 Redis Nginx Maven Git版本控制 Springboot
背景
在线教育是以网络为介质的教育方式
优势:随时随地都可以学习
在很多领域都很流行 母婴 学前教育 少儿教育 中小学生 高校学生 留学 执业考试 职业技能 成人外语 个人兴趣
商业模式
B2C Business Customer
两个角色 管理员(增删改)和普通用户(查)
在线教育使用这种模式 核心模块:课程模块
B2B2C
电商使用这种模式
京东
普通用户可以买京东自营或普通商家的东西
功能模块
系统后台 管理员使用
讲师管理模块
课程分类管理模块
后端 java/python/php...
前端 JavaScript/vue/react...
课程管理模块
课程 课程描述 章节 视频
统计分析模块
统计某门课程的播放量 购买量,用图表展示
订单管理
订单状态
订单量
banner管理
轮播图/幻灯片 从数据库中取出来展示
权限管理
用户权限
系统前台 普通用户使用
首页数据显示
讲师列表和详情
课程列表和详情
视频在线播放
评论
登录注册
微信扫码登录
微信扫码支付
技术点 前后端分离
后端
spring boot、spring cloud、MybatisPlus、spring security
redis maven easyExcel jwt OAuth2
前端
vue element-ui axios node.js
其它
阿里云oss存储服务、视频点播服务、短信服务
微信登录和支付
docker
git
jenkins
MybatisPlus 操作数据库
Mybatis的增强工具,进一步封装代码,简化开发
官网:http://mp.baomidou.com/ 教程/guide
入门
数据库准备
创建数据库mybatis_plus
创建表并插入数据
CREATE DATABASE mybatis_plus;
USE mybatis_plus;
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
创建项目
idea使用Spring Initializr创建一个springboot项目
可以修改url为https://start.aliyun.com/ 否则可能会连接网络超时
视频中将pomxml中的springboot的版本改为了2.2.1
引入相关依赖
MybatisPlus的依赖是baomidou的
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
< exclusions>
< exclusion>
< groupId> org.junit.vintage</ groupId>
< artifactId> junit-vintage-engine</ artifactId>
</ exclusion>
</ exclusions>
</ dependency>
< dependency>
< groupId> com.baomidou</ groupId>
< artifactId> mybatis-plus-boot-starter</ artifactId>
< version> 3.0.5</ version>
</ dependency>
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
</ dependency>
< dependency>
< groupId> org.projectlombok</ groupId>
< artifactId> lombok</ artifactId>
</ dependency>
安装lombok插件
setting-plugin-搜索lombok
网络不好可以离线安装 在GitHub下载后setting-plugin-install from disk
配置src/main/resources/application.properties
springboot可以使用properties或yml文件作为配置文件
注意springboot版本 2.1之后driver-class-name要加上cj url要加上时区
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
编写代码
创建实体包entity和mapper
创建User类
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
加上Data注解可以不用写get和set方法以及无参构造方法 可以在idea左下角的Structure中看到
创建UserMapper接口
mybatis中接口对应了一个xml文件,但MybatisPlus进行了封装,不需要自己创建xml文件了,只需要继承接口BaseMapper< T>
继承之后就可以调用已经封装好的增删改查方法
复杂的sql还是需要使用xml文件来实现
在启动类上加上注解MapperScan(“mapper包的地址”)
因为启动时会找接口对应的实现类对象,如果没有实现类就会找不到报错
加上后会扫描mapper包
在test包中的MpdemoApplicationTests进行测试
一般是controller调用service,service调用mapper
所以需要在controller中注入service,service中注入mapper
这里测试直接在类MpdemoApplicationTests中注入mapper
@Autowired
private UserMapper userMapper;
这里会报错,因为UserMapper是一个接口,没有对应的实现类,找不到它的实现类对象
不解决这个错误也可以运行
要解决可以在UserMapper上加上任意一个注解Component/Service/Repository都可以
调用userMapper的方法selectList查询User表中的所有数据
//查询User表所有数据
@Test
void testFindAll() {
List< User> users = userMapper.selectList(null);
System.out.println(users);
}
如果报错Access denied for user 'root'@'localhost'(Using Password YES)是密码错误
查看sql输出日志
在application.properties中加上
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
就可以在输出窗口中看到调用方法具体的执行过程,包括生成的sql语句,参数以及返回结果
总结
写实体类User和mapper类UserMapper(继承BeanMapper< User> ),调用UserMapper封装好的方法
可能遇到的错误:application.properties文件前面的标记不是叶子形状
解决:将文件复制到target/classes下
mp实现添加操作
User user = new User();
//这里不设置id,其是主键,mp会自动生成一个19位的唯一的id
user.setName("张三");
user.setAge(22);
user.setEmail("zhangsan@qq.com");
int insert = userMapper.insert(user);
System.out.println("insert" + insert);//影响的行数
主键生成策略 https://www.cnblogs.com/haoxinyue/p/5208136.html
自动增长 AUTO_INCREMENT
缺点:分表时不方便,需要得到上一张表的最后一个id,将其+1才能生成新表
分表指的是一张表中数据过于庞大,分成几张表
UUID
生成随机的唯一的一个值
b0c245ff-f915-4790-8b64-a5b9014bb6bd
缺点:排序不方便
优点:不需要关注上一张表的数据
redis生成id
主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现
使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
mp自带策略
使用Twitter的snowflake算法生成19位的唯一的值
使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0
数据中心,如北京是001,天津是002
机器ID,如北京数据中心有100台机器,001-100
配置策略
不配置策略默认mp自带策略,会自动识别属性类型来设置,数字类型默认ID_WORKER,字符串类型默认ID_WORKER_STR
配置策略是在对应属性上加注解@TableId(type = IdType.AUTO)
idType后可以加
AUTO自增长
ID_WORKER mp自带策略 数字类型
ID_WORKER_STR mp自带策略 字符串类型
INPUT需要设置值 NONE需要设置值
UUID
mp实现修改操作
User user = new User();
user.setId(2L);
user.setAge(120);
int i = userMapper.updateById(user);//修改user表中id=2的用户的年龄为100
System.out.println(i);
自动填充
在user表中加上两个字段create_time和update_time,类型都是datetime
在user类中加上两个属性createTime和updateTime,类型都是Date
注意字段和对应属性的命名规范,下划线转驼峰
加上字段和属性之后,再增加和修改记录,如果不设置时间,时间默认是null
自动填充就是不手动设置创建和修改时间,即setCreateTime和setUpdateTime,而是使用mp的方式来设置时间
在user类中要自动填充的属性上添加注解@TableField(fill=FieldFill.INSERT/UPDATE/INSERT_UPDATE)
//添加数据时自动填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//添加和修改数据时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
创建类,实现接口MetaObjectHandler,复写方法insertFill和updateFill,
注意加上注解Component/Service/Repository 表示交给spring进行管理,否则自动填充会失效
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//使用mp实现添加操作就会执行这个方法
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
//使用mp实现修改操作就会执行这个方法
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
其中的metaObject是元数据对象,如表名、表字段
注意createTime在添加数据时才会填充,所以它的注解是INSERT,且只在insertFill方法中才进行设置
而updateTime在添加和修改数据时都会填充,所以它的注解是INSERT_UPDATE,且在insertFill和updateFill方法中都进行了设置
乐观锁
概念
主要解决丢失更新问题
事务是一组逻辑操作的基本单元,要么都成功,要么都失败
不考虑事务的隔离性,即并发情况下,会产生脏读、不可重复的、幻读的读问题以及丢失更新的写问题
例子
张三和李四都去修改id为1的用户的工资,开始是500
张三和李四都开启事务
张三将工资改为1000,李四将工资改为300
事务的提交都先后,张三先提交事务,改为了1000,李四又改为了300
之后张三查询工资发现是300,这就是丢失更新现象
多个人同时修改同一条记录,后面的会覆盖前面的,即丢失更新
解决
悲观锁
张三操作数据时,所有人都不能操作,张三操作完后,其他人才可以操作
串行操作
乐观锁
在表中加上字段version,默认为1
张三和李四要操作数据,先获取version,都为1
张三先提交事务,比较先前获取数据的version和当前数据库中的version是否一样,一样才可以修改,修改后将version+1
李四再提交事务,发现自己先前获取的version和当前数据库中的version不一样,不能修改
例子
抢火车票
当前打开12306的人都可以看到票只剩了一张,也都可以抢到,但只有一个人支付成功
使用mp实现乐观锁
表中加字段version INT
实体类中加属性Integer version,并给属性加上注解@Version
创建配置类MybatisPlusConfig,建议将所有配置操作,如关于插件的配置都加在该类中
配置乐观锁插件
@EnableTransactionManagement
@Configuration
@MapperScan("com.xm.mpdemo.mapper")
public class MybatisPlusConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
加上注解@Configuration,会在启动springboot的同时加载配置类
将启动类中的@MapperScan("com.xm.mpdemo.mapper")移到MybatisPlusConfig中
在实体类的属性version上加上注解@TableField(fill = FieldFill.INSERT),使其在添加数据时被自动填充
在MyMetaObjectHandler中的fillInsert方法中加上
this.setFieldValByName("version", 1, metaObject);
使用MpdemoApplicationTests的testInsert方法添加一条数据王五,默认version为1
然后创建testOptimisticLocker方法,实现乐观锁,需要注意的是要实现乐观锁,要先查询再修改
@Test
void testOptimisticLocker() {
//需要先查询再修改,才会有效果,version才会+1
User user = userMapper.selectById(1363859734217646081L);
user.setAge(120);
userMapper.updateById(user);
}
mp实现简单查询操作
selectById 根据id查询
selectBatchIds多个id批量查询
List< User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);
selectByMap 简单的条件查询 通过map封装查询条件
mp实现分页查询操作 类似PageHelper
在MybatisPlusConfig中配置分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
创建testSelectPage方法
new Page对象page,传入当前页码和每页显示记录数
调用selectPage方法,传入page对象和查询条件wrapper,每页条件写null
调用mp分页查询的过程中,底层会将所有分页数据封装到page对象中,可以通过遍历page对象获取所有分页数据
current当前页 records每页list集合 size每页显示的记录数 total表中总记录数 pages总页数
hasNext是否有下一页 hesPrevious是否有上一页
mp实现逻辑删除操作
逻辑删除指的是没有真正删除,而是加上了标记,查不到了
物理删除就是真正删除
int i = userMapper.deleteById(1L);
System.out.println(i);
批量删除
int i = userMapper.deleteBatchIds(Arrays.asList(2, 3));
System.out.println(i);
简单条件删除
HashMap< String, Object > map = new HashMap<>();
map.put("name", "Sandy");
map.put("age", 21);
int result = userMapper.deleteByMap(map);
System.out.println(result);//1
逻辑删除
表中加字段deleted tinyint 1
可以加上Default属性为0
或使用自动填充@TableField(fill = FieldFill.INSERT)并在fillInsert方法中加上
this.setFieldValByName("deleted", 0, metaObject);
两种方式只能使用一种
实体类加属性Integer deleted,并加上注解@TableLogic
application.properties加上
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
默认删除为1,未删除为0,如果不进行修改可以不加这个配置
MybatisPlusConfig加上逻辑删除插件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
测试
使用testInsert方法加上一条记录赵六,默认deleted为0
使用testDeleteById方法逻辑删除
查询时要加上where deleted=0,但使用mp查询会自动加上
要查询已删除数据需要在mapper包中创建xml文件,在xml文件中使用sql语句进行查询,mp实现不了
性能分析插件配置
可以看到sql语句执行的时间,进而进行优化
MybatisPlusConfig中加上插件配置
@Bean
@Profile({"dev", "test"})//设置dev test环境开启,即对开发和测试环境生效
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
application.properties
#环境设置:dev test prod
spring.profiles.active=dev
显示执行时间
Time:3 ms - ID:com.xm.mpdemo.mapper.UserMapper.insert
mp实现复杂条件查询操作
一般使用QueryWrapper类构建条件,其继承于Wrapper
创建QueryWrapper类的对象
QueryWrapper< User> wrapper = new QueryWrapper< User> ();
调用QueryWrapper类的方法设置查询条件
ge大于等于 gt大于 le小于等于 lt小于
age大于等于30
wrapper.ge("age", 30);
eq 等于
wrapper.eq("name", "王五");
ne 不等于 <>
wrapper.ne("name", "王五");
between 范围
wrapper.between("age", 20, 30);
like 模糊查询
wrapper.like("name", "lie");
orderBy orderByDesc orderByAsc 排序
wrapper.orderByDesc("age");
last 拼接到sql语句最后
wrapper.last("limit 1");
指定要查询的列
wrapper.select("id", "name", "age");