官网地址:https://baomidou.com/
创建数据库 mybatis-plus
创建 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, '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');
初始化项目
快速构建一个spring boot项目。
导入相关依赖。
<!--mybatis-plus(是自己开发的starter,不是官方的)--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--MySQL数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!版本的差异!
连接数据库
spring: datasource: # MySQL8的驱动(和MySQL5不同)。需要增加时区的配置 serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8 username: root password: admin server: port: 8086
mybatis-plus的优势。
使用传统方式:
使用mybatis-plus:
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
mapper接口
/** * Mapper接口 * * 继承了BaseMapper,所有的方法都来自于自己的父类。我们也可以编写自己的扩展方法。 */ public interface UserMapper extends BaseMapper<User> { }
使用
@SpringBootTest class MybatisplusApplicationTests { @Autowired(required = false) private UserMapper userMapper; @Test public void testSelect(){ System.out.println("=====查询所有的数据====="); //参数是一个wrapper,条件构造器,没有就填null。 List<User> list = userMapper.selectList(null); list.forEach(System.out::println); } }
用了Mybatis-plus后,这时的sql是不可见的,我们希望知道它是怎么执行的,所以需要打开日志!
# mybatis配置日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
@Test public void testInsert(){ System.out.println("=====insert method test======"); User user = new User(); //并没有设置userId。(帮我们自动生成id) user.setName("弹头"); user.setAge(23); user.setEmail("2238224084@qq.com"); int insert = userMapper.insert(user); System.out.println(insert); //受影响的行数 System.out.println(user); //id会自动回填 }
数据库插入的id的默认值为:全局的唯一id
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。几乎可以保证几乎全球唯一!
Mybatis-Plus自 3.3.0 开始,默认使用雪花算法(ASSIGN_ID)+UUID(不含中划线)
主键自增
我们需要配置主键自增:
@TableId(type = IdType.AUTO)
其余源码解释
//这是老版本的方式。新版本已经改变了。 public enum IdType { AUTO(0), //数据库id自增 NONE(1), //未设置主键 INPUT(2), //手动输入 ID_WORKER(3), //默认的全局唯一id UUID(4), //全局唯一id uuid ID_WORKER_STR(5); //默认的全局唯一id,字符串方式 }
@Test public void testUpdate() { User user = new User(); user.setId(1482682712933310466L); user.setName("王忠舟"); int update = userMapper.updateById(user); System.out.println("受影响的行数:" + update); }
@Test public void testUpdate() { User user = new User(); user.setId(1482682712933310466L); user.setName("王忠舟"); user.setAge(18); int update = userMapper.updateById(user); System.out.println("受影响的行数:" + update); }
创建时间、修改时间!这些操作都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据表都要配置上gmt_create(创建时间)、gmt_modified(修改时间)!而且都要自动化!
在表中新增字段 create_time
和 update_time
。取消默认值、更新操作。
把实体类同步,并在字段属性上增加注解。
//字段添加填充内容 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
需要自定义实现类,处理这个注解。在项目根路径下新建 /handler/MyMetaObjectHandler.java
,并继承 MetaObjectHandler
接口。
@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 = 2
,不满足该条更新语句,所以就无法操作了。)mybatis-plus使用步骤:
给数据库中增加version字段。
实体类中同步相应的字段。
@Version //乐观锁Version注解 private Integer version;
注册组件
@Configuration //将主程序的扫描移动到配置文件中。 @MapperScan("com.example.mybatisplus.mapper") public class MyBatisPlusConfig { //注册乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
测试乐观锁成功!先查询,后修改。
//测试乐观锁成功 @Test public void testOptimisticLocker(){ //1、查询用户信息 User user = userMapper.selectById(1482682712933310469L); //2、修改用户信息 user.setAge(18); user.setEmail("2238224084@qq.com"); //3、执行更新操作 userMapper.updateById(user); }
测试乐观锁失败!
//测试乐观锁失败(多线程下) @Test public void testOptimisticLocker2(){ //线程1 User user1 = userMapper.selectById(1482682712933310469L); user1.setAge(21); user1.setName("芜湖"); //模拟另一个线程执行了插队操作。 User user2 = userMapper.selectById(1482682712933310469L); user2.setAge(20); //线程1已经查询了,但还没来得及更新,被另一个线程查询并抢先更新了。 userMapper.updateById(user2); //如果没有乐观锁,就会覆盖插队线程的值。 userMapper.updateById(user1); }
//id查询 @Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); } //批量查询 @Test public void testSelectBatchIds(){ List<Long> list = Arrays.asList(1L, 2L, 3L); List<User> users = userMapper.selectBatchIds(list); users.forEach(System.out::println); } //条件查询使用之一 map @Test public void testSelectByMap(){ HashMap<String, Object> map = new HashMap<>(); //自定义查询条件 map.put("name", "王忠舟"); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
分页在网站使用的十分之多!
原始的limit进行分页。
pageHelper第三方插件。
MP其实也内置了分页插件!
如何使用?
导入分页插件
// 分页插件(旧版) @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
直接使用Page对象即可!
@Configuration //将主程序的扫描移动到配置文件中。 @MapperScan("com.example.mybatisplus.mapper") public class MyBatisPlusConfig { //测试分页查询 @Test public void testPage(){ //参数1:当前页;参数2:页面数据条数。可以设置为变量从前端接收。 Page<User> page = new Page<>(1,5); userMapper.selectPage(page, null); page.getRecords().forEach(System.out::println); } }
//测试分页查询 @Test public void testPage(){ //参数1:当前页;参数2:页面数据条数。可以设置为变量从前端接收。 Page<User> page = new Page<>(2,5); userMapper.selectPage(page, null); page.getRecords().forEach(System.out::println); }
当前页和页面数据条数可以设置为变量,从前端接收。
//删除 @Test public void testDelete(){ int delete = userMapper.deleteById(1482682712933310466L); System.out.println("受影响的条数:"+delete); } //批量删除 @Test public void testDeleteBatchIds(){ int batchIds = userMapper.deleteBatchIds(Arrays.asList(1L, 2L)); System.out.println("受影响的条数:"+batchIds); } //通过map 删除 @Test public void testDeleteByMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("name", "弹头蛋头"); int delete = userMapper.deleteByMap(map); System.out.println("受影响的条数:"+delete); }
我们在工作中会遇到一些问题:逻辑删除!
物理删除:从数据库中直接删除
逻辑删除:没有真正从数据库中删除,而是通过一个变量来让他失效。deleted = 0 ==> deleted = 1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
步骤:
在数据表中增加一个 deleted
字段。
在实体类中同步属性。
@TableLogic //逻辑删除 private Integer deleted;
config中注册组件。
@Configuration //将主程序的扫描移动到配置文件中。 @MapperScan("com.example.mybatisplus.mapper") public class MyBatisPlusConfig { //逻辑删除(旧版) @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); } }
配置文件中配置逻辑删除
mybatis-plus: global-config: db-config: # 配置逻辑删除 logic-delete-value: 1 logic-not-delete-value: 0
测试删除,再执行查询操作。查看查询结果和表中的结果。
逻辑删除实际上是更新操作,并不是删除操作
以上的所有CRUD操作及其扩展操作,我们都必须精通掌握。会大大提高工作和写项目的效率!
我们在平时的开发中,会遇到一些慢sql。测试!
MP也提供性能分析插件,如果超过这个时间就停止运行!
使用:
导入插件
//SQL执行效率插件 @Bean @Profile({"dev", "test"}) //设置 dev test 环境开启,保证我们的效率 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); //设置sql执行的最大时间,如果超过了则不执行。(ms/毫秒) performanceInterceptor.setMaxTime(100); //开启sql格式化 performanceInterceptor.setFormat(true); return performanceInterceptor; }
在配置文件中配置环境为dev 或者 test 环境。
spring: profiles: # 开发环境 active: dev
测试使用
@Test public void testSelect() { System.out.println("=====selectAll method test====="); //参数是一个wrapper,条件构造器,没有就填null。 List<User> list = userMapper.selectList(null); list.forEach(System.out::println); }
使用性能分析插件,可以帮助我们提高效率!
十分重要:Wrapper
我们写一些复杂的sql就可以使用它来替代!
使用:
查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于20的数据,记住查看输出的sql进行分析。
@Test public void Test(){ //查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于20 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name").isNotNull("email").ge("age", 20); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
查询名字为“弹头”的数据(使用selectOne),记住查看输出的sql进行分析。
@Test public void testSelectOne(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name", "弹头"); //只查询一个的话可以用selectOne,若超过一个或为0个的话会报错。 System.out.println(userMapper.selectOne(wrapper)); }
查询年龄在 20 - 30 岁之间的用户的数量,记住查看输出的sql进行分析。
//查询年龄在 20 - 30 岁之间的用户的数量 @Test public void testBetweenAnd(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); //区间 wrapper.between("age", 20, 30); //查询结果数 Integer count = userMapper.selectCount(wrapper); System.out.println(count); }
模糊查询,记住查看输出的sql进行分析。
@Test public void testLike(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .notLike("email", "qq") .likeRight("email", "test4"); //Right和Left表示 "%" 在哪边 List<Map<String, Object>> mapList = userMapper.selectMaps(wrapper); mapList.forEach(System.out::println); }
使用子查询,记住查看输出的sql进行分析。
@Test public void testSelectObjs(){ 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); }
order排序查询,记住查看输出的sql进行分析。
@Test public void testOrder(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.orderByDesc("id"); List<User> userList = userMapper.selectList(wrapper); userList.forEach(System.out::println); }
dao、pojo、service、controller都自己去编写完成!
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
写一个代码生成器也挺复杂的,我感觉这东西适合大型项目,需要多个包的。就一个包的小型项目就自己手写也挺快的。
步骤:
还需要导入相应依赖
<!--mybatis-plus代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.0.5</version> </dependency> <!-- mybatis-plus代码生成器所需要的模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>
编写代码
package com.example.mybatisplus; 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 CodeGenerator { public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); //配置策略 //1、全局配置 GlobalConfig gc = new GlobalConfig(); String property = System.getProperty("user.dir"); //项目路径 gc.setOutputDir(property + "/src/main/java"); //新建文件夹的位置 gc.setAuthor("dan"); //标注作者 gc.setOpen(false); //是否新建完成打开资源管理器 gc.setFileOverride(false); //是否覆盖 gc.setIdType(IdType.ID_WORKER); //主键策略 gc.setDateType(DateType.ONLY_DATE); //日期类型 gc.setSwagger2(true); //自动生成swagger mpg.setGlobalConfig(gc); //2、设置数据源(数据库配置) DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("admin"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); //3、包的配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("blog"); pc.setParent("com.example"); pc.setEntity("pojo"); pc.setMapper("mapper"); pc.setController("controller"); pc.setService("service"); mpg.setPackageInfo(pc); //4、策略配置 StrategyConfig sc = new StrategyConfig(); sc.setInclude("user"); //映射数据库中的表名 sc.setNaming(NamingStrategy.underline_to_camel); sc.setColumnNaming(NamingStrategy.underline_to_camel); sc.setEntityLombokModel(true); //自动Lombok。 sc.setLogicDeleteFieldName("deleted"); //逻辑删除字段 TableFill createTime = new TableFill("create_time", FieldFill.INSERT); TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE); ArrayList<TableFill> list = new ArrayList<>(); list.add(createTime); list.add(updateTime); sc.setTableFillList(list); //乐观锁 sc.setVersionFieldName("version"); //乐观锁字段 sc.setRestControllerStyle(true); //驼峰命名 mpg.setStrategy(sc); //执行 mpg.execute(); } }