MybatisPlus
MyBatis-Plus简介
MyBaits-Plus
是一个MyBatis
的增强工具
,在MyBatis
的基础上只做增强不做改变
,为简化开发、提高效率而生
。
MyBatis-Plus
提供了通用的mapper和service
,可以在不编写任何SQL
语句的情况下,快速的实现对单表的CRUD、批量、逻辑删除、分页等操作。
官方地址:https://baomidou.com/
MyBaits-Plus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本
CURD
,性能基本无损耗,直接面向对象操作 - 强大的
CRUD
操作:内置通用Mapper
、通用Service
,仅仅通过少量配置即可实现单表大部分CRUD
操作,更有强大的条件构造器,满足各类使用需求 - 支持
Lambda
形式调用:通过Lambda
表达式,方便的编写各类查询条件,无需再担心字段写错 - 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一
ID 生成器 - Sequence
),可自由配置,完美解决主键问题 - 支持
ActiveRecord
模式:支持ActiveRecord
形式调用,实体类只需继承Model
类即可进行强大的CRUD
操作 - 支持自定义全局通用操作:支持全局通用方法注入(
Write once, use anywhere
) - 内置代码生成器:采用代码或者
Maven
插件可快速生成Mapper 、 Model 、 Service 、 Controller
层代码,支持模板引擎,更有超多自定义配置等您来使用 - 内置分页插件:基于
MyBatis
物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List
查询 - 分页插件支持多种数据库:支持
MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer
等多种数据库 - 内置性能分析插件:可输出
SQL
语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询 - 内置全局拦截插件:提供全表
delete 、 update
操作智能分析阻断,也可自定义拦截规则,预防误操作
支持数据库
任何能使用 MyBatis
进行 CRUD
, 并且支持标准 SQL
的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR
您的支持。
MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb、达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
框架结构
scan Entity
: 扫描实体,表由实体类
决定,然后通过反射技术
将我们的实体类进行抽取,
在根据相应的方法(insert、update、delete、select
),生成相应的sql
语句,然后注入到容器中。
代码及文档地址
官方地址:http://mp.baomidou.com
代码发布地址:
Github:https://github.com/baomidou/mybatis-plus
Gitee:https://gitee.com/baomidou/mybaits-plus
文档发布地址:https://baomidou.com/pages/24112f
入门案例
- 开发环境
IDE: idea 2019.2
JDK: JDK8+
构建工具:maven 3.5.4
MySQL版本:MySQL5.7
Spring Boot: 2.6.3
MyBatis-Plus: 3.5.1 - 创建数据库及表
//创建数据库
create database mybatis_plus;
//创建表结构
create table user (
id bigint(20) not null primary key comment '主键ID',
name varchar(20) default null comment '姓名',
age int(11) default null comment '年龄',
email varchar(50) default null comment '邮箱'
)
//插入数据
insert into user
values(1, 'Jone', 18, '111@qq.com'),
(2, 'Jack', 18, '11122@qq.com'),
(3, 'Tom', 20, '11133@qq.com'),
(4, 'Sandy', 21, '11144@qq.com'),
(5, 'Gille', 24, '11155@qq.com');
commit;
- 目录结构以及配置如下
创建Mapper
文件、User
实体类、application.yml
文件以及相应的测试方法
- UserMapper代码如
/**
* mybatis-plus
* 提供了通用的mapper和service
* 1. 需要继承BaseMapper类
* 2. BaseMapper包含了对单表 增、删、改、查的各种方法
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 根据id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> selectMapById(Long id);
//分页插件作用于我们自定义的分页时,第一个参数必须是Page
//@param相当于mapper.xml中的parameterType传递参数
/**
* 通过年龄查询用户信息并分页
* @param page mybatis-plus提供的分页参数,必须第一个位置
* @param age
* @return
*/
// Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
Page<User> selectPageVo( Page<User> page, Integer age);
}
- User
/**
* @NoArgsConstructor
* @AllArgsConstructor
* @Setter
* @Getter
* @EqualsAndHashCode
*/
@Data
@Accessors(chain = true)
@TableName("t_user")
public class User {
/* @TableId
private Long uid; //数据库bigint*/
@TableId(value = "uid")
private Long id;
private String name; //用户名
private Integer age; //年龄
private String email; //邮箱
private SexEnum sex;
}
- MybatisplusDemoApplication
@SpringBootApplication
public class MybatisplusDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusDemoApplication.class, args);
}
}
- application.yml
#配置端口信息
server:
port: 8084
#数据源 spring boot中默认所使用的数据源
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# mysql是5版本可以使用这个,建议使用8版本
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
#mysql 5 版本按照以下配置即可
#url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT
# 配置mybatis_plus打印日志
mybatis-plus:
#global-config:
# db-config:
# table-prefix: t_ #设置全局表的前缀
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/**/*.xml #默认位置
#配置类型别名,不区分大小写
type-aliases-package: com.cjw.mybatisplus.mybatisplusdemo.pojo
#扫描通用枚举的包
type-enums-package: com.cjw.mybatisplus.mybatisplusdemo.enums
- MybatisPlusTest
package com.cjw.mybatisplus.mybatisplusdemo;
import com.cjw.mybatisplus.mybatisplusdemo.mapper.UserMapper;
import com.cjw.mybatisplus.mybatisplusdemo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class MybatisPlusTest {
/**
* userMapper为什么爆红?
* 1、ioc只能存在类所对应的bean,不能存在接口所对应的bean
* 2、ioc默认是将bean 进行装配的
* 但是我们定义的是一个接口,导致可能无法装配,但是在
* 运行的时候可以装配的
* 3.可以在UserMapper中配置@Repository
*/
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList() {
//List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
/**
* Wrapper是条件构造器
* */
//通过条件构造器来查询一个list集合,若没有集合,则可设置null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybaits-plus驱动-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--lombok简化实体类开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
</dependencies>
- Mybatisplusconfig
@Configuration
/**
* 在springboot 使用mybatis需要
* 设置mapper接口和映射文件所在的包
*/
@MapperScan("com.cjw.mybatisplus.mybatisplusdemo.mapper")
public class Mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
//等价于
/* *//**
* 添加分页插件
* @return
*//*
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
return new PaginationInnerInterceptor();
}
*//**
* 添加乐观锁插件
* @return
*//*
@Bean
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}*/
}
- SexEnum
/**
* 枚举作为取值使用
* 因此使用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;
}
}
- Product、ProductMapper、ProductService、ProductServiceImpl
Product
@Data
@Accessors(chain = true)
@TableName("t_product")
public class Product {
private Long id;
private String name;
private Integer price;
@Version //标识乐观锁版本号字段
private Integer version;
}
ProductMapper
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
ProductService
public interface ProductService extends IService {
}
ProductServiceImpl
public class ProductServiceImpl extends ServiceImpl implements ProductService {
}
- 测试代码
- MybatisplusProductTest
@SpringBootTest
public class MybatisplusProductTest {
@Autowired
ProductMapper productMapper;
@Test
public void test01() {
/**
* ==> Preparing: SELECT id,name FROM t_product
* ==> Parameters:
* <== Columns: id, name
* <== Row: 1, 外星人笔记本
* <== Total: 1
*/
QueryWrapper<Product> queryWrapper = new QueryWrapper();
queryWrapper.select("id", "name");
List<Product> listProduct = productMapper.selectList(queryWrapper);
listProduct.forEach(System.out::println);
}
@Test
public void test02() {
//小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productLi.getPrice());
//小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:" + productWang.getPrice());
/*//小李将商品价格+50
productLi.setPrice(100);
productMapper.updateById(productLi);*/
//小李将商品价格+50
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
//小王将商品价格-30 (修改不成功)
productWang.setPrice(productWang.getPrice() - 30);
int result = productMapper.updateById(productWang);
if (result == 0) {
//@version只是控制其不进行更新,但是此时小王中获取的数据还是之前的100,因此要重新创建对象
System.out.println("productWang.getPrice() = " + productWang.getPrice());//productWang.getPrice() = 70
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice() - 30);
productMapper.updateById(productNew);
}
//老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productBoss.getPrice());
}
}
- FastAutoGeneratorTest
代码生成器
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT", "root", "root")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
//.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 设置mapperXml生成路径
})
//设置逆向生成表
.strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
- MybatisPlusEnumTest
@SpringBootTest
public class MybatisPlusEnumTest {
@Autowired
UserMapper userMapper;
@Test
public void test01() {
/**
* ==> Preparing: INSERT INTO t_user ( uid, name, sex ) VALUES ( ?, ?, ? )
* ==> Parameters: 1543962141062930433(Long), admin(String), 1(Integer)
* <== Updates: 1
*/
User user = new User();
user.setName("admin")
.setSex(SexEnum.MALE);
int result = userMapper.insert(user);
System.out.println("result = " + result);
}
}
- MybatisPlusPageTest
@SpringBootTest
public class MybatisPlusPageTest {
@Autowired
UserMapper userMapper;
@Test
public void test1() {
Page<User> page = new Page(1, 3);
userMapper.selectPageVo(page, 20);
}
}
- MyBatisPlusPluginsTest
@SpringBootTest
public class MyBatisPlusPluginsTest {
@Autowired
private UserMapper userMapper;
@Test
public void test1() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user LIMIT ?
* ==> Parameters: 3(Long)
*/
Page<User> page= new Page<User>(1, 3);
//返回page
userMapper.selectPage(page, null);
/**
* [User(id=4, name=小明, age=21, email=test@qq.com), User(id=5, name=Gille, age=24, email=11155@qq.com)]
* 1
* 1
* 2
* false
* false
*/
System.out.println(page.getRecords());
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getTotal());
System.out.println(page.hasNext());
System.out.println(page.hasPrevious());
}
}
- MyBatisPlusServiceTest
@SpringBootTest
public class MyBatisPlusServiceTest {
@Autowired
private UserService userService;
@Test
public void testConunt() {
//查询总记录数 select count(*) from user;
int count = userService.count();
System.out.println("总记录数: count = " + count);
}
/**
* 批量添加在usermapper中没有
* 因为usermapper是直接拼接成sql
*
*/
@Test
public void insert() {
List<User> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setAge(10 + i)
.setName("ybc" + i);
list.add(user);
}
//循环insert into
boolean bl = userService.saveBatch(list);
System.out.println("bl = " + bl);
}
}
- MybatisPlusTest
@SpringBootTest
public class MybatisPlusTest {
/**
* userMapper为什么爆红?
* 1、ioc只能存在类所对应的bean,不能存在接口所对应的bean
* 2、ioc默认是将bean 进行装配的
* 但是我们定义的是一个接口,导致可能无法装配,但是在
* 运行的时候可以装配的
* 3.可以在UserMapper中配置@Repository
*/
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList() {
//List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
/**
* Wrapper是条件构造器
*
* */
//通过条件构造器来查询一个list集合,若没有集合,则可设置null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
/**
* ==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
* ==> Parameters: 1542117549401255937(Long), 张三(String), 23(Integer), zhangsan@qq.com(String)
* <== Updates: 1
*/
@Test
public void testInsert() {
User user = new User();
user.setName("张三").setAge(23).setEmail("zhangsan@qq.com");
int result = userMapper.insert(user);
System.out.println("result======>" + result);
//默认使用雪花算法来生成id
// id ======>1542117549401255937 bigint long
System.out.println(" id ======>" + user.getId());
}
/**
* JDBC Connection [HikariProxyConnection@726483751 wrapping com.mysql.jdbc.JDBC4Connection@16da1abc] will not be managed by Spring
* ==> Preparing: DELETE FROM user WHERE id=?
* ==> Parameters: 1542117549401255937(String)
*/
@Test
public void testDelete() {
/**
* 通过id删除用户信息
* JDBC Connection [HikariProxyConnection@726483751 wrapping com.mysql.jdbc.JDBC4Connection@16da1abc] will not be managed by Spring
* ==> Preparing: DELETE FROM user WHERE id=?
* ==> Parameters: 1542117549401255937(String)
*/
/**
*
* int result = userMapper.deleteById(1542118893906358273l);
* System.out.println("result = " + result);
*/
/**
* ==> Preparing: DELETE FROM user WHERE name = ? AND age = ?
* ==> Parameters: 张三(String), 23(String)
*/
/*Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", "23");
int result_map = userMapper.deleteByMap(map);
System.out.println("result_map = " + result_map);*/
/**
* ==> Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? )
* ==> Parameters: 1(Long), 2(Long), 3(Long)
* <== Updates: 3
*/
List alist = Arrays.asList(1l, 2l , 3l);
userMapper.deleteBatchIds(alist);
}
@Test
public void testUpdate() {
/**
* ==> Preparing: UPDATE user SET name=?, age=?, email=? WHERE id=?
* ==> Parameters: aa1(String), 23(Integer), 1233@qq.com(String), 3(Long)
* <== Updates: 0
*/
//修改用户信息
User user = new User();
user.setId(3l).setName("aa1").setAge(23).setEmail("1233@qq.com");
userMapper.updateById(user);
}
@Test
public void userSelect() {
//通过id查询用户信息
/**
* ==> Preparing: SELECT id,name,age,email FROM user WHERE id=?
* ==> Parameters: 1(Long)
* <== Total: 0
*/
User user_id = userMapper.selectById(1l);
/**
* ==> Preparing: SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
* ==> Parameters: 1(Long), 2(Long), 3(Long)
* <== Total: 0
*/
List<Long> longs = Arrays.asList(1l,2l,3l);
//根据多个id执行
//select * from user r where r.id in (?,?,?)
List<User> listUser = userMapper.selectBatchIds(longs);
listUser.forEach(System.out::println);
//select * from user where name =? and age = ?
Map<String,Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 20);
List<User> usermap = userMapper.selectByMap(map);
usermap.forEach(System.out::println);
//System.out.println("usermap = " + usermap);
//select * from user
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
@Test
public void testZidingyi() {
/**
* ==> Preparing: select * from user r where r.id = ?
* ==> Parameters: 2(Long)
* <== Total: 0
*/
Map<String,Object> objectMap = userMapper.selectMapById(2l);
System.out.println(objectMap);
}
}
- MyBatisPlusWrapperTest
@SpringBootTest
public class MyBatisPlusWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void selectListMapper() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
* ==> Parameters: %张三%(String), 20(Integer), 30(Integer)
*/
//查询用户名包含a,年龄在20到30之间,邮箱信息不为null的用户信息。
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.like("name", "张三")
.between("age", 20, 30)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void test02() {
/**
* SELECT uid AS id,name,age,email FROM t_user ORDER BY age DESC,uid ASC
*/
//查询用户信息,按照年龄降序排序,若年龄相同,按照id升序排序
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void deleteWrapper() {
/**
* DELETE FROM t_user WHERE (email IS NULL)
*/
//删除邮箱地址为null的用户信息
//删除可以使用queryWrapper
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result = " + result);
}
@Test
public void updateWrapperTest() {
/**
* Preparing: UPDATE t_user SET name=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)
* ==> Parameters: 小明(String), test@qq.com(String), 20(Integer), %a%(String)
*/
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 20)
.like("name", "a")
.or()
.isNull("email");
User user = new User();
user.setName("小明")
.setEmail("test@qq.com");
/**
* 第一参数 设置填充内容 set
* 第二参数 设置修改条件 where
*/
int result = userMapper.update(user, queryWrapper);
System.out.println("result = " + result);
}
@Test
public void test05 () {
/**
* ==> Preparing: UPDATE t_user SET name=?, email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
* ==> Parameters: 小红(String), test@qq.com(String), %a%(String), 20(Integer)
* <== Updates: 0
*/
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like("name", "a")
//lamda consumer接口 Consumer<Param> consumer
//lambda中的条件有限执行
.and(i-> {
i.gt("age", 20)
.or()
.isNull("email");
});
User user = new User();
user.setName("小红")
.setEmail("test@qq.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("result = " + result);
}
@Test
public void test6() {
/**
* ==> Preparing: SELECT name,age,email FROM t_user
*/
//查询用户的用户名、年龄、邮箱信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age", "email");
List<Map<String, Object>> list = userMapper.selectMaps(queryWrapper);
list.forEach(System.out::println);
}
@Test
public void test7() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user
* WHERE (uid IN (select uid from t_user where uid <= 100))
*/
//查询id小于等于100的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select " +
" uid from t_user where uid <= 100");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void test8() {
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper
.like("name", "a")
//lamda consumer接口 Consumer<Param> consumer
//lambda中的条件有限执行
.and(i-> {
i.gt("age", 20)
.or()
.isNull("email");
});
updateWrapper.set("name", "晓红")
.set("email","test@qq.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
}
@Test
public void test9() {
//import com.baomidou.mybatisplus.core.toolkit.StringUtils;
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//不为空字符串、不为null、不为空白符
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("name", username);
}
if (ageBegin != null ) {
queryWrapper.ge("age", ageBegin);
}
if (ageEnd != null) {
queryWrapper.le("age", ageEnd);
}
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void test10() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user WHERE (age >= ? AND age <= ?)
* ==> Parameters: 20(Integer), 30(Integer)
*/
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "name", username)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null , "age", ageEnd);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void test11() {
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
//防止字段名写错
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null , User::getAge, ageEnd);
List<User> userList = userMapper.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
@Test
public void test12() {
/**
* ==> Preparing: UPDATE t_user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
* ==> Parameters: 晓红123(String), test123@qq.com(String), %a%(String), 20(Integer)
*/
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.like(User::getName, "a")
.and(i->i.gt(User::getAge, 20).or()
.isNull(User::getEmail));
lambdaUpdateWrapper.set(User::getName, "晓红123")
.set(User::getEmail, "test123@qq.com");
int result = userMapper.update(null, lambdaUpdateWrapper);
System.out.println("result = " + result);
}
}
基本功能
通用mapper-----BaseMapper
通过BaseMapper
来实现CRUD
@Test
public void testSelectList() {
//List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
/**
* Wrapper是条件构造器
* */
//通过条件构造器来查询一个list集合,若没有集合,则可设置null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
/**
* ==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
* ==> Parameters: 1542117549401255937(Long), 张三(String), 23(Integer), zhangsan@qq.com(String)
* <== Updates: 1
*/
@Test
public void testInsert() {
User user = new User();
user.setName("张三").setAge(23).setEmail("zhangsan@qq.com");
int result = userMapper.insert(user);
System.out.println("result======>" + result);
//默认使用雪花算法来生成id
// id ======>1542117549401255937 bigint long
System.out.println(" id ======>" + user.getId());
}
/**
* JDBC Connection [HikariProxyConnection@726483751 wrapping com.mysql.jdbc.JDBC4Connection@16da1abc] will not be managed by Spring
* ==> Preparing: DELETE FROM user WHERE id=?
* ==> Parameters: 1542117549401255937(String)
*/
@Test
public void testDelete() {
/**
* 通过id删除用户信息
* JDBC Connection [HikariProxyConnection@726483751 wrapping com.mysql.jdbc.JDBC4Connection@16da1abc] will not be managed by Spring
* ==> Preparing: DELETE FROM user WHERE id=?
* ==> Parameters: 1542117549401255937(String)
*/
/**
*
* int result = userMapper.deleteById(1542118893906358273l);
* System.out.println("result = " + result);
*/
/**
* ==> Preparing: DELETE FROM user WHERE name = ? AND age = ?
* ==> Parameters: 张三(String), 23(String)
*/
/*Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", "23");
int result_map = userMapper.deleteByMap(map);
System.out.println("result_map = " + result_map);*/
/**
* ==> Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? )
* ==> Parameters: 1(Long), 2(Long), 3(Long)
* <== Updates: 3
*/
List alist = Arrays.asList(1l, 2l , 3l);
userMapper.deleteBatchIds(alist);
}
@Test
public void testUpdate() {
/**
* ==> Preparing: UPDATE user SET name=?, age=?, email=? WHERE id=?
* ==> Parameters: aa1(String), 23(Integer), 1233@qq.com(String), 3(Long)
* <== Updates: 0
*/
//修改用户信息
User user = new User();
user.setId(3l).setName("aa1").setAge(23).setEmail("1233@qq.com");
userMapper.updateById(user);
}
@Test
public void userSelect() {
//通过id查询用户信息
/**
* ==> Preparing: SELECT id,name,age,email FROM user WHERE id=?
* ==> Parameters: 1(Long)
* <== Total: 0
*/
User user_id = userMapper.selectById(1l);
/**
* ==> Preparing: SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
* ==> Parameters: 1(Long), 2(Long), 3(Long)
* <== Total: 0
*/
List<Long> longs = Arrays.asList(1l,2l,3l);
//根据多个id执行
//select * from user r where r.id in (?,?,?)
List<User> listUser = userMapper.selectBatchIds(longs);
listUser.forEach(System.out::println);
//select * from user where name =? and age = ?
Map<String,Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 20);
List<User> usermap = userMapper.selectByMap(map);
usermap.forEach(System.out::println);
//System.out.println("usermap = " + usermap);
//select * from user
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
自定义功能
XXXmapper.xml
文件中配置自定义的sql
,一般为多表查询。
Service CRUD 接口
说明
1. 通用 Service CRUD
封装IService (opens new window)
接口,进一步封装 CRUD
采用 get 查询单行 、remove 删除、 list 查询集合 、page 分页前缀命名方式区分 Mapper 层避免混淆
2. 泛型 T
为任意实体对象
3. 建议如果存在自定义通用 Service
方法的可能,请创建自己的IBaseService
继承 Mybatis-Plus
提供的基类
对象, Wrapper
为 条件构造器
//实体了
boolean saveBatch(Collection<T> entityList, int batchSize);
@Transactional(
rollbackFor = {Exception.class}
)
//如果有id 修改,没有id则添加
default boolean saveOrUpdateBatch(Collection<T> entityList) {
return this.saveOrUpdateBatch(entityList, 1000);
}
IService
MyBatis-Plus
中有一个接口IService
和其实现类ServiceImpl
,封装了常见的业务层逻辑
创建Service接口和实现类
代补:
UserService
import com.baomidou.mybatisplus.extension.service.IService;
import com.cjw.mybaitsplusdatasource.pojo.User;
public interface UserService extends IService<User> {
}
UserServiceImpl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cjw.mybaitsplusdatasource.mapper.UserMapper;
import com.cjw.mybaitsplusdatasource.pojo.User;
import com.cjw.mybaitsplusdatasource.service.UserService;
import org.springframework.stereotype.Service;
@Service
//@DS(value = "slave_1")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
UserMapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
Mybatis-Plus的常用注解
@Table_name
- 作用:当实体类和表名不一致的时候,设置实体类所对应的表名
@TableName("t_user")
public class User {
- 全局设置表的统一前缀
# 配置mybatis_plus打印日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#默认位置
mapper-locations: classpath*:/mapper/**/*.xml
#设置全局表的前缀 在userMapper.xml中是失败的没有找到t_user表
global-config:
db-config:
table-prefix: t_
@TableId
- 作用:
mybatisplus
默认会将id
作为主键,表名的ID和实体类不对应时
,可以使用@TableId
,将属性所对应的字段指定为主键。
@TableId
private Long uid; //数据库bigint
或者这种
@TableId("uid")
private Long id;
- TableId中的value属性
用于指定主键的字段,当实体类是id
时,而字段名不是id
时。
//value的作用:将属性指定字段名
@TableId("uid")
private Long id;
- TableId中的type属性
- 自定递增:满足两个条件一个是数据库中的主键必须时自增的,另一个条件时
Type指定自增
type
作用设置主键的生成策略
@TableId(value = "uid", type = IdType.AUTO)
private Long id;
- 全局配置主键生成策略
当我们设置了id的值,那么就不会使用雪花算法。
雪花算法
-
背景
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。
数据库的扩展方式主要包括:业务分表、主从复制、数据库分表。 -
数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数量也会达到带台数据库服务器的处理瓶颈,例如,淘宝的几亿用户数据。如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表,示意图如下:
-
垂直分表
垂直分表适合将表中某些不常用且占用了大量空间的列拆分出去。
例如:前面示意图中的nickname和description字段,假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用age和sex两个字段进行查询,而nickname和descriptioin两个字段主要用于展示,一般不会在业务查询中用到。description本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询age和sex时,就能带来一定的性能提升。 -
水平分表
水平分表适合表行数特别大的表,有的公司要求单表行数超过5000万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问特性。对于一些比较复杂的表。可能超过1000万就要分表了;而对于一些简单的表,即使存储数据超过1亿行,也可以不分表。
但不管怎么样,当看到表的数据量达到千万级别时,作为架构就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。
水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理 -
主键自增
- 以最常见的用户ID为例,可以按照1000000的范围大小进行分段,1`9999999放到表1中,10000000~19999999放到表2中,以此类推。
- 复杂点:分段大小的选取,分段大小会导致切分后字表数量过多,增加维护量复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在100万至2000万之间,具体需要根据业务选取合适的分段大小。
- 优点:可以随着数据的增加平滑地扩充新的表,例如:现在的用户是100万,如果增加到1000万,只需要增加新的表就可以了,原有的数据不需要动。
- 缺点:分布不均匀,例如按照1000万来进行分表,有可能某个分段实际存储的数据量只有1条,而另外一个分段实际存储的数据量有1000万条。
- 取模
- 同样以用户ID为例,例如我们一开始就规划了10个数据库表,可以简单地用user_id % 10的值来表示数据所属的数据库表编号,ID为985的用户放到编号为5的子表中,ID为10086的用户放到编号为6的子表中。
- 复杂点: 初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在的问题
- 优点:表分布比较均匀
- 缺点:扩充新的表很麻烦,所有数据都要重分布
- 雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键不重复性,以及相同表的主键的有序性。
- 核心思想:
长度工64bit(一个long型)
首先是一个符号位,1bit标识,由于long基本类型在java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
4bit时间戳(毫秒级),存储的是时间戳的差值(当前时间戳 + 开始时间戳),结果约等于69.73年
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID)。
- 优点: 整体上按照时间自增顺序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
@TableField
1. 将下划线转换为驼峰格式(默认数据库表中user_name,字段为userName)
2. 当字段名称和数据库字段名称不一致时,则使用@TableField
@TableLogic
- 逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:做删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看此条数据记录
- 使用场景:可以进行数据恢复
- 实现逻辑删除
- step1: 数据库中创建逻辑删除状态列,设置默认值为0
当执行查询的时候,就是is_deleted=0,而不会将is_deleted所有结果都查询出来。
条件构造器简介
wrapper介绍
- wrapper: 条件构造抽象类,最顶端父类
- AbstractWrapper:用于查询条件封装,生成sql的where条件
- QueryWrapper: 查询条件封装
- UpdateWrapper: Update条件封装
- AbstractLambdaWrapper: 使用Lambda语法
- LambdaQueryWrapper: 用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper: Lambda更新封装Wrapper
- AbstractWrapper:用于查询条件封装,生成sql的where条件
QueryWrapper
查询、修改、删除都可以使用
组装查询条件
@Test
public void selectListMapper() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
* ==> Parameters: %张三%(String), 20(Integer), 30(Integer)
*/
//查询用户名包含a,年龄在20到30之间,邮箱信息不为null的用户信息。
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.like("name", "张三")
.between("age", 20, 30)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
组装排序条件——orderbyasc 和 orderbydesc
@Test
public void test02() {
/**
* SELECT uid AS id,name,age,email FROM t_user ORDER BY age DESC,uid ASC
*/
//查询用户信息,按照年龄降序排序,若年龄相同,按照id升序排序
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
组装删除条件
@Test
public void deleteWrapper() {
/**
* DELETE FROM t_user WHERE (email IS NULL)
*/
//删除邮箱地址为null的用户信息
//删除可以使用queryWrapper
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result = " + result);
}
实现修改功能
@Test
public void updateWrapperTest() {
/**
* Preparing: UPDATE t_user SET name=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)
* ==> Parameters: 小明(String), test@qq.com(String), 20(Integer), %a%(String)
*/
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 20)
.like("name", "a")
.or()
.isNull("email");
User user = new User();
user.setName("小明")
.setEmail("test@qq.com");
/**
* 第一参数 设置填充内容 set
* 第二参数 设置修改条件 where
*/
int result = userMapper.update(user, queryWrapper);
System.out.println("result = " + result);
}
条件的优先级
@Test
public void test05 () {
/**
* ==> Preparing: UPDATE t_user SET name=?, email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
* ==> Parameters: 小红(String), test@qq.com(String), %a%(String), 20(Integer)
* <== Updates: 0
*/
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like("name", "a")
//lamda consumer接口 Consumer<Param> consumer
//lambda中的条件优先执行
.and(i-> {
i.gt("age", 20)
.or()
.isNull("email");
});
User user = new User();
user.setName("小红")
.setEmail("test@qq.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("result = " + result);
}
组装select字句
@Test
public void test6() {
/**`在这里插入代码片`
* ==> Preparing: SELECT name,age,email FROM t_user
*/
//查询用户的用户名、年龄、邮箱信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age", "email");
List<Map<String, Object>> list = userMapper.selectMaps(queryWrapper);
list.forEach(System.out::println);
}
组装子查询
@Test
public void test7() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user
* WHERE (uid IN (select uid from t_user where uid <= 100))
*/
//查询id小于等于100的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select " +
" uid from t_user where uid <= 100");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
通过UpdateWrapper实现修改功能
使用UpdateWrapper不用使用实体类它本身有set方法,而QueryWrapper则需要使用实体类修改值
@Test
public void test8() {
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper
.like("name", "a")
//lamda consumer接口 Consumer<Param> consumer
//lambda中的条件有限执行
.and(i-> {
i.gt("age", 20)
.or()
.isNull("email");
});
updateWrapper.set("name", "晓红")
.set("email","test@qq.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
}
模拟开发中组装条件的情况
@Test
public void test9() {
//import com.baomidou.mybatisplus.core.toolkit.StringUtils;
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//不为空字符串、不为null、不为空白符
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("name", username);
}
if (ageBegin != null ) {
queryWrapper.ge("age", ageBegin);
}
if (ageEnd != null) {
queryWrapper.le("age", ageEnd);
}
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
使用condition组装条件
@Test
public void test10() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user WHERE (age >= ? AND age <= ?)
* ==> Parameters: 20(Integer), 30(Integer)
*/
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "name", username)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null , "age", ageEnd);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
LambdaQueryWrapper
@Test
public void test11() {
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
//防止字段名写错
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null , User::getAge, ageEnd);
List<User> userList = userMapper.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
LambdaUpdateWrapper
@Test
public void test12() {
/**
* ==> Preparing: UPDATE t_user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
* ==> Parameters: 晓红123(String), test123@qq.com(String), %a%(String), 20(Integer)
*/
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.like(User::getName, "a")
.and(i->i.gt(User::getAge, 20).or()
.isNull(User::getEmail));
lambdaUpdateWrapper.set(User::getName, "晓红123")
.set(User::getEmail, "test123@qq.com");
int result = userMapper.update(null, lambdaUpdateWrapper);
System.out.println("result = " + result);
}
MyBatis-Plus分页插件的配置和使用
分页插件
MyBatis Plus
自带分页插件,只要简单的配置即可实现分页功能。
- 添加配置类
在配置类中添加分页插件,在配置类中可以将配置在application.yml
中的包扫描配置在配置类中。
@Configuration
/**
* 在springboot 使用mybatis需要
* 设置mapper接口和映射文件所在的包
*/
@MapperScan("com.cjw.mybatisplus.mybatisplusdemo.mapper")
public class Mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
- 处理分页设置
@Test
public void test1() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user LIMIT ?
* ==> Parameters: 3(Long)
*/
Page<User> page= new Page<User>(1, 3);
//返回page
userMapper.selectPage(page, null);
System.out.println(page);
}
- 处理分页相关数据获取
@Test
public void test1() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email FROM t_user LIMIT ?
* ==> Parameters: 3(Long)
*/
Page<User> page= new Page<User>(1, 3);
//返回page
userMapper.selectPage(page, null);
/**
* [User(id=4, name=小明, age=21, email=test@qq.com), User(id=5, name=Gille, age=24, email=11155@qq.com)]
* 1
* 1
* 2
* false
* false
*/
System.out.println(page.getRecords());
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getTotal());
System.out.println(page.hasNext());
System.out.println(page.hasPrevious());
}
自定义分页功能
- 自定义实体类别名
# 配置mybatis_plus打印日志
mybatis-plus:
#global-config:
# db-config:
# table-prefix: t_ #设置全局表的前缀
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/**/*.xml #默认位置
#配置类型别名
type-aliases-package: com.cjw.mybatisplus.mybatisplusdemo.pojo
- UserMapper
分页插件作用于我们自定义的分页时,第一个参数必须是Page
//分页插件作用于我们自定义的分页时,第一个参数必须是Page
//@param相当于mapper.xml中的parameterType传递参数
/**
* 通过年龄查询用户信息并分页
* @param page mybatis-plus提供的分页参数,必须第一个位置
* @param age
* @return
*/
// Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
Page<User> selectPageVo( Page<User> page, Integer age);
- userMapper.xml
<!--
Page<User> selectPageVo(@Param("age") Page<User> page, @Param("age") Integer age);
-->
<select id="selectPageVo" parameterType="User" resultType="User">
select uid,name,age,email from t_user
where age > #{age}
</select>
- 测试自定义分页
==> Preparing: SELECT COUNT(*) FROM t_user WHERE age > ?
==> Parameters: 20(Integer)
<== Columns: COUNT(*)
<== Row: 2
<== Total: 1
@SpringBootTest
public class MybatisPlusPageTest {
@Autowired
UserMapper userMapper;
@Test
public void test1() {
Page<User> page = new Page(1, 3);
userMapper.selectPageVo(page, 20);
}
}
乐观锁插件
乐观锁插件 通过每次更新时查询版本号,来进行更新。
场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去吧商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150员,价格太高。可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统,小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格增加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存储了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元,几分钟后,这个商品很快出售了1千多件商品,老板亏了1万多。
乐观锁和悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了,如果被修改过了,则重新取出的被修改后的价格,150元。这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格等操作,也会保证最终的价格是120元。
模拟修改冲突
//创建表结构
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)
value(1, '外星人笔记本', 100);
commit;
乐观锁实现流程
数据库中添加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;
Mybatis-plus实现乐观锁
实体类中添加注解,修改实现类
@Data
@Accessors(chain = true)
@TableName("t_product")
public class Product {
private Long id;
private String name;
private Integer price;
@Version //标识乐观锁版本号字段
private Integer version;
}
配置文件中添加乐观锁插件
Parameter 'MP_OPTLOCK_VERSION_ORIGINAL' not found. Available parameters are [param1, et]
mybatisplus将插件写法更新如下:注释的是旧写法
@Configuration
/**
* 在springboot 使用mybatis需要
* 设置mapper接口和映射文件所在的包
*/
@MapperScan("com.cjw.mybatisplus.mybatisplusdemo.mapper")
public class Mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
//等价于
/* *//**
* 添加分页插件
* @return
*//*
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
return new PaginationInnerInterceptor();
}
*//**
* 添加乐观锁插件
* @return
*//*
@Bean
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}*/
}
测试乐观锁代码
@Test
public void test02() {
//小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productLi.getPrice());
//小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:" + productWang.getPrice());
/* //小李将商品价格+50
productLi.setPrice(100);
productMapper.updateById(productLi);*/
//小李将商品价格+50
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
//小王将商品价格-30 (修改不成功)
productWang.setPrice(productWang.getPrice() - 30);
productMapper.updateById(productWang);
//老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productBoss.getPrice());
/**
* ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
* ==> Parameters: 1(Integer)
* <== Columns: id, name, price, version
* <== Row: 1, 外星人笔记本, 150, 5
* <== Total: 1
*/
}
优化修改流程
@Test
public void test02() {
//小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productLi.getPrice());
//小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:" + productWang.getPrice());
/*//小李将商品价格+50
productLi.setPrice(100);
productMapper.updateById(productLi);*/
//小李将商品价格+50
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
//小王将商品价格-30 (修改不成功)
productWang.setPrice(productWang.getPrice() - 30);
int result = productMapper.updateById(productWang);
if (result == 0) {
//@version只是控制其不进行更新,但是此时小王中获取的数据还是之前的100,因此要重新创建对象
System.out.println("productWang.getPrice() = " + productWang.getPrice());//productWang.getPrice() = 70
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice() - 30);
productMapper.updateById(productNew);
}
//老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + productBoss.getPrice());
}
通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus
通用枚举来实现
数据库中t_user中sex字段
定义枚举类
@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;
}
}
扫描枚举包
# 配置mybatis_plus打印日志
mybatis-plus:
#global-config:
# db-config:
# table-prefix: t_ #设置全局表的前缀
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/**/*.xml #默认位置
#配置类型别名,不区分大小写
type-aliases-package: com.cjw.mybatisplus.mybatisplusdemo.pojo
#扫描通用枚举的包
type-enums-package: com.cjw.mybatisplus.mybatisplusdemo.enums
处理数据添加测试
@Test
public void test01() {
/**
* ==> Preparing: INSERT INTO t_user ( uid, name, sex ) VALUES ( ?, ?, ? )
* ==> Parameters: 1543962141062930433(Long), admin(String), 1(Integer)
* <== Updates: 1
*/
User user = new User();
user.setName("admin")
.setSex(SexEnum.MALE);
int result = userMapper.insert(user);
System.out.println("result = " + result);
}
代码生成器
- 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
- 快速生成
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT", "root", "root")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
//.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
.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
(原库)与mybaits_plus1
(新建),将mybaits_plus
库的product
表移动到mybatis_plus_1
库,这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功。
- 创建数据库及表
create database mybatis_plus_1;
create table t_product
(
id bigint(20) not null comment '主键ID',
name varchar(30) null default null comment '商品名称',
price int default 0 comment '价格',
version int default 0 comment '乐观锁版本号',
primary key (id)
);
insert into t_product (id, name, price)
values(1, '外星人笔记本', 100);
commit;
alter table t_product add COLUMN sex int(2) comment '性别';
select * from t_product;
- 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
- 配置数据源
说明:注释掉之前的数据库连接,添加新配置
#数据源 spring boot中默认所使用的数据源
spring:
datasource:
dynamic:
#设置默认的数据源或者数据源组,默认值即为master
primary: master
#严格匹配数据源,默认false,true未匹配到指定数据源时异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
-
创建用户Service
-
测试
@SpringBootTest
public class mybatisplusTest {
@Autowired
UserMapper userMapper;
@Autowired
UserService userService;
@Test
public void test01() {
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void test02() {
/**
* ==> Preparing: SELECT uid AS id,name,age,email,sex FROM t_user WHERE uid=?
* ==> Parameters: 1(Integer)
* <== Total: 0
*/
User user = userService.getById(1);
System.out.println("user = " + user);
}
}
结果:
1、 都能顺利获取对象,则测试成功
2、如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切换,是不是就能实现读写分离
MyBatisX插件
MyBatis-Plus
为我们提供了强大的mapper和service
模板,能够大大的提高开发效率
但是在真正开发过程中,MyBatis-Plus
并不能为我们解决所有问题,例如一些复杂的SQL
,多表联查,我们就需要自己去编写代码和SQL
语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX
插件
MyBatisX一款基于IDEA的快速开发插件中,为效率而生。
TUser变成了User
insertSelective//当传入