导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[封装03-设计模式] Decorator 装饰器模式在前端的应用
[封装04-设计模式] Publish Subscribe 发布订阅模式在前端的应用
[封装05-ElementUI源码01] Row Col Container Header Aside Main
Footer
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-vue07] keep-alive
[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[部署04] [复习] gitlabCI + docker-compose + ssh免密登录 + 最全Dockerfile
[部署05] Kubernetes01
[数据结构和算法01] 二分查找和排序
[数据结构和算法02] 回文字符串
[数据结构和算法03] 栈 和 队列
[数据结构和算法04] 链表 和 树
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber01
[深入25] Typescript
[深入26] Drag
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
[前端学java14-Mybatis Plus] 分页插件 和 乐观锁插件
复习笔记-01
复习笔记-02
复习笔记-03
复习笔记-04
前置知识
(1) 一些单词
radix 基数
trap 陷阱
optimism 乐观 // optimistic 乐观的 adj
pessimism 悲观
(2) Mybatis-plus 的安装和使用
1. 安装 Mybatis-Plus maven 依赖
---
<!-- mybatis plus -->
<!-- mybatis plus包含了 ( mybatis-spring-boot-starter ) 和 ( spring-boot-starter-data-jdbc ) -->
<!-- 在 application.yml 文件中通过 mybatis-plus: xxx 对其进行定制配置 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
2. 在启动类上添加 @MapperScan
---
@MapperScan("com.example.lingjing.mapper")
public class Application {}
3
因为 mybatis-plus 可以和 mybatis 混用
所以 我们也安装配置一下mybatis
---
mybatis安装和配置和使用
---
3.1 安装
安装 mybatis maven 依赖
---
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
3.2 配置
application.yml 中配置 mybatis
---
mybatis:
# 配置 mybatis 规则
# 默认的前置路径就是 resources 文件夹中
# config-location: classpath:mybatis/mybatis-config.xml # mybatis全局配置文件 ( 注意:如果有下面的 configuration 就要注释掉 config-location )
# 1
# mapper-locations
# - mybatis的sql映射文件,注意是 mapper-locations,有s
# - 默认的路径前缀是 src/main/resources
# 2
# classpath
# - 1. 代表的是 src/main/java
# - 2. 代表的是 src/main/resources
# 这里指定了mapper的xml配置查询文件的位置,在 ( mapper类 和 mapper的xml文件 ) 是一一对应的
mapper-locations: classpath:mapper/*.xml
# configuration
# - 配置规则,和 mybatis-config.xml 文件中配置是一样的效果,使用一种即可,即 ( configuration ) 或者 ( config-location ) 二选一
configuration:
map-underscore-to-camel-case: true
3.3 使用
- mybatis有两种方式来使用
- 纯注解方式
- xml方式 ( 更推荐 )
---
编写bean对象
public class UserBean {}
编写mapper类
@Mapper
public interface UserMapper {
public UserBean getUser(Integer id);
}
编写mapper xml
<mapper namespace="com.example.lingjing.mapper.UserMapper">
<select id="getUser" resultType="com.example.lingjing.bean.UserBean">
select * from user where id=#{id}
</select>
</mapper>
4 mybat-plus的使用
- 可以使用mapper的能力,也可以使用service的能力
- mapper
- service
4.1 mapper
- 主要就是编写一个mapper接口去 extends BaseMapper<bean>
---
第一步
@Mapper
public interface UserMybatisPlusMapper extends BaseMapper<UserMybatisPlusBean> {}
第二步
就可以通过 @Autowired 注入后使用了,比如如下
@SpringBootTest
@Slf4j
public class UserMybatisPlusMapperTest {
@Autowired
UserMybatisPlusMapper userMybatisPlusMapper;
// 1
// mapper 相关测试
// - select -> selectById + selectList
// - update -> updateById
// - delete -> deleteById
// - insert -> insert
// 查 select single
@Test
public void mybatisPlusUserSelectTest() {
UserMybatisPlusBean userPlusBean = userMybatisPlusMapper.selectById(1);
System.out.println(userPlusBean);
}
// 查 select list
@Test
public void mybatisPlusUserSelectTestList() {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "woow_wu7");
List list = userMybatisPlusMapper.selectList(null); // null表示查询所有
log.info("list => {}", list);
}
// 增 insert
@Test
public void mybatisPlusUserInsertTest() {
UserMybatisPlusBean userPlusBean1 = new UserMybatisPlusBean();
// 2
// builder() 是 lombok 的 @Builder 的能力
UserMybatisPlusBean build = userPlusBean1.builder()
.name("aaa")
.age(900)
.address("bbb")
.id(900)
.build();
log.info("build => {}", build);
// 3
// 遇到问题
// - 问题:lombok 的 builder() 不生效?
// - 原因:builder()是有返回值的,返回值build才是最终设置过后的值,而不是userPlusBean1
int status = userMybatisPlusMapper.insert(build);
log.info("status => {}", status);
}
// 改 update
@Test
public void mybatisPlusUserUpdateTest() {
UserMybatisPlusBean userPlusBean2 = new UserMybatisPlusBean();
// 修改的就是上面 insert 的数据
UserMybatisPlusBean build = userPlusBean2.builder()
.name("AAA")
.age(900)
.address("BBB")
.id(900)
.build();
log.info("build => {}", build);
int status = userMybatisPlusMapper.updateById(build);
log.info("status => {}", status);
}
// 删 delete
@Test
public void mybatisPlusUserDeleteTest() {
int status = userMybatisPlusMapper.deleteById(900);
log.info("status => {}", status);
}
}
4.2 service
- 主要就是编写一个 ( 接口 ) interface 去 extends ( Iservice<bean对象> )
- 然后编写一个 ( 实现类 ) 去 extends ( ServiceImpl<mapper类, bean对象> )
---
第一步 - service接口
public interface UserMybatisPlusService extends IService<UserMybatisPlusBean> {}
第二步 - service实现类
@Service
public class UserMybatisPlusServiceImpl extends ServiceImpl<UserMybatisPlusMapper, UserMybatisPlusBean> implements UserMybatisPlusService {}
第三步 - 使用
@SpringBootTest
@Slf4j
public class UserMybatisPlusServiceTest {
@Autowired
UserMybatisPlusService userMybatisPlusService;
// 查 list
@Test
public void getList() {
List<UserMybatisPlusBean> list = userMybatisPlusService.list();
log.info("list => {}", list);
}
}
(一) mybatis-plus 分页插件
(1) mybatis-plus 分页插件的使用
- mybatis-plus提供了
- (
拦截器MybatisPlusInterceptor
) - (
添加内部拦截器addInnerInterceptor
) - (
内部分页拦截器PaginationInnerInterceptor
) - (
分页对象page
) - (
mapper.selectPage
)
- (
- 7-java-spring-boot-server 项目仓库源码地址
(1.1) 编写配置类
-
- 在配置类中,新建
new MybatisPlusInterceptor() 实例
- 在配置类中,新建
-
- 在 MybatisPlusInterceptor实例上调用添加拦截方法
addInnerInterceptor
- 在 MybatisPlusInterceptor实例上调用添加拦截方法
-
- 添加分页拦截器
new PaginationInnerInterceptor(DbType.MYSQL)
- 添加分页拦截器
(1.1) 编写配置类
---
@Configuration // 配置类
public class MyBatisPlusPaginationConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1
// MybatisPlusInterceptor
// 2
// interceptor.addInnerInterceptor
// 3
// PaginationInnerInterceptor
// 4
// 向MyBatis-Plus的过滤器链中添加 ( 分页拦截器 )
// 需要设置数据库类型( 主要用于分页方言 )
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// mysql类型,我们使用的是 mysql 数据库
return interceptor;
}
}
(1.2) 使用
- 1.生成 page 对象,具有 current, size 属性
- 2.通过在业务中调用
mapper.selectPage(page对象, wrapper)
即可
@SpringBootTest
@Slf4j
public class TestMybatisPlusPaginationPlugin {
@Autowired
UserMybatisPlusMapper userMybatisPlusMapper;
@Test
public void testPaginationPlugin() {
// page对象
// - 由 mybatis-plus 提供
// - current,size
Page<UserMybatisPlusBean> objectPage = new Page<>(1, 2); // 查询第1页,每次条数2条
// selectPage
// - 是 mybatis-plus mapper 提供的能力
userMybatisPlusMapper.selectPage(objectPage, null);
log.info(" objectPage.getRecords() => {}", objectPage.getRecords()); // ----- 查到的数据
log.info(" objectPage.getTotal() => {}", objectPage.getTotal()); // --------- 总数据条数
log.info(" objectPage.getPages() => {}", objectPage.getPages()); // --------- 总页码数
log.info(" objectPage.hasNext() => {}", objectPage.hasNext()); // ----------- 是否有下一页
log.info(" objectPage.hasPrevious() => {}", objectPage.hasPrevious()); // --- 是否有上一页
}
}
(2) 自定义分页功能 - 在mybatis的查询语句中使用
- 其实就是利用mybatis-plus的 (
page对象
) 来实现
第一步
- 在 mapper接口interface中定义 - 自定义分页的方法
---
/**
* @desc 通过 用户ID 查询 用户信息
* @param page Mybatis-plus 提供的分页对象,必须是第一个参数位置
* @param id 表数据 id
* @return page
*/
// 重要
// 自定义分页方法
// - 返回值:是mybatis-plus的 - page对象
@Mapper
public interface UserMapper {
public Page<UserBean> selectPageVo(
@RequestParam("page") Page<UserBean> page,
@RequestParam("id") Integer id
);
}
第二步
- 在 mapper.xml 文件中编写查询语句,就是 mybatis 的那一套
---
<?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">
<!-- namespace 是 ( mapper类 ) 的全路径,注意分隔符是.而不是/ -->
<mapper namespace="com.example.lingjing.mapper.UserMapper">
<!-- 查 -->
<!-- 注意:resultType 是返回值类型,即Bean对象的文件路径-->
<!-- 注意:id 是方法名-->
<!-- 问题:那如何使用 别名 呢?-->
<!-- 回答:在 mybatis-plus 的配置项中设置,就不用写bean的全路径了-->
<select id="selectPageVo" resultType="com.example.lingjing.bean.UserBean">
select * from user where id=#{id}
</select>
</mapper>
第三步 - 使用
---
@SpringBootTest
@Slf4j
public class TestCustomPagination {
@Autowired
UserMapper userMapper;
@Test
public void testCustomPagination() {
Page<UserBean> objectPage = new Page<UserBean>(1, 2);
userMapper.selectPageVo(objectPage, 1); // 参数是 page, id
log.info(" objectPage.getRecords() => {}", objectPage.getRecords()); // ----- 查到的数据
log.info(" objectPage.getTotal() => {}", objectPage.getTotal()); // --------- 总数据条数
log.info(" objectPage.getPages() => {}", objectPage.getPages()); // --------- 总页码数
log.info(" objectPage.hasNext() => {}", objectPage.hasNext()); // ----------- 是否有下一页
log.info(" objectPage.hasPrevious() => {}", objectPage.hasPrevious()); // --- 是否有上一页
}
}
(二) 乐观锁 和 悲观锁
(1) 概念
乐观锁 和 悲观锁
- 悲观锁:并发操作时,阻塞执行(悲观的认为数据会被其他线程同时修改),加锁,读和取都会阻塞
- 乐观锁:并发操作时,读不阻塞(乐观的认为数据不会被其他线程同时修改,随便读),不加锁,写会根据版本号version来判断报错或重试
乐观锁
- 概念:
- 在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁
- 但是在更新的时,会判断一下在此期间,别的线程有没有更新这个数据
- 实现
- CAS
- Version版本号机制
- 适用场景
- 适合并发 ( 读 ) 操作多的场景,( 不加锁 ) 的特点使得读操作的性能高
悲观锁
- 概念
- 在操作数据时非常悲观,每次读数据的时,都认为同时有其他线程在修改数据
- 所以每次拿到数据时就会 ( 上锁 ),操作完才会释放锁,上锁期间其他线程将会 ( 阻塞 )
- 缺点
- 阻塞其他线程,效率低
- 可能造成某个线程永久等待,即发生 ( 死锁 ) 的可能性比较大
- 适用场景
- 适合并发 ( 写 ) 操作多的场景,( 先加锁再进行写操作 ),保证写操作的数据正确性
口诀:
- 乐观锁,不加锁,读
- 悲观锁,加锁,写
---
案例:
商品价格 100
a和b同时在操作商品价格,a减10,b加10
乐观锁:100 - 10 a操作,b也操作 100 + 10,==================> 最终 110
悲观锁:100 - 10 = 90 a操作完后,b才操作 90 + 10 = 100,=====> 最终 100
(2) mybatis-plus 乐观锁插件
(1) 编写配置类
@Configuration
public class MyBatisPlusOptimisticLockerConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1
// MybatisPlusInterceptor
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 拦截器
// 2
// 向MyBatis-Plus的过滤器链中添加 ( 乐观锁拦截器 )
// 注意要想 bean 中添加 @Version 注解
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 添加乐观锁插件
return interceptor;
}
}
(2) bean 对象添加 @Version
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component
@TableName("price") // mybatis-plus 查询的表名
public class PriceBean {
public Integer price;
public String name;
public Integer id;
@Version // mybatis-plus 标识乐观锁版本号
public Integer version;
}
(3) 编写mapper接口
@Mapper
public interface ProductMapper extends BaseMapper<ProductBean> {}
(4) 测试乐观锁
@SpringBootTest
@Slf4j
public class TestMybatisPlusOptimisticLocker {
@Autowired
ProductMapper productMapper;
@Test
public void testMybatisPlusOptimisticLocker() {
ProductBean productMapper1 = productMapper.selectById(1);
ProductBean productMapper2 = productMapper.selectById(1);
// 1 获取到的价格
log.info("1获取到的价格price1 => {}", productMapper1.getPrice());
// 2 获取到的价格
log.info("2获取到的价格price2 => {}", productMapper2.getPrice());
// 1 将价格 + 10
productMapper1.setPrice(productMapper1.getPrice() + 10);
productMapper.updateById(productMapper1); // 更新数据库
// 2 将价格 - 10
productMapper2.setPrice(productMapper1.getPrice() - 10);
productMapper.updateById(productMapper2); // 更新数据库
ProductBean productMapperEnd = productMapper.selectById(1);
log.info("最终查询到的价格price => {}", productMapperEnd.getPrice()); // 110
}
}