Mybatisplus从字面上面理解时Mybatis的加强版,它可以大大节省我们大量的工作时间,所有的增删改查代码它都可以在mybatisplus中自动的完成,其本质上上Mybatis的一个增强工具,在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生。
使用官方提供的快速开始文档
mybatisplus
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)
);
DELETE FROM user;
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]');
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.1.tmpversion>
dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatisplus?userSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
private Long id;
private String name;
private Integer age;
private String email;
}
使用mybatisplus之后只需要在对应的mapper上面继承基本的类BaseMapper即可(不需要像Mybatis那样繁琐)。到此所有的CRUD已经Mybatisplus帮我们自动编写完成十分方便(当然我们也可以在这里面定义自己的代码)。
@Repository //代表这个接口是Dao层的
public interface UserMapper extends BaseMapper<User> {
}
@MapperScan("com.chailong.getspring.mapper") //扫描mapper的文件夹
@SpringBootApplication
public class GetspringApplication {
public static void main(String[] args) {
SpringApplication.run(GetspringApplication.class, args);
}
@SpringBootTest
class GetspringApplicationTests {
@Autowired
private UserMapper userMapper; //注入usermapper的bean
@Test
void contextLoads() {
//seleclist的参数是一个wrapper(条件构造器)
List<user> userList=userMapper.selectList(null);//查询全部
for (user user1 : userList) {
System.out.println(user1);
}
}
}
在前面的操作过程中我们是具体看不到我们的sql语句的,为了能在控制台看到我们的sql语句,我们需要对日志进行配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
@Test
void testInsert() {
user usernew=new user();
usernew.setName("chailong");
usernew.setAge(11);
usernew.setEmail("[email protected]");
int result=userMapper.insert(usernew);
System.out.println(result);
}
思考这里为什么会自动生成唯一ID?
这就要涉及到分布式系统唯一id生成中的雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证ID唯一性。
@TableId
注解可以配置主键生成策略(下面是常见的策略)默认策略是ID_WORKER全局唯一ID
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
@TableId(type = IdType.ID_WORKER )
private Long id;
private String name;
private Integer age;
private String email;
}
- 在实体类字段上配置
@TableId(type=IdType.AUTO)
- 勾选Mysql数据库中ID自增选项
public enum IdType {
AUTO(0), //数据库id自增
NONE(1),//未设置主键
INPUT(2),//手动输入(数据库里面自己填)
ID_WORKER(3),//默认全局唯一id
UUID(4),//全局唯一id
ID_WORKER_STR(5);//ID_worker的字符串表示法
}
测试类代码
@Test
void testUpdate() {
user usernew=new user();
usernew.setId(11);
usernew.setName("jakiechai");
usernew.setAge(11);
usernew.setEmail("[email protected]");
int i=userMapper.updateById(usernew);
System.out.println(i);
}
结果:
可以发现mybatis-plus帮我们实现了自动动态配置,这就需要讲到mybatis-plus的自动填充技术,下面介绍自动填充技术。
问题提出:在创建数据表中我们有些参数是不希望用户去手动填充的而是希望系统帮我们去自动填充(如表创建时间和表修改时间等)。阿里巴巴开发手册规定几乎所有数据表字段都必须要有一个gmt_create(创建时间)和gmt_modified(修改时间),而这两个参数是需要系统自动填充的;这就涉及到了自动填充技术。
在表中新增字段创建时间和修改时间设置默认值即可(修改表字段即可,注意pojo对应属性也要更改)
- 删除数据库默认值
- 实体类属性上加入相关注解
在TableField接口中有一个fill函数用于属性值的自动填充
FieldFill fill() default FieldFill.DEFAULT;
FieldFill
的值表示填充的方法
public enum FieldFill {
DEFAULT, //默认不填充
INSERT, //插入时填充
UPDATE, //更新时填充
INSERT_UPDATE; //更新或插入时填充
}
然后在类属性上加入@TableField
注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
@TableId(type = IdType.AUTO )
private int id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
- 在主目录下建立一个Handler包,专门用来放SpringBoot项目的处理器,编写处理器代码
@Slf4j //使用日志
@Component //处理器加入到ioc容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//实现插入时自动填充
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
//setFieldValByName(字段名,字段值,metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
log.info("insert over");
}
//实现更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
//setFieldValByName(字段名,字段值,metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
log.info("update over");
}
}
@Test
void testInsert() {
user usernew=new user();
usernew.setName("menglei");
usernew.setAge(11);
usernew.setEmail("[email protected]");
int result=userMapper.insert(usernew);
System.out.println(result);
}
所谓的乐观锁,其实主要就是一种思想,因为乐观锁的操作过程中其实没有没有任何锁的参与,乐观锁只是和悲观锁相对,严格的说乐观锁不能称之为锁。所以要了解乐观锁的概念,通常与悲观锁对比起来看才更好理解,下面我们就通过乐观锁与悲观锁的对比来更好的理解乐观锁。乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 取出记录时获取当前version
- 更新时带上这个version
- 执行更新时,set version=nerVersion where version =oldVersion
- 如果version不对,就更新失败
//A执行更新
update user set name="kuanshen",version=version+1
where id=2 and version =1
//B执行更新
update user set name="kuanshen",version=version+1
where id=2 and version =1
假设B先完成了更新,version被改变,此时A就会更新失败,这就是乐观锁实现线程安全的机制
4. 使用Mybatis-plus的乐观锁插件
- 给数据表中加入version字段,并设置默认值为1
- 实体类同步,并给属性上加入乐观锁注解
- 在Handler文件下编写乐观锁的组件
@Configuration //声明这是一个配置类
@EnableTransactionManagement //事务
public class MybatisplusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
- 测试乐观锁
@Test
void testOptimisticLocker(){
//1查询用户信息
user usermy=userMapper.selectById(1);
//2.修改用户信息
usermy.setName("mike");
usermy.setAge(11);
usermy.setEmail("jake.com");
//3.更新用户信息
userMapper.updateById(usermy);
}
运行结果:更新成功version加1(单线程不存在现场安全问题,一定会更新成功)
@Test
void testOptimisticLocker(){
//线程1(准备更新但是还没更新)
user usermy=userMapper.selectById(1);
usermy.setName("mike2");
usermy.setAge(11);
usermy.setEmail("jake.com");
//线程2(插队在线程1之前执行了更新)
user usermy2=userMapper.selectById(1);
usermy2.setName("mike1");
usermy2.setAge(11);
usermy2.setEmail("mary.com");
userMapper.updateById(usermy2);//线程2的更新操作
userMapper.updateById(usermy);//线程1的更新操作(如果没有乐观锁就会把线程2更新的值直接覆盖掉)
}
@Test
void testquery(){
System.out.println(userMapper.selectBatchIds(Arrays.asList(1,2,3)));
}
在以前的ssm项目中几乎使用的都是原始的limit分页方式或者使用pageHelper等第三方插件,Mybaits-plus也为开发者准备了分页插件,开发直接使用即可,十分方便
- 配置分页组件(在配置类中加入分页的Bean就行)
@Bean
public PaginationInterceptor pageInationInterceptor(){
return new PaginationInterceptor();
}
- 使用Page对象进行分页
@Test
void testPage(){
Page<user> page=new Page<>(2,5);//当前页第2页,每页5条数据
userMapper.selectPage(page,null);//null是给条件查询器参数(这里不使用)
page.getRecords().forEach(System.out::println);
}
@Test
void testDeleteById(){
int result=userMapper.deleteById(1);
System.out.println(result);
}
物理删除:从数据库中直接删除
逻辑删除:并没有从数据库中删除而是通过一个变量让其不能使用(达到了所谓删除的目的),用于防止数据的丢失,相当于回收站的作用
- 增加一个删除标记字段在数据库中
- pojo增加属性并使用注解
@TableLogic
private int flage;
- 配置逻辑删除组件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
- 配置文件中配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
- 测试逻辑删除
@Test
void logicdelet(){
userMapper.deleteById(2); //逻辑删除2号用户
}
结果:数据并没有删除(但flage变成了1即数据已经被逻辑删除了)
然后测试一下是否能查询到逻辑删除的数据:
@Test
void testquery(){
System.out.println(userMapper.selectById(2));
}
慢sql:慢SQL指的是MySQL慢查询,是运行时间超过long_query_time值的SQL。真实的慢SQL通常会伴随着大量的行扫描、临时文件排序或者频繁的磁盘flush,直接影响就是磁盘IO升高,让正常的SQL变成了慢SQL,大面积执行超时。
在实际操作中我们会经常遇到一些慢sql,如何找出这些慢sql我们的Mybatis-plus也提供了 相应的插件:
性能分析插件:
性能分析拦截器,用于输出每条SQL语句及其执行时间。SQL性能执行分析,开发环境使用,超过指定时间停止运行,有助于发现问题。
//SQL执行效率插件
@Bean
@Profile({"dev","test"}) //设置 dev test 环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1); //设置sql的最大执行时间为1ms
performanceInterceptor.setFormat(true);//是否开启格式化支持
return performanceInterceptor;
}
spring:
profiles:
active: dev
@Test
void testPage(){
Page<user> page=new Page<>(2,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
在前面使用的mybatis-plus提供的sql函数接口中,我们会发现我们把wrapper这个参数设置为了null,wrapper就是我们的条件构造器,用于写一些复杂的sql
@Test
void contextLoads(){
//查询name不为空的用户,并且邮箱不为空的用户,年龄>21的用户
QueryWrapper<user> wrapper=new QueryWrapper<user>(); //创建条件构造器
wrapper.isNotNull("name"); //name不为空的查询条件
wrapper.isNotNull("email"); //邮箱不为空
wrapper.ge("age",12); //年龄大于12
userMapper.selectList(wrapper).forEach(System.out::println);
}
结果:
@Test
void contextLoads(){ //查询名字为menglei的用户
//查询name不为空的用户,并且邮箱不为空的用户,年龄>21的用户
QueryWrapper<user> wrapper=new QueryWrapper<user>(); //创建条件构造器
wrapper.eq("name","menglei");
userMapper.selectOne(wrapper);
}
代码自动生成:AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
public static void main(String[] args) {
//构建一个代码自动生成器对象
AutoGenerator autoGenerator=new AutoGenerator();
//配置策略
//1全局配置
GlobalConfig gc=new GlobalConfig();
String property = System.getProperty("user.dir"); //获取用户目录
gc.setOutputDir(property+"/src/src/main/java");//代码文件的生成路径
gc.setAuthor("chailong"); //设置作者
gc.setFileOverride(false);//是否覆盖
gc.setServiceName("%sService");//去service的i前缀
gc.setIdType(IdType.ID_WORKER); //设置Id自增
gc.setDateType(DateType.ONLY_DATE);//设置日期类型
gc.setSwagger2(true);//设置swagger
autoGenerator.setGlobalConfig(gc);
//2. 设置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatisplus");
dataSourceConfig.setUsername("root");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setPassword("123456");
dataSourceConfig.setDbType(DbType.MYSQL);
autoGenerator.setDataSource(dataSourceConfig);
//3.设置包
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("blog");
packageConfig.setParent("com.chailong.getspring");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setController("controller");
autoGenerator.setPackageInfo(packageConfig);
//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("flage"); //设置逻辑删除名字
//4.设置自动填充
TableFill create_time = new TableFill("create_time", FieldFill.INSERT_UPDATE);
TableFill update_time = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> arrayList = new ArrayList<>();
arrayList.add(create_time);
arrayList.add(update_time);
strategy.setTableFillList(arrayList);
//乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
autoGenerator.setStrategy(strategy);
autoGenerator.execute();//执行
}
运行结果: