Mybatis_plus

快速入门

使用第三方组件:

  • 导入对应的依赖
  • 研究依赖如何配置
  • 代码如何编写
  • 提高扩展技术能力!

步骤

1、创建数据库 mybatis-plus

2、创建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, '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');
-- 真实开发中, version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified

3、编写项目,初始化项目,使用Springboot初始化

4、导入依赖

<!--        数据库驱动-->
        <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.0.5</version>
        </dependency>

使用mybatis-plus 可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!版本差异

5、连接数据库!与mybatis相同

# mysql配置
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=Asia/Shanghai&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8

6、使用了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.kuang.mapper")

  • 测试
@SpringBootTest
class MybatisPlusApplicationTests {

    //继承了BaseMapper,所有的方法都来自自己的父类
    //我们也可以像往常一样编写自己的扩展方法
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        //查询全部的用户
        //参数是一个Wrapper,条件构造器,这里先不用设置为null
        List<User> list = userMapper.selectList(null);
        for (User user : list) {
            System.out.println(user);
        }
    }

}

结果:

为什么通过短短几部就完成了?

1、SQL谁来完成的? -->mybatis-plus 完成的

2、方法哪里来的? -->mybatis-plus 完成的

配置日志

所有的sql现在是不可见的,我们希望知道它是如何执行的,所以我们必须看日志!

# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl 

日志配置完毕,后面的学习就需要注意这个自动生成的SQL

CRUD扩展

insert

 //测试插入
    @Test
    public void testInsert(){
        User user = new User();
        user.setName("遇见狂神说");
        user.setAge(12);
        user.setEmail("9923014@qq.com");
        int insert = userMapper.insert(user);
        System.out.println(insert);
        System.out.println(user);
    }

数据库插入的id默认值为:全局唯一id

主键生成策略

插入操作

默认 ID_WORKER 全局唯一id

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

主键自增

我们需要配置主键自增:

1、实体类字段上@TableId(type = IdType.AUTO)

2、数据库字段一定要自增!

3、再次测试插入即可!

其他源码解释

public enum IdType {
    AUTO(0),	//数据库id自增
    NONE(1),	// 未设置主键
    INPUT(2),	// 手动输入
    ID_WORKER(3),	//默认的全局id
    UUID(4),	//全局唯一id
    ID_WORKER_STR(5);	//ID_WORKER 的字符串表示法

    private int key;

    private IdType(int key) {
        this.key = key;
    }

    public int getKey() {
        return this.key;
    }
}

更新操作

 //测试更新
    @Test
    public void testUpdate(){
        User user = new User();
        //通过条件自动拼接动态sql
        user.setId(6L);
        user.setName("遇见chenfangy");
        user.setAge(18);
        // 注意:updateById 需要传入的参数是一个对象!
        int i = userMapper.updateById(user);
        System.out.println(i);
    }

所有的sql都是自动帮你动态配置的!

自动填充

创建时间、修改时间!这些个操作都是一般都是自动化完成的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modified、几乎所有的表都要配置上!而且需要自动化!

方式一:数据库级别(不建议这样使用)

1、在表中新增字段 create_time,update_time

2、再次测试插入方法,我们需要先把实体类同步!

    private Date createTime;
    private Date updateTime;

3、再次更新查看结果即可

方式二:代码级别

1、删除数据库的默认值、更新操作!(没有设置自动更新)

2、实体类字段属性需要增加注解

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    //对应数据库中的主键(uuid、自增id、雪花算法、redis、zookeeper)
    @TableId(type = IdType.INPUT)   //IdType.INPUT一旦手动输入id后,就需要自己配置id了
    private Long id;
    private String name;
    private Integer age;
    private String email;

    //字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

3、编写处理器来处理这个注解

@Slf4j  //日志
@Component  //一定不要忘记把处理器加到IOC容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill.....");
        //MetaObjectHandler 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);
    }
}

4、测试插入

5、测试更新、观察时间即可

乐观锁

乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试

悲观锁:顾名思义十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
乐观锁:1、先查询,获得版本号 version = 1
-- A 
update user set name  = "kuangshen",version = version + 1
-- B 线程抢先完成,这个时候 version = 2 , 会导致 A 修改失败!
update user set name = "kuangshen",version = version + 1
where id = 2 and version = 1

测试一下MP的乐观锁插件

1、给数据中增加version字段!

2、我们实体类加对应的字段

@Version
    /**
     * @Version 乐观锁注解
     */
    private Integer version;

3、注册组件

/**
 * @author chenfangy
 * @Configuration 被这个注解标注说明是个配置类
 * @MapperScan 扫描我们的mapper文件
 */
@MapperScan("com.kuang.mapper")
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {

    /**
     * 
     *  注册乐观锁插件
     */
    @Bean
    public MybatisPlusInterceptor MybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

4、测试一下

//测试乐观锁成功案例
    @Test
    public void testMybatisPlusLock(){
        //1、查询用户的信息
        User user = userMapper.selectById(1L);
        //2、修改用户的信息
        user.setName("小南风");
        user.setAge(18);
        //3、执行更新操作
        userMapper.updateById(user);
    }
    //测试乐观锁失败案例 多线程下
    @Test
    public void testMybatisPlusLock2(){
        //线程1
        User user = userMapper.selectById(1L);
        user.setName("小南风");
        user.setAge(18);
        // 模拟另外一个线程执行了插队操作
        User user2 = userMapper.selectById(1L);
        user2.setName("小南风222");
        user2.setAge(11);
        //3、执行更新操作
        userMapper.updateById(user2);
        // 自旋锁来多次尝试提交!
        userMapper.updateById(user);    //如果没有乐观锁就会覆盖插队线程的值!
    }

查询操作

 //测试查询
    @Test
    public void testSelectById(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }

    // 测试批量查询
    @Test
    public void testSelectByBatchId(){
        List<User> list = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        list.forEach(System.out::println);
    }

    // 按条件查询之一使用Map使用
    @Test
    public void testSelectByBatchIds(){
        HashMap<String,Object> map = new HashMap<>();
        //自定义要查询
        map.put("name","遇见狂神说");
        map.put("age",12);

        List<User> list = userMapper.selectByMap(map);
        for (User user : list) {
            System.out.println(user);
        }
    }

分页查询

1、原始的limit进行分页

2、pageHelper 第三方插件

3、MP其实也内置了分页插件!

使用方法

1、配置拦截器组件即可!

/**
     *
     * @return 分页查询
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

2、直接使用Page对象即可!

//测试分页查询
    @Test
    public void testPage(){
        /**
         * 参数一:当前页
         * 参数二:页面大小
         * 使用的话前端传  pageNo:页码 pagesize:每页几条数据 current :当前页
         */
        Page<User> page = new Page<>(3,5);
        userMapper.selectPage(page,null);
        for (User record : page.getRecords()) {
            System.out.println(record);
        }
        System.out.println("当前页是------>"+page.getCurrent());
        System.out.println(page.getTotal()+"<---总数是");
    }

删除操作

基本的删除操作:

 // 测试删除
    @Test
    public void testDeleteById(){
        userMapper.deleteById(6L);
    }

    // 通过id批量删除
    @Test
    public void testDeleteBatchId(){
 userMapper.deleteBatchIds(Arrays.asList(1331491338792853506L,1331491338792853507L));	//数组中放的是主键user的id
    }

    // 通过条件删除
    @Test
    public void testDeleteMap(){
        HashMap<String,Object> map = new HashMap<>();
        map.put("name","遇见狂神说");
        userMapper.deleteByMap(map);
    }

在工作中会遇到逻辑删除的问题!

逻辑删除

物理删除:从数据库中直接移除

逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效!delete = 0 --> delete = 1

栗:管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

测试:

1、在数据表中增加一个deleted字段

2、pojo实体类中增加属性!

/**
     *@TableLogic 逻辑删除
     */
    @TableLogic
    private Integer deleted;

3、配置,3.3.0之后官方这样说:

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)

4、测试

当再次去查1号这个用户时:

条件构造器

复杂的sql可以使用它来替代

测试一:

 @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        //查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .isNotNull("name")      //查询年龄不为空
                .isNotNull("email")     //查询邮箱不为空
                .ge("age",12);      //查询年龄大于等于12
        userMapper.selectList(wrapper).forEach(System.out::println);

    }

测试二:

@Test
    void contextLoads2() {
        //查询名字为Tom的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","Tom");
        User user = userMapper.selectOne(wrapper);  //查询一个数据,出现多个结果使用List或Map
        System.out.println(user);
    }

测试三:

@Test
    void contextLoads3() {
        //查询年龄在20~30岁之间的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",20,30);   //区间
        Integer count = userMapper.selectCount(wrapper);//查询结果数
        System.out.println("----->"+count);
    }

测试四: 查看输出的SQL进行分析

@Test
    void contextLoads4() {
        //模糊查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 左和右的区别   %t :以t(t代表某个字符)结尾   t% :以t开头
        wrapper
                .notLike("name","a")
                .likeRight("email","t");

        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);//查询结果数
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }
    }

测试五:

@Test
    void contextLoads5() {

        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // id 在子查询中查出来
        wrapper
                .inSql("id","select id from user where id < 3");

        List<Object> list = userMapper.selectObjs(wrapper);
        list.forEach(System.out::println);

    }

代码自动生成器

demo:

// 代码自动生成器
public class GeneratorCode {
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);  实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/库名?
useSSL=false&useUnicode=true&characterEncoding=utf-8&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("code");	//主文件下面生成都在这个文件夹内
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("address","accountalterrec","catalog","department","user_record","
user_say"); // 设置要映射的表名  根据需要设置
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//strategy.setEntityLombokModel(true); // 自动lombok;
strategy.setLogicDeleteFieldName("deleted"); //配置逻辑删除 --MP
// 自动填充配置
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_2
mpg.setStrategy(strategy);
mpg.execute(); //执行
}
}

感谢狂神老师一路陪伴!狂神老师B站课程链接-->

posted @ 2020-11-26 10:12  CHENFANgY  阅读(94)  评论(0编辑  收藏  举报