【后端】MyBatis-Plus 体系(三)

本文详细介绍了MyBatis-Plus的分页插件配置与使用,包括XML自定义分页。接着讲解了乐观锁的概念、场景、工作原理,并通过实例展示了MyBatis-Plus如何实现乐观锁。此外,还涵盖了通用枚举的配置和使用,代码生成器的快速生成方法,以及多数据源配置和测试。最后提到了MyBatisX插件用于快速生成CRUD代码。
摘要由CSDN通过智能技术生成

MyBatis-Plus

六、插件

1. 分页插件

  • MyBatis-Plus自带分页插件,只要简单的配置即可实现分页功能
1.1 添加配置类
@Configuration 
@MapperScan("com.alex.mybatisplus.mapper") 
//可以将主类中的注解移到此处 
public class MybatisPlusConfig { 
	@Bean 
	public MybatisPlusInterceptor mybatisPlusInterceptor() { 
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 
		return interceptor; 
	} 
}
1.2 测试
@Test 
public void testPage(){ 
	//设置分页参数 
	Page<User> page = new Page<>(1, 5); 
	userMapper.selectPage(page, null); 
	//获取分页数据 
	List<User> list = page.getRecords(); 
	list.forEach(System.out::println); 
	System.out.println("当前页:"+page.getCurrent()); 
	System.out.println("每页显示的条数:"+page.getSize()); 
	System.out.println("总记录数:"+page.getTotal()); 
	System.out.println("总页数:"+page.getPages()); 
	System.out.println("是否有上一页:"+page.hasPrevious()); 
	System.out.println("是否有下一页:"+page.hasNext()); 
}

2. xml自定义分页

2.1 UserMapper中定义接口方法
/**
 * 根据年龄查询用户列表,分页显示 
 * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位 
 * @param age 年龄 
 * @return 
*/
	Page<User> selectPageVo(@Param("page") Page<User> page, 
		@Param("age") Integer age);
2.2 UserMapper.xml中编写SQL
<!--SQL片段,记录基础字段--> 
<sql id="BaseColumns">id,username,age,email</sql> 
<!--Page<User> selectPageVo(Page<User> page, Integer age);--> 
<select id="selectPageVo" resultType="User"> 
	SELECT <include refid="BaseColumns"></include> 
	FROM t_user 
	WHERE age > # {age} 
</select>
2.3 测试
@Test 
public void testSelectPageVo(){ 
	//设置分页参数 
	Page<User> page = new Page<>(1, 5); 
	userMapper.selectPageVo(page, 20); 
	//获取分页数据 
	List<User> list = page.getRecords(); 
	list.forEach(System.out::println); 
	System.out.println("当前页:"+page.getCurrent()); 
	System.out.println("每页显示的条数:"+page.getSize()); 
	System.out.println("总记录数:"+page.getTotal()); 
	System.out.println("总页数:"+page.getPages()); 
	System.out.println("是否有上一页:"+page.hasPrevious()); 
	System.out.println("是否有下一页:"+page.hasNext()); 
}

3. 乐观锁

3.1 场景
  • 一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
  • 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就
    完全被小王的覆盖了。
  • 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
3.2 乐观锁与悲观锁
  • 上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
  • 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
3.3 模拟修改冲突
  • 数据库中增加商品表
CREATE TABLE t_product ( 
	id BIGINT(20) NOT NULL COMMENT '主键ID', 
	NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称', 
	price INT(11) DEFAULT 0 COMMENT '价格', 
	VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号', 
	PRIMARY KEY (id) 
);
  • 添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
  • 添加实体
package com.alex.mybatisplus.entity; 
import lombok.Data; 
@Data 
public class Product { 
	private Long id; 
	private String name; 
	private Integer price; 
	private Integer version; 
}
  • 添加mapper
public interface ProductMapper extends BaseMapper<Product> { }
  • 测试
@Test 
public void testConcurrentUpdate() { 
	//1、小李 
	Product p1 = productMapper.selectById(1L); 
	System.out.println("小李取出的价格:" + p1.getPrice()); 
	//2、小王 
	Product p2 = productMapper.selectById(1L); 
	System.out.println("小王取出的价格:" + p2.getPrice());
	//3、小李将价格加了50元,存入了数据库 
	p1.setPrice(p1.getPrice() + 50); 
	int result1 = productMapper.updateById(p1); 
	System.out.println("小李修改结果:" + result1); 
	//4、小王将商品减了30元,存入了数据库 
	p2.setPrice(p2.getPrice() - 30); 
	int result2 = productMapper.updateById(p2); 
	System.out.println("小王修改结果:" + result2); 
	//最后的结果 
	Product p3 = productMapper.selectById(1L); 
	//价格覆盖,最后的结果:70 
	System.out.println("最后的结果:" + p3.getPrice()); 
}
3.4 乐观锁实现流程
  • 数据库中添加version字段
  • 取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
  • 更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, 
`version`=`version` + 1 
WHERE id=1 AND `version`=1
3.5 Mybatis-Plus实现乐观锁
  • 修改实体类
package com.alex.mybatisplus.entity; 
import com.baomidou.mybatisplus.annotation.Version; 
import lombok.Data; 
@Data 
public class Product { 
	private Long id; 
	private String name; 
	private Integer price; 
	@Version 
	private Integer version; 
}
  • 添加乐观锁插件配置
@Bean 
public MybatisPlusInterceptor mybatisPlusInterceptor(){ 
	MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 
	//添加分页插件 
	interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 
	//添加乐观锁插件 
	interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); 
	return interceptor; 
}
  • 测试修改冲突
小李查询商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小王查询商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小李修改商品价格,自动将version+1
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
小王修改商品价格,此时version已更新,条件不成立,修改失败
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最终,小王修改失败,查询价格:150
SELECT id,name,price,version FROM t_product WHERE id=?
  • 优化流程
@Test 
public void testConcurrentVersionUpdate() { 
	//小李取数据 
	Product p1 = productMapper.selectById(1L); 
	//小王取数据 
	Product p2 = productMapper.selectById(1L); 
	//小李修改 + 50 
	p1.setPrice(p1.getPrice() + 50); 
	int result1 = productMapper.updateById(p1); 
	System.out.println("小李修改的结果:" + result1); 
	//小王修改 - 30 
	p2.setPrice(p2.getPrice() - 30); 
	int result2 = productMapper.updateById(p2); 
	System.out.println("小王修改的结果:" + result2); 
	if(result2 == 0){ 
		//失败重试,重新获取version并更新 
		p2 = productMapper.selectById(1L);
		p2.setPrice(p2.getPrice() - 30); 
		result2 = productMapper.updateById(p2); 	
	}
	System.out.println("小王修改重试的结果:" + result2); 
	//老板看价格 
	Product p3 = productMapper.selectById(1L); 
	System.out.println("老板看价格:" + p3.getPrice()); 
}

七、通用枚举

  • 表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现

1. 数据库表添加字段sex

在这里插入图片描述

2. 创建通用枚举类型

package com.alex.mp.enums; 
import com.baomidou.mybatisplus.annotation.EnumValue; 
import lombok.Getter; 
@Getter 
public enum SexEnum { 
	MALE(1, "男"), 
	FEMALE(2, "女"); 

	@EnumValue 
	private Integer sex; 
	private String sexName; 
	
	SexEnum(Integer sex, String sexName) { 
		this.sex = sex; 
		this.sexName = sexName;
	} 
}

3. 配置扫描通用枚举

mybatis-plus: 
  configuration: 
    # 配置MyBatis日志 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 
  global-config: 
    db-config: 
      # 配置MyBatis-Plus操作表的默认前缀 
      table-prefix: t_ 
      # 配置MyBatis-Plus的主键策略 
      id-type: auto 
  # 配置扫描通用枚举 
  type-enums-package: com.alex.mybatisplus.enums

4. 测试

@Test 
public void testSexEnum(){ 
	User user = new User(); 
	user.setName("Enum"); 
	user.setAge(20); 
	//设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库 
	user.setSex(SexEnum.MALE); 
	//INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? ) 
	//Parameters: Enum(String), 20(Integer), 1(Integer) 
	userMapper.insert(user); 
}

八、代码生成器

1. 引入依赖

<dependency> 
	<groupId>com.baomidou</groupId> 
	<artifactId>mybatis-plus-generator</artifactId> 
	<version>3.5.1</version> 
</dependency> 
<dependency> 
	<groupId>org.freemarker</groupId> 
	<artifactId>freemarker</artifactId> 
	<version>2.3.31</version> 
</dependency>

2. 快速生成

public class FastAutoGeneratorTest {
	public static void main(String[] args) { 		
		FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false", "root", "123456") 
			.globalConfig(builder -> { 
				builder.author("alex") // 设置作者 
				//.enableSwagger() // 开启 swagger 模式 
				.fileOverride() // 覆盖已生成文件 
				.outputDir("D://mybatis_plus"); // 指定输出目录 
			})
			.packageConfig(builder -> { 
				builder.parent("com.alex") // 设置父包名 
					.moduleName("mybatisplus") // 设置父包模块名 
	.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); 
	// 设置mapperXml生成路径 })
			.strategyConfig(builder -> { 
				builder.addInclude("t_user") // 设置需要生成的表名 
				.addTablePrefix("t_", "c_"); // 设置过滤表前缀 
			})
			.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker 引擎模板,默认的是Velocity引擎模板 
			.execute();
	}
}

九、多数据源

  • 适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等
  • 目前我们就来模拟一个纯粹多库的一个场景,其他场景类似
  • 场景说明:
    • 我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例,分别获取用户数据与商品数据,如果获取到说明多库模拟成功

1. 创建数据库及表

  • 创建数据库mybatis_plus_1和表product
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 
use `mybatis_plus_1`; 
CREATE TABLE product ( 
	id BIGINT(20) NOT NULL COMMENT '主键ID', 
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称', 
	price INT(11) DEFAULT 0 COMMENT '价格', 
	version INT(11) DEFAULT 0 COMMENT '乐观锁版本号', 
	PRIMARY KEY (id) 
);
  • 添加测试数据
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
  • 删除mybatis_plus库product表
use mybatis_plus; 
DROP TABLE IF EXISTS product;

2. 引入依赖

<dependency> 
	<groupId>com.baomidou</groupId> 
	<artifactId>dynamic-datasource-spring-boot-starter</artifactId> 
	<version>3.5.0</version> 
</dependency>

3. 配置多数据源

  • 说明:注释掉之前的数据库连接,添加新配置
spring: 
  # 配置数据源信息 
  datasource: 
    dynamic: 
    # 设置默认的数据源或者数据源组,默认值即为master 
    primary: master 
    # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源 
    strict: false 
    datasource: 
      master: 
        url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf- 8&useSSL=false 
        driver-class-name: com.mysql.cj.jdbc.Driver 
        username: root 
        password: 123456 
      slave_1: 
        url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf- 8&useSSL=false 
        driver-class-name: com.mysql.cj.jdbc.Driver 
        username: root 
        password: 123456

4. 创建用户service

public interface UserService extends IService<User> { }
@DS("master") //指定所操作的数据源 
@Service 
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
implements UserService { }

5. 创建商品service

public interface ProductService extends IService<Product> { }
@DS("slave_1") 
@Service 
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> 
implements ProductService { }

6. 测试

@Autowired 
private UserService userService; 

@Autowired 
private ProductService productService; 

@Test 
public void testDynamicDataSource(){ 
	System.out.println(userService.getById(1L)); 
	System.out.println(productService.getById(1L)); 
}
  • 结果:
    • 都能顺利获取对象,则测试成功

十、MyBatisX 插件

1. MyBatisX 代码生成器

  • 安装:Settings -> Plugins -> MyBatisX
  • idea里Database 加入自己的数据库
  • MySQL -> schemas -> mybatis_plus -> t_user

请添加图片描述

请添加图片描述
请添加图片描述

2. 快速生成 CRUD

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬小帽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值