为什么要学习它呢?
MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!
类似组件:JPA、 tk-mapper、MyBatisPlus
简介
官网: 简介 | MyBatis-Plus (baomidou.com) 简化 Mybatis开发
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
特性
地址: 快速开始 | MyBatis-Plus (baomidou.com)
步骤:
步骤
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)
);
-- 真实开发中, version(乐观锁),deleted(逻辑删除),gmt_create,gmt_modified
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
初始化项目
创建Spring Boot项目
添加依赖
<!--Mybatis_Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
说明︰我们使用mybatis-plus 可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus ! 版本的差异!
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true*characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
##mysql8需要增加时区的配置 serverTimezone=GMT%2B8
传统方式pojo-dao(连接mybatis,配置mapper.xml文件 ) -service-controller
使用了mybatis_plus之后
public class User {
//对应数据库中的主键(uuid、自增id、雪花算法、redis. zookeeper )
private Long id;
private String name;
private Integer age;
private String email;
//有参构造 无参构造 set与get方法 toString()方法
}
// 在对应的mapper上面继承基本的类 BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 泛型
//所有的CRUD操作都已经编写完成
//不需要向以前的配置文件
}
//主类扫描接口
@MapperScan("com.qd.mybatis_plus.mapper")
@SpringBootTest
class MybatisPlusApplicationTests {
//继承了Base Mapper,所有的方法都来自父类
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//查询全部
//参数是一个Wrapper,条件构造器
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
问题来了 ?
问题:我们所有的sql是不可见的,我们希望知道他是怎么执行的 ,我们必须要配置日志
## 配置日志 默认的控制台日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
//测试插入
@Test
public void testInsert() {
User user = new User();
user.setName("前度");
user.setAge(3);
user.setEmail("[email protected]");
int i = userMapper.insert(user); //帮我们自动生成id
System.out.println(i); //受影响的行数 1
System.out.println(user);
}
数据库插入的id的默认值为:全局的唯一id
默认 ID_WORKER 全局唯一id
参考文章: 分布式系统唯一ID生成方案汇总 - nick hao - 博客园 (cnblogs.com)
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心:北京,上海等,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增 配置
@TableId(type = IdType.AUTO)
其余的源码解释
public enum IdType {
AUTO(0), //数据库id自增
NONE(1), //未设置主键 必须自己配置id
INPUT(2), //手段输入
ID_WORKER(3), //默认的全局唯一id
UUID(4), //全局唯一id uuid
ID_WORKER_STR(5); // ID_WORKER的字符串表示法
}
//测试更新
public void testUpdate(){
User user = new User();
user.setName("前度666");
user.setAge(3);
user.setEmail("[email protected]");
//updateById 但是参数是一个对象!
//可以通过条件拼接动态sql
userMapper.updateById(user);
}
所有的sql都是自动帮你动态配置的!
创建时间、修改时间!这些个操作一遍都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册∶所有的数据库表: gmt_create
、gmt_modified
所有表必备 需要自动化
方法一:数据库级别 (不建议使用)
create_time
, update_time
private Data createTime;
private Data updateTime;
方法二: 代码级别
//字段添加填充内容
@TableField(fill = FieldFill.INSERT) //查询
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) //查询与更新
private Date updateTime;
编写处理器处理注解即可
handler
包 含有 MyMetaObjectHandler
类 去实现 MetaObjectHandler
@Component //一定不要忘记把处理器注册到IOC容器中!!!
public class MyMetaObjectHandler implements MetaObjectHandler {
private final static Date DATE_TIME = new Date();
//插入时候填充策略
@Override
public void insertFill(MetaObject metaObject) {
//setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
this.setFieldValByName("createTime", DATE_TIME, metaObject);
this.setFieldValByName("updateTime", DATE_TIME, metaObject);
}
//更新时候填充策略
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", DATE_TIME, metaObject);
}
}
4 .测试
乐观锁 : 十分乐观,它总是认为不会出现问题,无论干什么不去上锁 ! 如果出现了问题,再次更新值测
悲观锁 :十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
version、new version
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
-- 乐观锁 : 1 先查询,获得版本号 version = 1
-- A线程
update user set name = "前度" , version = version + 1
where id = 2 and version = 1 ;
-- B线程 抢先完成,这个时候版本号被修改 version = 2
update user set name = "前度" , version = version + 1
where id = 2 and version = 1 ;
乐观锁插件: 乐观锁 | MyBatis-Plus (baomidou.com)
@Version //乐观锁 Version注解
private Integer version;
注册组件
添加 config
包 含有 MyBatisPlusConfig
类
@MapperScan("com.qd.mybatis_plus.mapper") //扫描
@EnableTransactionManagement //开启事务
@Configuration //配置类
public class MyBatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
//测试乐观锁插件 成功
@Test
public void testOptimisticLockerInterceptor1() {
//查询用户修改
User user = userMapper.selectById(1L);
//修改用户信息
user.setAge(20);
//执行更新
userMapper.updateById(user);
}
//----------------------------------------------------------
//测试乐观锁插件 失败 多线程下
@Test
public void testOptimisticLockerInterceptor2() {
//线程1
User user = userMapper.selectById(1L);
user.setAge(21);
//模拟另外一个线程执行插队操作
User user2 = userMapper.selectById(1L);
user2.setAge(22);
userMapper.updateById(user2);
//可以使用自旋锁多次尝试提交
userMapper.updateById(user); //如果没有乐观锁,就会覆盖插队线程的值
}
//结果仍为22
//测试查询
@Test
public void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
//测试批量查询
@Test
public void testSelectById02() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
//测试条件之一查询 map操作
@Test
public void testSelectById03() {
HashMap<String, Object> map = new HashMap<>();
//自定义查询
map.put("name", "前度");
map.put("age", 18); //多个条件
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
分页在网站这一块可太重要了,实现分页的方法有:
如何使用分页插件呢?
官网: 分页 | MyBatis-Plus (baomidou.com)
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
//测试分页查询
@Test
public void testPage() {
//参数一: 当前页
//参数二: 页面大小
//使用了分页插件之后,分页操作非常简单
Page<User> page = new Page<>(2, 5); //第1页 每页5条
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
page.getTotal();//获得总数
}
//测试删除
@Test
public void testDeleteById() {
userMapper.deleteById(1402192921201025026L);
}
//测试通过id批量删除
@Test
public void testDeleteById02() {
userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
}
//测试通过map条件删除
@Test
public void testDeleteById03() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "前端");
userMapper.deleteByMap(map);
}
我们在工作中会遇到一些问题; 逻辑删除
物理删除 : 从数据库中直接移除
逻辑删除 : 在数据库中没有移除,而是通过一个变量来让他失效 ! deleted=0 => deleted=1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
测试
deleted
字段 @TableLogic //逻辑删除
private Integer deleted;
配置逻辑删除组件
//逻辑删除组件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
//注意:高版本无需配置组件 直接下一步即可
yaml
##配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
//测试删除
@Test
public void testDeleteById() {
userMapper.deleteById(1402192921201025026L);
}
可以发现,数据仍在数据库中 并且在查询中会字段拼接该参数 查询不到逻辑删除的字段
PS:以上CRUD操作及其扩展,必须精通,会大大提高效率
这里不作过多配置 xml 的使用 请前往下面两篇博客学习
wrapper
十分重要 我们写一些复杂的sql就可以使用它来替代!
官网: 条件构造器 | MyBatis-Plus (baomidou.com)
@Test
public void contextLoads() {
//查询name不为空 并且邮箱不为空 年龄大于等于3岁的用户
QueryWrapper<User> Wrapper = new QueryWrapper<>();
Wrapper.isNotNull("name")
.isNotNull("email")
.ge("age", 20); //大于等于
userMapper.selectList(Wrapper).forEach(System.out::println);
}
@Test
public void contextLoads02() {
//查询name为前度
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "前度");
System.out.println(userMapper.selectOne(wrapper)); //查询一个 查询多个用list,map
}
@Test
public void contextLoads03() {
//查询年龄在20到30之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 30); //区间
Integer count = userMapper.selectCount(wrapper); //查询结果数
System.out.println(count);
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
@Test
public void contextLoads04() {
//子查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id<3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
@Test
public void contextLoads05() {
//通过id进行排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
更多使用方法直接查看官方文档即可
run前开启MySQL 服务!!! 有些配置需要具体更改建议写在 test
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
import java.util.List;
public class AutoGenerateTest {
public static void main(String[] args) {
// 创建generator对象
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir"); // 项目根目录不可修改
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("qiandu"); // 作者信息
gc.setOpen(false); // 打开文件夹
gc.setFileOverride(false);// 是否覆盖
gc.setServiceName("%sService"); // Service层接口 去前缀
gc.setIdType(IdType.AUTO);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(false); // 设置Swagger2文档
mpg.setGlobalConfig(gc);
// 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL); // 枚举类型 选择数据库
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setUrl("jdbc:mysql://localhost:3306/ssmdb?serverTimezone=Asia/Shanghai");
// jdbc:mysql://localhost:3306/数据库名称?serverTimezone=Asia/Shanghai
mpg.setDataSource(dsc);
// 设置包信息
PackageConfig pc = new PackageConfig();
pc.setParent("com.qd"); //包
pc.setEntity("pojo");
pc.setMapper("mapper");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 修改*mapper.xml文件
// 注入自定义配置,可以在 VM 中使用 cfg.abc 【可无】
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
}
};
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
// 生成模块化mapper文件
return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "/" +
tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 策略配置 与数据库相关
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tb_goods"); // 表名 改!!!
strategyConfig.setTablePrefix("tb_"); // 去除表前缀tb_
strategyConfig.setNaming(NamingStrategy.underline_to_camel); // 表名映射实体类
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); // 字段映射实体类
strategyConfig.setEntityLombokModel(true); // 加载Lombok
strategyConfig.setLogicDeleteFieldName("deleted"); // 逻辑删除 具体看情况!
// 自动填配置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategyConfig.setTableFillList(tableFills);
// 乐观锁配置
strategyConfig.setVersionFieldName("version");
// 设置controller
strategyConfig.setRestControllerStyle(true); // restful风格
strategyConfig.setRestControllerStyle(true); // 下划线命名
mpg.setStrategy(strategyConfig);
// 运行
mpg.execute();
System.out.println("====================【 PS:将会在mapper层与资源目录共同生成mapper文件,请保留合适的文件!!!】====================");
}
}
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan("com.qd.mybatis_plus.mapper") //扫描
@EnableTransactionManagement //开启事务
@Configuration //配置类
public class MyBatisConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
//分页组件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
//逻辑删除组件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
1、导包
<!--freemarker模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--c3p0 druid连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--mybatis-plus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>1.18.20</version>
</dependency>
<!--测试test依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--导入aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
构建排除
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
或
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
<exclude>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclude>
<exclude>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
android系统增加framework服务在 frameworks/base/core/java/android/os目录中增加对应的aidl文件在frameworks/base/Android.mk文件中增加定义的aidl文件 LOCAL_SRC_FILES +=mmm frameworks/base 此时会自动根据aidl文件生成对应的stub接口在frameworks/base/
spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。比如,我们判断一个输入参数是否合法,可以用如下方式一 基础使用因为spring-boot已经引入了基础包,所以直接使用就可以了1 首先在controller上声明需要对数据进行校验@RequestMapping(value="/url.json",method= {R...
方法一:大部分手动实现//P5734 【深基6.例6】文字处理软件//#define LOCAL#include <iostream>#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>#include <cctype>#define inf 0x3f3f3f3f#define eps 1e-6using namespac.
看到这本书的封面插画,你是否以为即将面对一本童话故事书?实际上,这本书“意义重大”,绝非童话,是用“羊吃草”的比喻给我们解释:大数据时代,人工智能在隐私安全前提下实现数据合作的最优解决方案。这就是世界上第一本“联邦学习”专著——《Federated Learning》(英文版)/《联邦学习》(中文版),由微众银行首席人工智能官杨强教授及人工智能部刘洋、程勇、康焱、陈天健、于涵等多位人工智能领域顶级专家历时两年,共同编撰而成。2019年12月,《Federated Learning》由美国Morgan
水平居中和垂直居中cssThere are many ways to align HTML elements with CSS. One of the most common things developers struggle with is trying to center an element in the middle of the page. 有许多方法可以将HTML元素与CSS对齐。 ...
错误提示1/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/MySQL_python-1.2.3-py2.6-macosx-10.6-x86_64.egg/_mysql.so: mach-o, but wrong architecture
1.背景要识别两张图片是否相似,首先我们可能会区分这两张图是人物照,还是风景照等......对应的风景照是蓝天还是大海......做一系列的分类。从机器学习的的角度来说,首先要提取图片的特征,将这些特征进行分类处理,训练并建立模型,然后在进行识别。但是让计算机去区分这些图片分别是哪一类是很不容易的,不过计算机可以知道图像的像素值的,因此,在图像识别过程中,通过颜色特征来识别是相似图片是...
虽说是CSDN,但因为处理的过程中要用到病理图片,所以这里简单的介绍一下病理图像的标准化。首先介绍一下我们的病理图像。病理图像就是我们高中时候用的玻片在扫描仪下拍摄的高分辨率图像。具体制作过程可以参见百度。https://wenku.baidu.com/view/60b5d0fe2af90242a995e58a.html病理图像可以帮助医生对病人做出诊断。通过病理图像,我们可以看到肿瘤细胞的.....................
人生最低的境界是平凡,其次是超凡脱俗,最高是返璞归真的平凡。Kf_account.cs代码: public partial class Kf_account : Form { private readonly DataTable adt_user = new DataTable(); private readonly string as_INIFile =
这篇博文来讲解Spring Boot 和邮件发送集成的示例。
Sharepoint2010中引入了客户端对象模型(COM) 来加强外部对sharepoint站点信息的访问(sharepoint2007只能通过web service)SharePoint中有3种客户端对象模型:ECMAScript.NET托管客户端对象模型Silverlight客户端对象模型3种客户端对象模型都通过Client.svc来实现与服务器的交互,...