所有的CRUD代码都可以自动完成!
JPA tk-mapper MyBaitsPlus三者类似
偷懒的
简介
是什么?MyBaits本来就是简化JDBC操作的
官网:https://baomidou.com/,让MyBaits更加简单
特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作BaseMapper<>强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求,以后的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 操作智能分析阻断,也可自定义拦截规则,预防误操作地址:https://baomidou.com/guide/quick-start.html
使用第三方组件:
导入对应的依赖研究依赖如何配置代码如何编写提高扩展技术能力步骤
创建数据库mybatis_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) ); -- 真实开发中,version(乐观锁),deleted(逻辑删除),gmt_create,gmt_motified 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>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <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.0</version> </dependency>说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!版本的差异
连接数据库!这一步和mybatis相同
注意
mysql 5 驱动不同 com.mysql.jdbc.Driver
mysql 8 驱动不同,需要增加时区的配置 com.mysql.cj.jdbc.Driver
高版本兼容低版本
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/mybatis-plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver假的6.传统方式 pojo-dao(连接mybatis,配置mapper.xml文件)-servie-controller
使用了mybatis-plus之后
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { private long id; private String name; private Integer age; private String email; }mapper接口
//在对应的mapper继承基本的类 BaseMapper @Repository //代表持久层 public interface UserMapper extends BaseMapper<User> { //所有的CRUD编写完成,不需要配置一大堆文件 }注意点:我们需要在主启动类上去扫描mapper包下的所有接口@MapperScan("com.yang.mybatis_plus.mapper")
测试类中测试
@SpringBootTest class MybatisPlusApplicationTests { //继承了BaseMapper,所有方法都来自父类,我们也可以编写自己的扩展方法 @Autowired private UserMapper userMapper; @Test void contextLoads() { //参数是一个Wrapper,条件构造器,这里我们先不用 //查询全部用户 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } } 结果查询完毕思考问题
SQL谁帮我们写的?MyBatisPlus方法哪里来的?MyBaitsPlus写好的?我们所有的sql是不可见的,我们希望知道如何执行,所以必须要看日志
上线去掉(时间消耗大)
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl数据库插入的id默认值为:全局的唯一ID
分布式唯一id生成策略:https://www.cnblogs.com/haoxinyue/p/5208136.html
默认:ID_WORKER 全局唯一ID
雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0,可以保证几乎全球唯一
主键自增
我们需要配置主键自增:
实体类主键字段上@TableId(type = IdType.AUTO)数据库字段一定要是自增的再次测试插入即可其余的源码解释
public enum IdType { //数据库id自增 AUTO(0), //未设置主键 NONE(1), //手动输入 INPUT(2), ASSIGN_ID(3), ASSIGN_UUID(4), /** @deprecated */ @Deprecated //默认的全局id ID_WORKER(3), /** @deprecated */ @Deprecated //截取字符串,ID_WORKER的字符串表示法 ID_WORKER_STR(3), /** @deprecated */ @Deprecated //全局唯一的id UUID(4); private final int key; private IdType(int key) { this.key = key; } public int getKey() { return this.key; } }所有的SQL都是帮你动态配置的
@Test public void testUpdate() { User user = new User(); //通过条件自动拼接动态sql user.setId(5L); user.setName("关注我,杨剑"); user.setAge(12); //注意:updateById 但是参数是对象! int i = userMapper.updateById(user); System.out.println(i); }创建时间,修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新
阿里巴巴手册:所有的数据库表:gmt_create创建时间,gmt_modified,几乎所有的表都要配置上!而且需要自动化
方式一:数据库级别(工作中不允许这样做)
在表中新增字段,create_time,update_time
类型为timestamp,默认值为CURRENT_TIMESTAMP
再次测试插入方法,我们需要把实体类同步
private Date createTime; private Date updateTime;方式二:代码级别
删除数据库的默认值,更新操作
在实体类字段属性上需要增加注解
//字段添加填充内容 @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====="); //setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)" 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); } }测试插入
测试更新,观察时间即可。
在面试过程中,经常会被问到乐观锁,悲观锁,
乐观锁:顾名思义,十分乐观,他总是认为不会出现问题,无论干什么都不去上锁,如果出现了问题再去加锁,再次更新值测试
悲观锁:顾名思义十分悲观,他总是认为出现问题,无论干什么都会上锁!再去操作!
这里主要讲解乐观锁机制!
乐观锁实现方式:
取出记录时,获取当前version更新时,带上这个version执行更新时, set version = newVersion where version = oldVersion如果version不对,就更新失败 乐观锁, 1.先查询获得版本号 version=1 --A update user set name = "yangjian",version = version+1 where id = 2 and version=1 --B线程抢先完成,这个时候版本号被修改了,version = 2,导致A失败测试一下MP的乐观锁插件
给数据库中增加version字段
同步实体类 对应字段
@Version //乐观锁version注解 private Integer version;注册组件
//扫描mapper文件夹 @MapperScan("com.yang.mybatis_plus.mapper") @EnableTransactionManagement @Configuration //配置类 public class MyBatisPlusConfig { //注册mybatis插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }测试乐观锁
//测试乐观锁成功,单线程 @Test public void testOptimisticLocker() { //查询用户信息 User user = userMapper.selectById(1L); //修改用户信息 user.setName("yangjian"); user.setEmail("213321312@qq.com"); //执行更新操作 userMapper.updateById(user); } //测试乐观锁失败,多线程下 @Test public void testOptimisticLocker2() { //线程 1 User user = userMapper.selectById(1L); user.setName("yangjian"); user.setEmail("213321312@qq.com"); //模拟另一个线程执行了插队操作 User user2 = userMapper.selectById(1L); user2.setName("yangjian2"); user2.setEmail("213321312@qq.com2"); userMapper.updateById(user2); //自旋锁来尝试多次提交 userMapper.updateById(user);//如果没有乐观锁就会覆盖插队线程的值! }分页在网站使用的十分之多
原始的limit分页pageHelper插件MP也内置了分页插件如何使用!
配置拦截器组件即可
//分页插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }直接使用page对象即可
//测试分页查询 @Test public void testPage(){ /* 参数一:当前页 参数二:页面大小 使用分页插件后变得简单了 */ Page<User> userPage = new Page<>(1,5); userMapper.selectPage(userPage,null); userPage.getRecords().forEach(System.out::println); System.out.println(userPage.getTotal()); }根据id删除记录
//测试删除 @Test public void testDeleteById(){ userMapper.deleteById(1L); }通过id批量删除
//通过id批量删除 @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(2L,3L,4L)); }通过map删除
//通过map删除 @Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("name","杨剑"); userMapper.deleteByMap(map); }我们在工作中会遇到一些问题:逻辑删除
物理删除:从数据库中直接移除
逻辑删除:在数据库中没有被移除,而是通过一个变量让他失效
管理员可以查看被删除的记录,防止数据的丢失,类似于回收站
测试一下:
在数据表中增加一个deleted字段
实体类中增加属性
@TableLogic //逻辑删除 private Integer delete;逻辑删除的配置
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)测试一下删除[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-88hUmxOl-1601861794500)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201004202803255.png)]
本质上执行的为update操作,值已经改变了,但未被删除再次查询时会自动拼接上 and deleted = 0,不可以查到被逻辑删除的字段以上所有的CRUD操作及其扩展操作都必须精通掌握,会大大提高效率
我们在平时的开发中会遇到一些慢sql。测试,druid
MP也提供了性能分析插件,如果超过这个时间就停止运行
依赖引入
<!-- https://mvnrepository.com/artifact/p6spy/p6spy --> <dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>配置yaml
spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:h2:mem:test ...spy.properties配置
#3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory #3.2.1以下使用或者不配置 #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 #driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2注意!
driver-class-name 为 p6spy 提供的驱动类url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址打印出sql为null,在excludecategories增加commit批量操作不打印sql,去除excludecategories中的batch批量操作打印重复的问题请使用MybatisPlusLogFactory (3.2.1新增)该插件有性能损耗,不建议生产环境使用。十分重要:Wrapper
我们写一些复杂
测试1
@Test void contextLoads(){ //查询name不为空的用户,并且邮箱不为空的用户,年龄大于12岁 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name") .isNotNull("email") .ge("age",12); userMapper.selectList(wrapper).forEach(System.out::println);//和map对比一下 }测试2
@Test public void test2(){ //查询名字为Jack QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name","Jack");//查询一个数据,出现多个结果使用List,或者Map userMapper.selectOne(wrapper); }测试3
@Test void test3(){ //年龄在20-30之间的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age",20,30);//区间 Integer count = userMapper.selectCount(wrapper);//查询结果数 System.out.println(count); }测试4,记住查看输出的sql进行分析,模糊查询
@Test void test4(){ //模糊查询 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.notLike("name","J") .likeRight("email","t"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);//查询结果数 maps.forEach(Systm.out::println); }测试5,内查询
@Test void test5() { //模糊查询 QueryWrapper<User> wrapper = new QueryWrapper<>(); //id在子查询中查出来 wrapper.inSql("id","select id from user where id < 3"); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }测试6 ,排序
@Test void test6() { //模糊查询 QueryWrapper<User> wrapper = new QueryWrapper<>(); //通过id进行排序(降序) wrapper.orderByDesc("id"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
package com.yang.mybatis_plus; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.po.TableFill; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.ArrayList; //代码自动生成器 public class MyCode { 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("杨剑"); gc.setOpen(false);//是否打开资源管理器 gc.setFileOverride(false);//是否覆盖 gc.setServiceName("%sService");//去service的I前缀 gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); mpg.setGlobalConfig(gc); //2.设置数据源 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/mybaits_plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); //3.包的配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("blog"); pc.setParent("com.lu"); 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 modifiedTime = new TableFill("create_time", FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(createTime); tableFills.add(modifiedTime); strategy.setTableFillList(tableFills); //乐观锁配置 strategy.setVersionFieldName("version"); strategy.setRestControllerStyle(true);//开启restful的驼峰命名 strategy.setControllerMappingHyphenStyle(true);//localhost:8080/hello_id_2 mpg.setStrategy(strategy); mpg.execute();//执行 } }