Mybatis-plus
快速入门
-
导入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version> </dependency> 尽量不要同时导入 mybatis 和 mybatis-plus,版本的差异
-
连接数据库
spring.datasource.username=root spring.datasource.password=123456 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
-
配置实体类、mapper接口(继承BaseMapper泛型为要操作的实体类)
// 在对应的Mapper上面继承基本的类 BaseMapper @Repository // 代表持久层 public interface UserMapper extends BaseMapper<User> { // 所有的CRUD操作都已经编写完成了 // 你不需要像以前的配置一大堆文件了! }
-
主启动类上去扫描我们的mapper接口
@MapperScan("com.kuang.mapper")
日志
配置一个标准的日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
CRUD
-
插入
数据库插入的id 默认值: 全局的唯一id
// 测试插入 @Test public void testInsert(){ User user = new User();user.setName("狂神说Java");user.setAge(3);user.setEmail("24736743@qq.com");int result = userMapper.insert(user); // 帮我们自动生成idSystem.out.println(result); // 受影响的行数System.out.println(user); // 发现,id会自动回填 }
-
更新
// 测试更新 @Test public void testUpdate(){ User user = new User();// 通过条件自动拼接动态sqluser.setId(6L);user.setName("关注公众号:狂神说");user.setAge(18);// 注意:updateById 但是参数是一个 对象!int i = userMapper.updateById(user);System.out.println(i); }
主键生成策略
雪花算法
生成策略
public enum IdType { AUTO(0), // 数据库id自增NONE(1), // 未设置主键INPUT(2), // 手动输入ID_WORKER(3), // 默认的全局唯一idUUID(4), // 全局唯一id uuidID_WORKER_STR(5); //ID_WORKER 字符串表示法 }
- 主键自增
-
实体类字段上使用:
@TableId(type=IdType>AUTO)
-
数据库字段设置为自增
插入、更新操作
自动填充
创建时间、修改时间!这些个操作一遍都是自动化完成的,我们不希望手动更新!
-
数据库级别
- 在表中新增字段 create_time, update_time
- 默认值分别设置为:CURRENT_TIMESTAMP
- 同步实体类(插入属性)
- 勾选修改时间的更新选项
-
Java代码级别
-
表中增加字段
-
实体类同步并增加注解
创建时间: @TableField( fill = FieldFill . INSERT )
修改时间: @TableField( fill = FieldFill . INSERT_UPDATE )
-
编写处理器处理注解
@Slf4j @Component // 一定不要忘记把处理器加到IOC容器中! public class MyMetaObjectHandler implements MetaObjectHandler { // 插入时的填充策略@Overridepublic void insertFill(MetaObject metaObject) { log.info("start insert fill.....");// setFieldValByName(String fieldName, Object fieldVal, MetaObject)this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}// 更新时的填充策略@Overridepublic 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 = "kuangshen", version = version + 1
where id = 2 and version = 1-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
MP 的乐观锁插件
-
数据库增加version 字段
-
实体嘞增加version 属性
-
注册组件
// 扫描我们的 mapper 文件夹 @MapperScan("com.kuang.mapper") @EnableTransactionManagement @Configuration // 配置类 public class MyBatisPlusConfig { // 注册乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
查询、分页查询
-
查询
# 属性查找 User user = userMapper.selectById(1L);# 属性查找一批 List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));# 使用map k-v 查找 HashMap<String, Object> map = new HashMap<>(); // 自定义要查询 map.put("name","狂神说Java"); map.put("age",3); List<User> users = userMapper.selectByMap(map);
-
分页查询
-
limit 分页查询
-
pageHelper
-
MP的内置插件
-
拦截器组件
// 分页插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
- 使用
// 测试分页查询 @Test public void testPage(){ // 参数一:当前页 // 参数二:页面大小 // 使用了分页插件之后,所有的分页操作也变得简单的! Page<User> page = new Page<>(2,5); userMapper.selectPage(page,null); page.getRecords().forEach(System.out::println); System.out.println(page.getTotal()); }
-
删除、逻辑删除
-
删除
// 测试删除 @Test public void testDeleteById(){ userMapper.deleteById(1240620674645544965L); }// 通过id批量删除 @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(1240620674645544961L,1240620674645544962L)); }// 通过map删除 @Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>();map.put("name","狂神说Java");userMapper.deleteByMap(map); }
-
逻辑删除
并非真正意义上的物理删除,而是通过一个变量让他们失效
-
数据表和实体类中增加一个deleted 属性
-
配置
// 逻辑删除组件! @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }
# 配置逻辑删除 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0
-
性能分析插件
作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间
MP也提供性能分析插件,如果超过这个时间就停止运行!
-
在SpringBoot中配置环境为dev或者 test 环境!
-
导入插件
/** * SQL执行效率插件 */ @Bean @Profile({ "dev","test"})// 设置 dev test 环境开启,保证我们的效率 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = newPerformanceInterceptor();performanceInterceptor.setMaxTime(100); // ms设置sql执行的最大时间,如果超过了则不执行performanceInterceptor.setFormat(true); // 是否格式化代码return performanceInterceptor; }
-
测试(只要该条sql 超过了规定的时间就会抛异常)
条件构造器wrapper
- 用于描写一些复杂的sql
?
void contextLoads() {
QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12wrapper.isNotNull("name").isNotNull("email").ge("age",12);userMapper.selectList(wrapper)// 查询名字狂神说wrapper.eq("name","狂神说");User user = userMapper.selectOne(wrapper); // 查询一个数据,出现多个结果使用List// 查询年龄在 20 ~ 30 岁之间的用户wrapper.between("age",20,30); // 区间Integer count = userMapper.selectCount(wrapper);// 查询结果数// 查询年龄在 20 ~ 30 岁之间的用户// 左和右 t%wrapper.notLike("name","e").likeRight("email","t");List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);// id 在子查询中查出来wrapper.inSql("id","select id from user where id<3");List<Object> objects = userMapper.selectObjs(wrapper);// 通过id进行排序wrapper.orderByAsc("id");List<User> users = userMapper.selectList(wrapper);userMapper.selectList(wrapper).forEach(System.out::println); // 和我们刚才学习的map对比一下
}
代码自动生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器
通过 AutoGenerator 可以快速生成 Entity、 Mapper、Mapper XML、Service、Controller 等各个模块的代码
极大的提升了开发效率。
package com.kuang;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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 KuangCode {
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("zj");// 是否打开目录gc.setOpen(false);gc.setFileOverride(false); // 是否覆盖gc.setServiceName("%sService"); // 去Service的I前缀gc.setIdType(IdType.ID_WORKER);// 时间日期格式gc.setDateType(DateType.ONLY_DATE);// 使用Swaggergc.setSwagger2(true);mpg.setGlobalConfig(gc);//2、设置数据源DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/kuang_community?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");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.kuang");pc.setEntity("entity");pc.setMapper("mapper");pc.setService("service");pc.setController("controller");mpg.setPackageInfo(pc);//4、策略配置StrategyConfig strategy = new StrategyConfig();strategy.setInclude("blog_tags","course","links"); // 设置要映射的表名strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);strategy.setEntityLombokModel(true); // 自动lombok;strategy.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);strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");strategy.setRestControllerStyle(true);strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2mpg.setStrategy(strategy);mpg.execute(); //执行}
}
Swagger
前后端分离
前后端分离
- 前端 ==> 前端控制层、视图层
- 后端 ==> 后端控制层、服务层、数据访问层
- 前后端通过API进行交互、相对独立且松耦合
前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发
Swagger
- 号称世界上最流行的API框架
- Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
- 直接运行,在线测试API
- 支持多种语言 (如:Java,PHP等)
- 官网:https://swagger.io/
快速入门
-
导入依赖 ( Springfox-swagger2 、springfox-swagger-ui )
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version> </dependency>
-
编写SwaggerConfig 配置类来配置Swagger
@Configuration //配置类 @EnableSwagger2// 开启Swagger2的自动配置 public class SwaggerConfig { }
-
访问:http://localhost:8080/swagger-ui.html
配置Swagger
-
Swagger实例Bean是Docket,通过配置Docket 实例配置Swagger
@Bean //配置docket以配置Swagger具体参数 public Docket docket() { return new Docket(DocumentationType.SWAGGER_2); }
-
通过apiInfo( ) 属性配置文档信息
//配置文档信息 private ApiInfo apiInfo() { Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱");return new ApiInfo("Swagger学习", // 标题"学习演示如何配置Swagger", // 描述"v1.0", // 版本"http://terms.service.url/组织链接", // 组织链接contact, // 联系人信息"Apach 2.0 许可", // 许可"许可链接", // 许可连接new ArrayList<>()// 扩展); }
-
Docket 实例关联上 apiInfo()
@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); }
配置扫描接口
1、构建Docket时通过select()方法配置怎么扫描接口。
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("swagger_study.controller"))// 还可以使用 paths 进行路径过滤 即这里只扫描请求以/swagger_study 开头的接口.paths(PathSelectors.ant("/swagger_study/")).build();
}
// RequestHandlerSelectors 还可以有一下参数用来配置扫描包any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)// 根据包路径扫描接口
basePackage(final String basePackage)
// paths 同样也可以有如下配置any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制
配置是否使用swagger
@Beanpublic Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) // 关闭swagger .enable(false) .select() .apis(RequestHandlerSelectors.basePackage("swagger_study.controller")) .paths(PathSelectors.ant("/swagger_study/")) .build();}
-
问题:(如果项目处于test、dev 环境时使用 swagger ,处于prod 时不使用swagger)
@Beanpublic Docket docket(Environment environment) { // 设置要显示swagger的环境 Profiles of = Profiles.of("dev", "test"); // 判断当前是否处于该环境 // 通过 enable() 接收此参数判断是否要显示 boolean b = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(b) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select() .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) .paths(PathSelectors.ant("/kuang/**")) .build();}
配置API分组
-
如果没有配置分组,默认是default。通过 .groupName()方法即可配置分组
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .groupName("hello") // 配置分组 // 省略配置....}
-
配置多个分组只需要配置多个 docket 即可
@Beanpublic Docket docket1(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group1");}@Beanpublic Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group2");}@Beanpublic Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group3");}
配置实体类
-
实体类
@ApiModel("用户实体")public class User { @ApiModelProperty("用户名") public String username; @ApiModelProperty("密码") public String password;}
@ApiModel 为类添加注释
@ApiModelProperty 为类属性添加注释
只要出现在接口方法的返回值上的实体都会显示在这里
而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的
-
只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中
@RequestMapping("/getUser")public User getUser(){ return new User();}
常用注解
Swagger注解 | 简单说明 |
---|---|
@Api(tags = “xxx模块说明”) | 作用在模块类上 |
@ApiOperation(“xxx接口说明”) | 作用在接口方法上 |
@ApiModel(“xxxPOJO说明”) | 作用在模型类上:如VO、BO |
@ApiModelProperty(value = “xxx属性说明”,hidden = true) | 作用在类方法和属性上,hidden设置为true可以隐藏该属性 |
@ApiParam(“xxx参数说明”) | 作用在参数、方法和字段上,类似@ApiModelProperty |
小结
Swagger就是用来给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读
正式环境要记得关闭Swagger,安全考虑、节省运行
皮肤
导入不同的 ui 包 实现不同的皮肤定义
-
默认
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version></dependency>
http://localhost:8080/swagger-ui.html
-
bootstrap-ui
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version></dependency>
http://localhost:8080/doc.html
-
layui-ui
<dependency> <groupId>com.github.caspar-chen</groupId> <artifactId>swagger-ui-layer</artifactId> <version>1.1.3</version></dependency>
http://localhost:8080/docs.html
-
mg-ui
<dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version></dependency>
http://localhost:8080/document.html