为什么要学习它?MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成
JPA、tk-mapper、MyBatisPlus
MyBatis 本来就是简化 JDBC 操作!
可以理解MybatisPlus是Mybatis的增强工具,在Mybatis的基础上只做增强不做该变,为简化开发、提高效率而生
任何能使用
MyBatis
进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
使用第三方组件:
创建数据库 myabtis_plus
创建 user 表
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)
);
新增数据
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');
编写项目,初始化项目!使用 SpringBoot 初始化!
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
我们使用 mybatis-plus 可以节省我们大量的代码,尽量不要同时导入 mybatis 和 mybatis-plus!版本的差异!
连接数据库!这一步和 mybatis 相同!
spring:
datasource:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
# useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
pojo-dao(连接mybatis,配置mapper.xml文件)-service-controller —— 传统方式
使用了 mybatis-plus 之后
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}
mapper接口
/**
* 在对应的 Mapper 上面实现基本的接口 BaseMapper
* 继承了BaseMapper,所有的方法都来自父类
* 当想要添加方法时,也可以编写自己的扩展方法
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 当继承 BaseMapper后,CRUD已经编写完成
*/
}
注意点,我们需要在主启动类上去扫描我们的mapper包下的所有接口
@MapperScan("com.renex.mybatisplus.mapper")// 扫描 mapper 包
@SpringBootApplication
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
使用
@SpringBootTest
class MybatisplusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// 参数是一个Wrapper,条件构造器,这里不用就先null
// 查询所有用户
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
结果:
我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,所以必须看日志
打开配置日志:
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springboot项目,配置文件一般为:application.yaml
插入代码
@Test
public void testInsert(){
User userOne = new User();
userOne.setAge(12);
userOne.setName("张三");
userOne.setEmail("user@example.com");
int reult = userMapper.insert(userOne);
System.out.println(reult);
System.out.println(userOne);
}
当数据库中未设置主键递增,mybatisPlus 自动会为当前插入命令生成主键。
// ASSIGN_ID 默认选择雪花算法
@TableId(type = IdType.ASSIGN_ID)
private Integer id;
雪花算法是一種生成分布式全局唯一ID的算法,生成的ID稱為Snowflake IDs或snowflakes。這種算法由Twitter創建,並用於推文的ID。Discord和Instagram等其他公司採用了修改後的版本。
一個Snowflake ID有64位元。前41位是時間戳,表示了自選定的時期以來的毫秒數。
public enum IdType {
AUTO(0),// 数据库 ID 自增
NONE(1),// 未设置主键
INPUT(2),// 手动输入
ASSIGN_ID(3),// 默认全局唯一 ID (使用雪花算法)
ASSIGN_UUID(4);// 默认全局唯一 ID :/ 通用唯一识别码
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
我们需要配置主键子层:(缺一不可)
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
}
一旦更改未手动输入ID后,就需要自己配置ID
@TableId(type = IdType.INPUT)
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@TableId(type = IdType.INPUT)
private Integer id;
private String name;
private Integer age;
private String email;
}
@Test
@DisplayName("测试更新")
public void testUpdate() {
User userOne = new User();
userOne.setId(6);
userOne.setName("李四");
int row = userMapper.updateById(userOne);
System.out.println(row);
}
所有的sql都是自动动态配置的!
创建时间、修改时间!这些操作都是自动化完成的,不要手动更新!
在阿里巴巴开发手册中规定:
所有的数据表格都有一个创建时间:gmt_create
还有一个修改时间:gmt_modified
这两个字段几乎所有的表都要配置上!而且需要自动化!
删除数据库的默认值,更新操作!
实体类字段属性上需要增加注解
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
FieldFill源码
public enum FieldFill {
DEFAULT,
INSERT,
UPDATE,
INSERT_UPDATE;
private FieldFill() {
}
}
编写处理器来处理注解即可
/**
* 需要将该处理器注入斤IOC容器中
*/
@Slf4j
@Component
public class MyMetaHandler implements MetaObjectHandler {
/**
* 插入字段时启用的方法
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("Insert starting.....");
// 设置字段信息
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
/**
* 修改字段时启动的方法
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("Update starting.....");
// 设置修改字段信息
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
测试插入、更新即可。
在数据库表的创建中,通常会有version这个字段来得到当行数据的版本信息
-- 乐观锁:1、先查询,获得版本号 version=1
-- A
update user set name = "11",version = version+1
where id=2 and version=1;
-- B 线程抢先完成,这个时候version=2,会导致 A 修改失败
update user set name = "11",version = version+1
where id=2 and version=1;
给数据库中增加version字段!
修改实体类信息,并添加上乐观锁注解
@Version // 乐观锁注解
private Integer version;
注册组件
@EnableTransactionManagement// 管理事务开启
@MapperScan("com.renex.mybatisplus.mapper")
@Configuration// 配置类
public class MybatisConfig {
// 注册乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试
@Test
@DisplayName("测试乐观锁成功")
public void testOptimisticLocaker(){
User user = userMapper.selectById(6);
user.setName("测试乐观锁");
user.setAge(200);
userMapper.updateById(user);
}
@Test
@DisplayName("测试乐观锁失败")
public void testOptimisticLocaker02(){
User user = userMapper.selectById(6);
user.setName("失败");
user.setAge(100);
User user2 = userMapper.selectById(6);
user2.setName("成功");
user2.setAge(150);
userMapper.updateById(user2);
// 在没有 乐观锁 的情况下,这条代码将会覆盖原有的数据
userMapper.updateById(user);
}
@Test
@DisplayName("全部查询")
public void select01(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
@Test
@DisplayName("单个查询")
public void select02(){
User user = userMapper.selectById(5);
System.out.println(user);
}
@Test
@DisplayName("批量查询")
public void select03(){
// 批量查询的数值,如果是 int 类型,那么查询出来的结果会自动进行排序
List<User> users = userMapper.selectBatchIds(Arrays.asList(2, 4, 6, 1));
users.forEach(System.out::println);
}
@Test
@DisplayName("按 条件查询")
public void select04(){
// 这种条件查询等于是 && 追加条件,而不是 ||
HashMap<String, Object> sqlMap = new HashMap<>();
sqlMap.put("name","Jone");
sqlMap.put("age","12");
List<User> users = userMapper.selectByMap(sqlMap);
System.out.println("-------------------------------------");
users.forEach(System.out::println);
}
配置拦截器即可
@EnableTransactionManagement// 管理事务开启
@MapperScan("com.renex.mybatisplus.mapper")
@Configuration// 配置类
public class MybatisConfig {
/**
* 配置 mybatisplus 拦截器
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 注册分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
使用 selectPage 方法
@Test
@DisplayName("分页查询")
public void select05(){
// 第一个参数:第几页;第二个参数:展示的条数
Page<User> objectPage = new Page<>(1, 3);
userMapper.selectPage(objectPage, null);
System.out.println("-----------------------------------------");
// 获取page的所有记录,然后遍历打印
objectPage.getRecords().forEach(System.out::println);
}
@Test
@DisplayName("基础删除")
public void test06(){
int row = userMapper.deleteById(5);
System.out.println("删除成功:"+row);
}
@Test
@DisplayName("批量删除")
public void test07(){
int row = userMapper.deleteBatchIds(Arrays.asList(1, 2));
System.out.println("删除成功:"+row);
}
@Test
@DisplayName("条件删除")
public void test08(){
HashMap<String, Object> delMap = new HashMap<>();
delMap.put("age",12);
delMap.put("name","时间自动填充");
int row = userMapper.deleteByMap(delMap);
System.out.println("删除成功:"+row);
}
管理员可以查看被删除的记录!防止数据的丢失。类似于回收站
在数据表中添加一个deleted字段
实体类中增加属性
@TableLogic // 逻辑删除
private Integer deleted;
配置 yaml (新版本)
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
测试删除
在 3.2 版本后,MP 去除了性能分析插件! 如果需要使用sql分析,那么可以使用数据源第三方的sql分析
Wrapper,十分重要
通过Wrapper,我们可以使用以java代码的方式进行条件配置。 这意味着我们不需要再手动更改sql代码进行where条件判断了!但还是有个缺陷,无法针对多表联查的条件限定。
测试一:
@Test
@DisplayName("查询")
void test01(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name") // 查询列名name中的值不为空的数据
.isNotNull("email") // 查询列名email部位哦那个的数据
.ge("age",12);// 查询列名age中数据大于等于12的数据
List<User> users = userMapper.selectList(wrapper);
System.out.println("------------------------------------------------------------");
users.forEach(System.out::println);
System.out.println("------------------------------------------------------------");
}
测试二:
@Test
@DisplayName("查询2")
void test02(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Sandy").eq("deleted","0");
User users = userMapper.selectOne(wrapper);
System.out.println("------------------------------------------------------------");
System.out.println(users);
System.out.println("------------------------------------------------------------");
}
测试三
@Test
@DisplayName("查询3")
void test03(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);// 区间。?-?之间
Long users = userMapper.selectCount(wrapper);
System.out.println("------------------------------------------------------------");
System.out.println("满足条件的有: "+users+" 行");
System.out.println("------------------------------------------------------------");
}
测试四:
@Test
@DisplayName("查询4")
void test04(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 左 %e
// 右 e%
// 左和右 %e%
wrapper.notLike("name","e")
.likeRight("email","t");// 以t开头
List<Map<String, Object>> users = userMapper.selectMaps(wrapper);
System.out.println("------------------------------------------------------------");
users.forEach(System.out::println);
System.out.println("------------------------------------------------------------");
}
测试五:
@Test
@DisplayName("连接查询-查询5")
void test05(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 内连接
wrapper.inSql("id","select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
System.out.println("------------------------------------------------------------");
objects.forEach(System.out::println);
System.out.println("------------------------------------------------------------");
}
测试六:
@Test
@DisplayName("查询6")
void test06(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");// 根据列id来左倒序排序
List<User> objects = userMapper.selectList(wrapper);
System.out.println("------------------------------------------------------------");
objects.forEach(System.out::println);
System.out.println("------------------------------------------------------------");
}
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>最新版本</version>
</dependency>
注意:
当前包未传递依赖 MP 包,需要自己引入!
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
FastAutoGenerator.create(DATA_SOURCE_CONFIG)
// 全局配置
.globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride())
// 包配置
.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.entityBuilder().enableLombok().addTableFills(
new Column("create_time", FieldFill.INSERT)
).build())
/*
模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
.templateEngine(new BeetlTemplateEngine())
.templateEngine(new FreemarkerTemplateEngine())
*/
.execute();
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
详情配置查看