一:Mybtis-plus介绍
Mybatis本来就是简化JDBC操作的!
官网:https://baomidou.com/ MybatisPlus,为简化开发而生
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
二、快速入门
-
建立数据库和表
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、gem_mo 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');
-
导入依赖
<!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mybatis-plus 是自己开发的,非官方的!--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version> </dependency>
-
application.properties
# mysql 5 驱动不同 com.mysql.jdbc.Driver # mysql 8 驱动不同 com.mysql.cj.jdbc.Driver、需要增加时区的配置 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
-
application.yaml
# 配置日志(默认控制台输出) mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
编写实体类
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private String name; private int age; private String email; }
-
主入口扫描所有mapper接口
@MapperScan("com.luffy.mybatisplus.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() { List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
三、CRUD扩展
3.1、雪花算法
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。
优点:
- 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
- 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
- 不依赖第三方库或者中间件。
- 算法简单,在内存中进行,效率高。
缺点:
- 依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。
雪花算法的原理就是生成一个的 64 位比特位的 long 类型的唯一 id。
- 最高 1 位固定值 0,因为生成的 id 是正整数,如果是 1 就是负数了。
- 接下来 41 位存储毫秒级时间戳,2^41/(1000606024365)=69,大概可以使用 69 年。
- 再接下 10 位存储机器码,包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 台机器。
- 最后 12 位存储序列号。同一毫秒时间戳时,通过这个递增的序列号来区分。即对于同一台机器而言,同一毫秒时间戳下,可以生成 2^12=4096 个不重复 id。
3.2、@TableId注解
@TableId注解是专门用在主键上的注解,如果数据库中的主键字段名和实体中的属性名,不一样且不是驼峰之类的对应关系,可以在实体中表示主键的属性上加@Tableid注解,并指定@Tableid注解的value属性值为表中主键的字段名既可以对应上。
使用方式
参数类型 | 作用 |
---|---|
AUTO | 数据库自增 |
NONE | MP set主键,雪花算法实现 |
INPUT | 需要手动赋值 |
ASSIGN_ID | MP分配ID,Long、Integer、String |
ASSIGN_UUID | 分配UUID,String |
实例
// 对应数据库的主键(uuid,自增id,雪花算法,redis,zookeeper)
@TableId(type = IdType.AUTO)
private Long id;
3.3、自动填充
创建时间、修改时间!这些个操作一遍都是自动化完成,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create\gmt_modified几乎所有的表都要配置上!而且需要自动化
方式一:数据库级别 (这种方法不支持,因为大公司不会让改动数据库)
在表中新增字段 create_time 、update_time(默认CURRENT_TIMESIAMP)
注意:mysql5.5的日期函数是now()
mysql5.6以上的支持CURRENT_TIMESIAMP
方式二:代码级别
-
实体类上的属性需要增加注解@TableField
-
把数据库的默认值删除
实体类注解例子
//创建时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
编写处理器来处理这个注释
@Slf4j
@Component //把处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("Start insert fill.... ");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("Start update fill.... ");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
3.4、乐观锁
乐观锁:顾名思义十分乐观,它总是被认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新测试
悲观锁:顾名思义十分悲观,它总是出现问题,无论干什么都会上锁!再去操作!
乐观锁执行流程
- 取出当前记录Version
- 更新时判断Version值
- 如果在更新时Version值不一致了,代表数据正在进行并发,值已被更改
- 后续更新的用户会更新失败,只会成功一个
测试乐观锁
-
数据库增加Version字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Z4iCnPu-1662874822525)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220906162216347.png)]
-
实体类对应数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K1HgKKX0-1662874822525)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220906162239568.png)]
-
编写乐观锁配置类
@MapperScan("com.luffy.mybatisplus.mapper") @Configuration//配置类 @EnableTransactionManagement//事务 public class MyBatisPlusConfig{ //mybatisPlus3.4.3.1写法 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //注册乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
-
测试
//测试乐观锁 线程下 更新一次后 version就会变成2 @Test void OptimisticLocker() { //单线程 //查询用户信息 User user=userMapper.selectById(1L); //修改用户信息 user.setName("xioajj"); user.setAge(21); //执行更新操作 userMapper.updateById(user); } //测试乐观锁 多线程下 @Test void OptimisticLockerD() { //第一线程 //查询用户信息 User user=userMapper.selectById(1L); //修改用户信息 user.setName("xioa2"); user.setAge(21); //第二线程 模拟另外一个线程执行插队操作 //查询用户信息 User user1=userMapper.selectById(1L); //修改用户信息 user1.setName("xiao3"); user1.setAge(21); userMapper.updateById(user1); //自旋锁来多次尝试提交 userMapper.updateById(user); //如果没有乐观锁 就会覆盖插队线程的值 }
3.5、查询操作
//查询全部
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
//测试批量查询
@Test
public void testSelectByBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
//按条件查询之一使用map操作
@Test
public void testSelectByBatchIds(){
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name","我是路飞");
hashMap.put("age","19");
List<User> users = userMapper.selectByMap(hashMap);
users.forEach(System.out::println);
}
3.6、分页功能
-
MyBatisPlusConfig类
@MapperScan("com.luffy.mybatisplus.mapper") @Configuration//配置类 @EnableTransactionManagement//事务 public class MyBatisPlusConfig{ //mybatisPlus3.4.3.1写法 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //注册乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //分页插件 我们使用的是mysql数据库 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
-
测试方法
//测试分页查询 @Test public void testPage(){ Page<User> userPage = new Page<>(1,5); userMapper.selectPage(userPage,null); //获取查出的记录 List<User> records = userPage.getRecords(); records.forEach(System.out::println); //获取总数 System.out.println(userPage.getTotal()); }
3.7、删除操作
//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1L);
}
//通过id批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList("1566981460006752259","1566981460006752260"));
}
//通过map删除
@Test
public void testDeleteMap(){
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name","xiao3");
userMapper.deleteByMap(hashMap);
}
3.8、逻辑删除
管理员可以查看被删除的记录!防止数据的丢失!类似于回收站!
-
物理删除:从数据库中直接移除
-
逻辑删除:在数据库中没有被移除,而是通过一个变量让他生效!deleted=0 --> deleted=1
-
在数据库表中增加一个deleted字段(默认值为0)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-McFXEjSX-1662874822526)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220911111424437.png)]
-
实体类中添加属性
@TableLogic // 逻辑删除 private Integer deleted;
-
application.yaml
mybatis-plus: # 配置逻辑删除 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置实体类字段上加上@TableLogic注解) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
-
删除测试(走的是更新操作,不是删除操作,会将deleted字段设置为1,逻辑上删除)
//测试删除 @Test public void testDeleteById(){ userMapper.deleteById(1L); }
四、性能更新插件
-
MP也提供了性能分析插件,如果超过这个时间就停止运行!(测试慢sql)
-
该插件只用于开发环境,不建议生产环境使用
-
旧版本使用方法(新版本另寻)
使用步骤
-
导入插件
/** * SQL执行效率插件 */ @Bean @Profile({"dev","test"})// 设置 dev test 环境开启 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100);// ms 设置sql执行的最大时间,如果超过就停止 performanceInterceptor.setFormat(true); return new PerformanceInterceptor(); }
-
测试
@Test void contextLoads(){ //参数是一个Wrapper,条件结构器,这里先不用,填null //查询全部用户 List<User> users =userMapper.selectList(null); user.forEach(System.out::println); }
-
输出
五、条件构造器
wrapper(我们写一些复杂的sql就可以使用它来代替)
@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;
//查询name和邮箱不为空,年龄大于12岁的用户
@Test
void testGe() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.isNotNull("name").isNotNull("email").ge("age",12);
userMapper.selectList(userQueryWrapper).forEach(System.out::println);
}
//查询名字为我是索隆
@Test
public void testEq() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name","我是索隆");
System.out.println(userMapper.selectOne(userQueryWrapper));
}
//查询年龄在10-20岁之间
@Test
void testBetWeen() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.between("age",10,20);
userMapper.selectList(userQueryWrapper);
}
//模糊查询
@Test
public void testLike() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeLeft("name", "索隆"); //name like '%索隆'
List<User> userList = this.userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
//内查询(可以多表联查)
@Test
public void testInSql() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id < 5");
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
//排序
@Test
public void testOrderBy() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.orderByDesc("id");
userMapper.selectList(userQueryWrapper).forEach(System.out::println);
}
}
六、代码生成器
-
好离谱
-
写完前端页面、数据库,使用代码生成器一键生成所有后端初始代码
pom.xml
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus 是自己开发的,非官方的!-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<!--MybatisPlus代码生成器 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--velocity依赖-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--3.0.7版本移除 对 mybatis-plus-generator 包的依赖,自己按需引入,尽在还需要导入模板依赖,-->
<!--generator依赖-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
<scope>test</scope>
</dependency>
生成代码
public class LuffyCode {
public static void main(String[] args) {
// 需要构建一个 代码自动生成器 对象
AutoGenerator mpg = new AutoGenerator();
// 1、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");//获取项目路径
gc.setOutputDir(projectPath+"/src/main/java");//设置路径
gc.setAuthor("Luffy");//设置作者
gc.setOpen(false);//是否打开资源管理器
gc.setFileOverride(false);// 是否覆盖原来生成的
gc.setServiceName("%sService");// 去Service的I前缀
gc.setIdType(IdType.ASSIGN_ID);//雪花算法自动生成id
gc.setDateType(DateType.ONLY_DATE);//日期格式
gc.setSwagger2(true);//配置swagger
mpg.setGlobalConfig(gc);//全局配置
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mp?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);//设置全局配置
//3、包的配置
PackageConfig pc = new PackageConfig();
//只需要改实体类名字 和包名 还有 数据库配置即可
pc.setModuleName("MybatisPlus");
pc.setParent("com.luffy");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);//全局配置
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user");//设置要映射的表名(逗号多配置)
strategy.setNaming(NamingStrategy.underline_to_camel);//下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//下划线转驼峰命名
strategy.setEntityLombokModel(true);// 自动lombok;
strategy.setLogicDeleteFieldName("deleted");//逻辑删除字段
// 时间自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime); tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);//controller是Restful风格
strategy.setControllerMappingHyphenStyle(true);// localhost:8080/hello_id_2
mpg.setStrategy(strategy);//全局配置
//执行全局配置
mpg.execute();
}
}