MyBatisPlus-入门使用笔记


MyBatisPlus

MyBatisPlus

快速入门

官网文档地址:https://baomidou.gitee.io/mybatis-plus-doc/#/install

使用第三方组件步骤:

1、导入相应的依赖

2、研究依赖如何配置

3、代码如何编写

4、提高扩展技术能力

步骤

1、创建数据库 User

idnameageemail
1Jone18test1@baomidou.com
2Jack20test2@baomidou.com
3Tom28test3@baomidou.com
4Sandy21test4@baomidou.com
5Billie24test5@baomidou.com

其对应的数据库 Schema 脚本如下:

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)
);

其对应的数据库 Data 脚本如下:

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');

初始化工程

创建一个springboot的工程

可以使用 Spring Initializer 快速初始化一个 Spring Boot 工程

添加依赖

引入spring boot starter 父工程依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
    <relativePath/>
</parent>

引入 spring-boot-starterspring-boot-starter-testmybatis-plus-boot-starterlombokmysql 依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

配置

application.yml配置文件中添加mysql的基本配置和项目启动的基本配置

spring:
#配置thymeleaf模板
  thymeleaf:
    cache: false #开启缓存
    prefix: classpath:/templates/
    suffix: .html
    encoding: utf-8
  #配置mysql数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://39.105.27.58/ssmarkert?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
    username: root
    password: lovehxp521..

#项目启动端口
server:
  port: 8080

#打印日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Springboot启动类中添加@MapperScan注解,扫描Mapper文件:

@SpringBootApplication
@MapperScan("com.hxp.ssmkert.mapper")
public class SsmkertApplication {

    public static void main(String[] args) {
  SpringApplication.run(SsmkertApplication.class, args);
    }

}

编码

编写实体类User.java(此处使用了Lombok简化代码)

@Data
public class User{
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

编写Mapper接口 UserMapper.java,所有的CRUD都已经帮我们写完了

public interface UserMapper extends BaseMapper<User> {

}

开始使用

添加测试类,进行整合测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void userMapperTest1(){
        List<User> users = userMapper.selectList(null);//条件为null
        users.forEach(System.out::println);
    }
}

userMapper 中的 selectList()方法的参数为MP内置的条件封装器Wrapper,所以不填写就是无条件

控制台输出:

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

思考问题?

1、SQL谁帮我们写的?Mybatis-Plus都写好了

2、方法哪里来的?Mybatis-Plus都写好了

配置日志

我们的mysql运行情况我们是不知道的,我们需要配置一下日志打印输出,帮助我们开发测试。

appliaction.yml文件中

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

主键的主键策略

1.使用注解添加主键策略

自增主键

//使用注解添加主键策略
@TableId(type = IdType.AUTO)//主键自增策略
private Long id;

注意:使用自增主键必须数据表设置自增

2.通过配置文件配置全局的主键生成策略

主键策略列表:
策略说明
AUTO数据库ID自增
NONE该类型为未设置主键类型
INPUT用户输入ID,该类型可以通过自己注册自动填充插件进行填充
ID_WORKER全局唯一ID (idWorker)
UUID全局唯一ID (UUID)
ID_WORKER_STR字符串全局唯一ID (idWorker 的字符串表示)
AUTO(0),//数据库ID自增
NONE(1),//该类型为未设置主键类型,新版本更新 默认为NONE
INPUT(2),//用户输入ID * 该类型可以通过自己注册自动填充插件进行填充

/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
ID_WORKER(3),//全局唯一ID (idWorker)
UUID(4),//全局唯一ID (UUID)
ID_WORKER_STR(5);//字符串全局唯一ID (idWorker 的字符串表示)

mybatis-plus 的CRUD

update
//修改测试
@Test
public void UpdateTest() {
    User user = new User();
    user.setId(6L);
    user.setName("一颗小土豆");
    user.setAge(24);
    user.setEmail("2426712259@qq.com");
    int update = userMapper.updateById(user);//根据主键id修改
    System.out.println(update);
}
/*打印输出的日志
==>  Preparing: UPDATE user SET name=?, age=?, email=? WHERE id=? 
==> Parameters: 一颗小土豆(String), 24(Integer), 2426712259@qq.com(String), 6(Long)
<==    Updates: 1
*/


//这里我们只修改name不修改其他的参数
@Test
    public void UpdateTest2() {
        User user = new User();
        user.setId(6L);
        user.setName("一颗小辣椒");
//        user.setAge(24);
//        user.setEmail("2426712259@qq.com");
        int update = userMapper.updateById(user);
        System.out.println(update);
    }
/*
==>  Preparing: UPDATE user SET name=? WHERE id=? 
==> Parameters: 一颗小辣椒(String), 6(Long)
<==    Updates: 1
*/

发现:

通过两个修改案例,我们发现mybatis-plus为我们实现的方法是动态SQL

自动填充

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

阿里巴巴开发手册:所有的数据库表都必须配置这两个字段:gmt_create、gmt_modified ,而且需要自动化。

方法一:数据库级别

1、修改数据库

gmt_create/create_time : datetime 添加默认值:CURRENT_TIMESTAMP
gmt_modified/update_time : datetime 添加默认值:CURRENT_TIMESTAMP 勾选根据当前时间戳跟新

在这里插入图片描述

2、再次测试,同步实体类代码

    private Date updateTime;
    private Date createTime;

我们插入一条新的数据,create_time和update_time 都已经帮我们生成了

在这里插入图片描述

我们执行修改操作并不手动更新时间,查看update_time是否发生变化

@Test
    public void UpdateTest() {
        //这里并没有修改时间
        User user = new User();
        user.setId(7L);
        user.setName("一颗小辣椒");
        int update = userMapper.updateById(user);
        System.out.println(update);
    }

执行结果,时间发生了变化。

在这里插入图片描述

方法二:代码级别

代码的自动填充

1.删除数据库的默认值

2.使用mybatis-plus注解

@TableField( fill = FieldFill.INSERT)
private Date createTime;
@TableField( fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

3.创建自定义监听

mybatis-plus 3.3.0版本之前:

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill");
        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);
    }
}

mybatis-plus 3.3.0版本之后:

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
        /* 上面选其一使用,下面的已过时(注意 strictInsertFill 有多个方法,详细查看源码) */
        //this.setFieldValByName("operator", "Jerry", metaObject);
        //this.setInsertFieldValByName("operator", "Jerry", metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
        /* 上面选其一使用,下面的已过时(注意 strictUpdateFill 有多个方法,详细查看源码) */
        //this.setFieldValByName("operator", "Tom", metaObject);
        //this.setUpdateFieldValByName("operator", "Tom", metaObject);
    }
}

4.插入测试

 //测试插入
    @Test
    public  void testInert(){
        User user = new User();
        user.setName("一根小黄瓜");
        user.setAge(24);
        user.setEmail("15542256441@qq.com");
        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

/*
==>  Preparing: INSERT INTO user ( name, age, email, create_time, update_time ) VALUES ( ?, ?, ?, ?, ? ) 
==> Parameters: 一根小黄瓜(String), 24(Integer), 15542256441@qq.com(String), 2020-05-28 15:24:06.774(Timestamp), 2020-05-28 15:24:06.774(Timestamp)
<==    Updates: 1

*/

测试结果 create_time&update_time 都已经生成
在这里插入图片描述

5.更新测试

 //修改测试
    @Test
    public void UpdateTest() {
        User user = new User();
        user.setId(8L);
        user.setName("一坨小西瓜");
        int update = userMapper.updateById(user);
        System.out.println(update);
    }

/*
==>  Preparing: UPDATE user SET name=?, update_time=? WHERE id=? 
==> Parameters: 一坨小西瓜(String), 2020-05-28 15:28:17.975(Timestamp), 8(Long)
<==    Updates: 1
*/

结果:update_time发生变化
在这里插入图片描述

官方的自动填充的文档

自动填充功能

示例工程:

👉 mybatis-plus-sample-auto-fill-metainfo

  • 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
  • 注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置!
public class User {

    // 注意!这里需要标记为填充字段
    @TableField(.. fill = FieldFill.INSERT)
    private String fillField;

    ....
}
  • 自定义实现类 MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
        /* 上面选其一使用,下面的已过时(注意 strictInsertFill 有多个方法,详细查看源码) */
        //this.setFieldValByName("operator", "Jerry", metaObject);
        //this.setInsertFieldValByName("operator", "Jerry", metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
        /* 上面选其一使用,下面的已过时(注意 strictUpdateFill 有多个方法,详细查看源码) */
        //this.setFieldValByName("operator", "Tom", metaObject);
        //this.setUpdateFieldValByName("operator", "Tom", metaObject);
    }
}

注意事项:

  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  • 不需要根据任何来区分可以使用父类的fillStrategy方法
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入填充字段
     */
    INSERT,
    /**
     * 更新填充字段
     */
    UPDATE,
    /**
     * 插入和更新填充字段
     */
    INSERT_UPDATE
}

乐观锁

再面试过程中,我们经常被问到悲观锁和乐观锁!

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

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

主要适用场景

意图:

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
乐观锁:1,先查询,获得版本号 version = 1
-- A线程 修改用户名
update user set name = "一颗小土豆" ,version = version + 1
where id = 2 and version = 1

-- B线程 抢先完成修改,这个时候 version = 2 ,会导致 A线程修改失败
update user set name = "一堆小土豆" ,version = version + 1
where id = 2 and version = 1

测试MP 乐观锁插件

1.添加 代码实体类和数据表 的字段 version

    /*添加注解开启客观锁*/
	@Version
    private Integer version;

在这里插入图片描述

2.创建Mybatis-Plus配置文件,开启乐观锁

@Configuration
public class MybatisPlusConfig {
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

3.测试

测试乐观锁 成功的操作


    /*测试乐观锁 成功的操作*/
    @Test
    public void testOptimsticLocker(){
        //1、查询用户信息
        User user = userMapper.selectById(8L);
        //2、修改用户信息
        user.setName("一堆小西瓜");
        //3、执行更新操作
        userMapper.updateById(user);
    }
/*
==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? 
==> Parameters: 一堆小西瓜222222(String), 24(Integer), 15542256441@qq.com(String), 2020-05-28 15:24:07.0(Timestamp), 2020-05-28 16:29:41.583(Timestamp), 3(Integer), 8(Long), 2(Integer)
<==    Updates: 1
*/

测试乐观锁 失败的操作


    /*测试乐观锁 失败的操作*/
    @Test
    public void testOptimsticLocker2(){
        //模拟两个线程同时操作
        //线程一 没来得及提交,被线程二抢先
        User user = userMapper.selectById(8L);
        user.setName("一堆小西瓜1111111");

        //线程二,
        User user2 = userMapper.selectById(8L);
        user2.setName("一堆小西瓜222222");
        userMapper.updateById(user2);

        userMapper.updateById(user);
    }
/*执行结果
==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? 
==> Parameters: 一堆小西瓜222222(String), 24(Integer), 15542256441@qq.com(String), 2020-05-28 15:24:07.0(Timestamp), 2020-05-28 16:29:41.583(Timestamp), 3(Integer), 8(Long), 2(Integer)
<==    Updates: 1 线程二 执行成功

==>  Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? 
==> Parameters: 一堆小西瓜1111111(String), 24(Integer), 15542256441@qq.com(String), 2020-05-28 15:24:07.0(Timestamp), 2020-05-28 16:29:41.669(Timestamp), 3(Integer), 8(Long), 2(Integer)
<==    Updates: 0 线程一 执行失败
*/

查询操作

1、根据全部


    @Test
    public void userMapperTest1(){
        List<User> users = userMapper.selectList(null);//条件为null
        users.forEach(System.out::println);
    }

2.根据id查询

    @Test
    public  void testSelect(){
        User user = userMapper.selectById(7);
        System.out.println(user);
    }

2.使用多个Id 查询

    @Test
    public void selectByIds(){
        List<User> userList = userMapper.selectBatchIds(Arrays.asList(7L, 8L, 9L));
        userList.forEach(System.out::println);
    }

/*
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? ) 
==> Parameters: 7(Long), 8(Long), 9(Long)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 7, 一颗小辣椒, 24, 15542256441@qq.com, 2020-05-28 07:00:32, 2020-05-28 06:55:47, 1
<==        Row: 8, 一堆小西瓜222222, 24, 15542256441@qq.com, 2020-05-28 07:24:07, 2020-05-28 08:29:42, 3
<==        Row: 9, 一颗小土豆, 25, 242642258@qq.com, 2020-05-28 08:04:42, 2020-05-28 08:04:42, 1
<==      Total: 3
*/

4.使用map多条件查询

   @Test
    public void selectByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","一颗小土豆");
        map.put("age",23);
        List<User> userList = userMapper.selectByMap(map);
        userList.forEach(System.out::println);
    }

/*
==>  Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE name = ? AND age = ? 
==> Parameters: 一颗小土豆(String), 23(Integer)
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 8, 一颗小土豆, 23, 15542256441@qq.com, 2020-05-28 07:24:07, 2020-05-28 08:29:42, 3
<==      Total: 1
*/

分页查询

分页在网站中使用的十分之多!

1、原始的limit 分页

2、pageHelper 分页插件

3、MyBatis-Plus内置分页插件

MyBatis-Plus内置分页插件 如何使用!

1、配置拦截器组件即可

//Spring boot方式
@Configuration
@MapperScan("com.hxp.ssmkert.mapper")//扫描mapper
@EnableTransactionManagement//开启事务 事务管理器
public class MybatisPlusConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    
     /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

2、测试代码

    //分页查询 查询第一页,显示5条数据
    @Test
    public void selectByPage(){
        Page<User> page = new Page<>(1,5);//pageNum,PageSize
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
    }

/*
==> Parameters: 
<==    Columns: id, name, age, email, create_time, update_time, version
<==        Row: 1, Jone, 18, test1@baomidou.com, 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<==        Row: 2, Jack, 20, test2@baomidou.com, 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<==        Row: 3, Tom, 28, test3@baomidou.com, 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<==        Row: 4, Sandy, 21, test4@baomidou.com, 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<==        Row: 5, Billie, 24, test5@baomidou.com, 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<==      Total: 5
*/

删除操作

物理删除

1、通过id删除

  //根据id删除
    @Test
    public void deleteById(){
        int i = userMapper.deleteById(9L);
    }

2、根据id删除多个

//根据id删除多个
@Test
public void deleteByIds(){
    userMapper.deleteBatchIds(Arrays.asList(7L,8L,9L));
}

逻辑删除

1.添加字段,使用注解(数据库添加相应的字段)

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

2.在配置文件中 开启逻辑删除组件

@Configuration
@MapperScan("com.hxp.ssmkert.mapper")//扫描mapper
@EnableTransactionManagement//开启事务 事务管理器
public class MybatisPlusConfig {

    /**
     *逻辑删除组件
     */
    @Bean
    public ISqlInjector sqlInjector(){
        return new LogicSqlInjector();
    }
}

3、逻辑删除


    //根据id删除
    @Test
    public void deleteById2(){
        int i = userMapper.deleteById(8L);
    }

/*
==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0 
==> Parameters: 8(Long)
<==    Updates: 1  我们可以发现现在他走的是修改,不再是删除了
*/

结果:
在这里插入图片描述

性能分析插件

我们在平时的开发中,会遇到一些慢sql。测试!druid

作用:性能分析拦截器,用于输出每条SQL语句及其执行时间

MP也提供性能分析插件,如果超过这个时间就停止运行!

1、导入插件

    /**
     * SQL执行效率插件
     */
    @Bean
    @Profile({"dev","test"})// 设置 dev test 环境开启
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setMaxTime(500);//设置sql执行的最大时间 500毫秒
        performanceInterceptor.setFormat(true);//开启sql格式化
        return performanceInterceptor;
    }

2、查询测试


    @Test
    public void selectByPage(){
        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
    }
/*
==> Parameters: 
<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 1, Jone, 18, test1@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<==        Row: 2, Jack, 20, test2@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<==        Row: 3, Tom, 28, test3@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<==        Row: 4, Sandy, 21, test4@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<==        Row: 5, Billie, 24, test5@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<==      Total: 5
 Time:50 ms  显示执行时间 只要超过我们指定的时间就会报错
			  显示格式化的SQL语句
Execute SQL:
    SELECT
        id,
        name,
        age,
        email,
        version,
        deleted,
        create_time,
        update_time 
    FROM
        user 
    WHERE
        deleted=0 LIMIT 0,5

*/

条件构造器(非常重要)

1、allEq

测试一:null2IsNull 为 true 时

 @Test
   public void allEqTest1(){

        /**
         * allEq 第二个参数 boolean null2IsNull 设为true,默认为true,可以不填
         */
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        HashMap<String, Object> map = new HashMap<>();
        map.put("age",18);
        map.put("name",null);
        wrapper.allEq(map);
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
        /**
         * Execute SQL:
         *     SELECT
         *         *
         *     FROM
         *         user
         *     WHERE
         *         deleted=0
         *         AND name IS NULL   这里我们发现,当我们的参数为null时,查询这个参数为null的。一般我们开发一个多个条件,当参数为null时应该是不查询该参数
         *         AND age = 18
         */
    }

测试二:null2IsNull 为 false 时

@Test
    public void allEqTest2(){
        /**
         * allEq 第二个参数 boolean null2IsNull 设为 false
         */
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        HashMap<String, Object> map = new HashMap<>();
        map.put("age",18);
        map.put("name",null);
        wrapper.allEq(map,false);
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
        /*
        Execute SQL:
        SELECT
           *
        FROM
            user
        WHERE
            deleted=0
            AND age = 18  看这里的查询条件我们会发现,当参数为null时,他会过滤掉该条件
        */
    }

官文:

allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

个别参数说明:

params : key为数据库字段名,value为字段值
null2IsNull : 为true则在mapvaluenull时调用 isNull 方法,为false时则忽略valuenull

  • 例1: allEq({id:1,name:"老王",age:null})—>id = 1 and name = '老王' and age is null
  • 例2: allEq({id:1,name:"老王",age:null}, false)—>id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) 

个别参数说明:

filter : 过滤函数,是否允许字段传入比对条件中
paramsnull2IsNull : 同上

  • 例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})—>name = '老王' and age is null
  • 例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)—>name = '老王'

2、eq 字符的比较 等于 =

eq(R column, Object val)
eq(boolean condition, R column, Object val)
  • 例: eq("name", "老王")—>name = '老王'

测试:

  //测试等于 =
    @Test
    public void eqTest(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","一颗小土豆");
        User user = userMapper.selectOne(wrapper);//selectOne() 查询一个
        System.out.println(user);
        /**
         * ==>  Preparing: SELECT * FROM user WHERE deleted=0 AND name = ?   一个简单的字符串比较 `=`
         * ==> Parameters: 一颗小土豆(String)
         */
    }

3、 ne 不等于

  • 例: ne("name", "老王")—>name <> '老王'

测试:

 @Test
    public void eqTest2(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ne("name","一颗小土豆");//不等于   name != '一颗小土豆'
        List<Object> objects = userMapper.selectObjs(wrapper);//查询object集合
        objects.forEach(System.out::println);
        /*
            SELECT * FROM user WHERE deleted=0 AND name <> ?
            ==> Parameters: 一颗小土豆(String)
            <==    Columns: id, name, age, email, version, deleted, create_time, update_time
            <==        Row: 1, Jone, 18, test1@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
            <==        Row: 2, Jack, 20, test2@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
            <==        Row: 3, Tom, 28, test3@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
            <==        Row: 4, Sandy, 18, test4@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
            <==        Row: 5, Billie, 24, test5@baomidou.com, 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
            <==        Row: 7, 一颗小辣椒, 24, 15542256441@qq.com, 1, 0, 2020-05-28 07:00:32, 2020-05-28 06:55:47
            <==      Total: 6
         */
    }

4、大于gt、小于 lt ,大于等于 ge、小于等于 le

测试

 /* gt 大于 */
    @Test
    public void geTest(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age",24);
        List<Map<String, Object>> mapList = userMapper.selectMaps(wrapper);
        mapList.forEach(System.out::println);
        /*
         * SELECT * FROM user WHERE deleted=0 AND age > ?
        */
    }
/* 小于同理 */

5、between BETWEEN 值1 AND 值2

   /* between  between and */
    @Test
    public void betweenTest(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",18,24);
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

6、notBetween 对应的

    /* notBetween */
    @Test
    public void notBetween(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.notBetween("age",18,24);
        userMapper.selectList(wrapper).forEach(System.out::println);
        /**
         * SELECT * FROM user WHERE deleted=0 AND age NOT BETWEEN ? AND ?
         */
    }

7、like 语句

 /**
     * like 普通的like 两边都查找
     */
    @Test
    public void likeTest(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name","小");
        userMapper.selectList(wrapper).forEach(System.out::println);
        /**
         * SELECT * FROM ser WHERE deleted=0 AND name LIKE '%小%'
         */
    }

8、notLike 语句

 /**
     * notLike
     */
    @Test
    public void notLike(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.notLike("name","小");
        userMapper.selectList(wrapper).forEach(System.out::println);
        /**
         * SELECT * FROM user WHERE deleted=0 AND name NOT LIKE '%小%'
         */
    }

9、likeLeft 语句

 /**
     * liveLeft
     */
    @Test
    public void likeLeft(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.likeLeft("name","土");
        userMapper.selectList(wrapper).forEach(System.out::println);
        /**
         * SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND name LIKE  '%土'
         */
    }

10、likeRight 语句

    /**
     * liveLeft
     */
    @Test
    public void likeRight(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.likeRight("name","T");
        userMapper.selectList(wrapper).forEach(System.out::println);
        /**
         * SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND name LIKE 'T%'
         */
    }

代码生成器

AutoGenerator 是一个Mybatis-Plus 的代码生成器,通过AutoGenerator可以快速的生成Entity、Mapper、Mapper XML、service、Controller等各个模块代码,极大地提升了开发效率。

package com.hxp.ssmkert.test;
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;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: 一颗小土豆
 * \* Date: 2020/5/30
 * \* Time: 11:25
 * \* mybatis-plus代码生成器
 */
public class MyAutoGeneratorTest {
    public static void main(String[] args) {
        String driverClassName = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://39.105.27.58/ssmarkert?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT";
        String username = "root";
        String password = "lovehxp521..";

        //1.首先需要创建一个代码生成器对象
        AutoGenerator generator = new AutoGenerator();
        //2.配置策略
        //(1) 全局配置
        GlobalConfig gconfig = new GlobalConfig();
        gconfig.setAuthor("一颗小土豆");//配置开发人员的名字
        gconfig.setSwagger2(true);//开启Swagger2文档
        gconfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");//设置代码输出的路径
        gconfig.setOpen(false);//是否打开输出目录
        gconfig.setFileOverride(true);//是否覆盖
        gconfig.setServiceName("%sService");//去掉service的前缀I
        gconfig.setIdType(IdType.AUTO);//设置主键策略
        gconfig.setDateType(DateType.ONLY_DATE);//时间类型对应策略,也可以默认
        //添加配置
        generator.setGlobalConfig(gconfig);

        //(2) 设置数据源
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDriverName(driverClassName);
        dsc.setDbType(DbType.MYSQL);
        dsc.setUrl(url);
        dsc.setUsername(username);
        dsc.setPassword(password);
        generator.setDataSource(dsc);

        //(3) 包的配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setModuleName("test");
        packageConfig.setParent("com.hxp.ssmkert");
        packageConfig.setMapper("mapper");
        packageConfig.setEntity("entity");
        packageConfig.setService("service");
        packageConfig.setServiceImpl("service.impl");
        packageConfig.setController("controller");
        //添加包的配置
        generator.setPackageInfo(packageConfig);
        //(4) 策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setInclude("user");//要映射的表名 ,可以传递多个
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);//设置驼峰命名
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略  设置驼峰命名
        strategyConfig.setEntityLombokModel(true);//启用lombok
        strategyConfig.setLogicDeleteFieldName("deleted");//逻辑删除
        //自动填充
        TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
        TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> tableFields = new ArrayList<>();
        tableFields.add(createTime);
        tableFields.add(updateTime);
        //设置自动填充
        strategyConfig.setTableFillList(tableFields);
        //设置乐观锁
        strategyConfig.setVersionFieldName("version");
        //生成 <code>@RestController</code> 控制器
        strategyConfig.setRestControllerStyle(true);
        //驼峰转连字符 localhost:8080/hello_id_2
        strategyConfig.setControllerMappingHyphenStyle(true);

        //数据库表配置
        generator.setStrategy(strategyConfig);


        //3.执行代码生成器
        generator.execute();
    }
}

Mybatis、Mybatis-Plus二级缓存使用

  • 注意点:

  • 在最新的3.x版本,实现二级缓存的配置也有了一些改变。

  • 官方建议在service使用缓存,但是你也可以直接在mapper层缓存,这里的二级缓存就是直接在Mapper层进行缓存操作

    Mybatis的二级缓存实现也十分简单,只要在springboot的配置文件打开二级缓存,即
    mybatis-plus:
      configuration:
        cache-enabled: true
    
    缓存接口的实现
    package com.qsmam.weixin.config;
    import com.qsmam.weixin.util.SpringUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.ibatis.cache.Cache;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.Set;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * \* Created with IntelliJ IDEA.
     * \* User: 一颗小土豆
     * \* Date: 2020/7/23
     * \* Time: 10:53
     * \* 配置mybatis二级缓存 保存到redis
     */
    @Slf4j
    public class MybatisRedisCache implements Cache {
        //读写锁
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
    	//RedisTemplate
        private RedisTemplate<String,Object> redisTemplate;
    
        {
            //RedisTemplate不能使用自动注入,所以使用工具类 BeanFactoryPostProcessor获得
            this.redisTemplate = SpringUtils.getBean(RedisTemplate.class);
        }
    
        private String id;
    
        public MybatisRedisCache(final String id){
            if (id == null){
                throw new IllegalArgumentException("Cache instances require an ID");
            }
            this.id = id;
        }
    
        @Override
        public String getId() {
            return this.id;
        }
    
        @Override
        public void putObject(Object key, Object value) {
            if (redisTemplate == null){
                //犹豫启动期间注入失败,只能运行期间注入,这段代码可以删除
                //redisTemplate = (RedisTemplate<String, Object>) ApplicationContextRegister.getApplicationContext().getBean("RedisTemplate");
            }
            if (value != null){
                redisTemplate.opsForValue().set(key.toString(),value);
            }
        }
    
        @Override
        public Object getObject(Object key) {
            try {
                return redisTemplate.opsForValue().get(key.toString());
            }catch (Exception e){
                log.error("缓存出错!");
                log.error(e.getMessage());
            }
            return null;
        }
    
        /**
         * 删除指定的缓存
         * @param key
         * @return
         */
        @Override
        public Object removeObject(Object key) {
            if (key!=null)
            redisTemplate.delete(key.toString());
            return null;
        }
    
        /**
         * 删除全部 缓存
         */
        @Override
        public void clear() {
            log.debug("清楚缓存");
            if (redisTemplate!=null){
                Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
                if (!CollectionUtils.isEmpty(keys)){
                    redisTemplate.delete(keys);
                }
            }
        }
    
        @Override
        public int getSize() {
            return (int) redisTemplate.execute(new RedisCallback() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.dbSize();
                }
            });
        }
    
        @Override
        public ReadWriteLock getReadWriteLock() {
            return this.readWriteLock;
        }
    }
    

    Spring工具类 方便在非spring管理环境中获取bean

    package com.qsmam.weixin.util;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * \* Created with IntelliJ IDEA.
     * \* User: 一颗小土豆
     * \* Date: 2020/7/23
     * \* Time: 11:57
     * \spring工具类 方便在非spring管理环境中获取bean
     */
    @Component
    public class SpringUtils implements BeanFactoryPostProcessor {
        /** Spring应用上下文环境 */
        private static ConfigurableListableBeanFactory beanFactory;
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            SpringUtils.beanFactory = configurableListableBeanFactory;
        }
        /**
         * 获取对象
         *
         * @param name
         * @return Object 一个以所给名字注册的bean的实例
         * @throws org.springframework.beans.BeansException
         *
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String name) throws BeansException
        {
            return (T) beanFactory.getBean(name);
        }
    
        /**
         * 获取类型为requiredType的对象
         *
         * @param clz
         * @return
         * @throws org.springframework.beans.BeansException
         *
         */
        public static <T> T getBean(Class<T> clz) throws BeansException
        {
            T result = (T) beanFactory.getBean(clz);
            return result;
        }
    
        /**
         * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
         *
         * @param name
         * @return boolean
         */
        public static boolean containsBean(String name)
        {
            return beanFactory.containsBean(name);
        }
    
        /**
         * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
         *
         * @param name
         * @return boolean
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.isSingleton(name);
        }
    
        /**
         * @param name
         * @return Class 注册对象的类型
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.getType(name);
        }
    
        /**
         * 如果给定的bean名字在bean定义中有别名,则返回这些别名
         *
         * @param name
         * @return
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.getAliases(name);
        }
    }
    
    
    mapper.xml文件声明缓存,这里3.x只需要这样配置
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zsy.mapper.CarouselMapper">
        <cache-ref namespace="com.zsy.mapper.CarouselMapper"/>
    </mapper>
    
    Mapper接口使用注解
    @Repository
    @CacheNamespace(implementation=MybatisRedisCache.class,eviction=MybatisRedisCache.class)
    public interface CarouselMapper extends BaseMapper<Carousel> {
    }
    

注意:

当我们重新配置过 RedisTemplate 并且交给spring进行管理的时候就需要制定一个主Bean,

方法:使用@Primary注解

@Primary

package com.qsmam.weixin.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: 一颗小土豆
 * \* Date: 2020/7/1
 * \* Time: 17:14
 * \
 */
@Configuration
public class SpringWebConfig {

    /**
     * 配置reids 序列化 防止乱码等问题
     * @param redisConnectionFactory
     * @return
     */
    @Primary //使用@Primary修饰的bean优先级会更高
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(redisConnectionFactory);
        //使用JSON格式的序列化,保存
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    //自定义cacheManager缓存管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory)
    {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ZERO)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

若果不指定就会出现以下的错误:↓↓↓↓↓↓

***************************
APPLICATION FAILED TO START
***************************

Description:

file [E:\码云项目\easy_mom_service_number\weixin\target\classes\com\qsmam\weixin\mapper\security\UserInfoMapper.class] required a single bean, but 2 were found: 需要一个单例的Bean,但是发现了2个
	- redisTemplate: defined by method 'redisTemplate' in class path resource [com/qsmam/weixin/config/SpringWebConfig.class]
	- stringRedisTemplate: defined by method 'stringRedisTemplate' in class path resource [org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.class]

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

雪花算法

Twitter的分布式自增ID雪花算法snowflake:https://github.com/souyunku/SnowFlake

概述

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。

有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。

twittersnowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。

结构

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。(转换成字符串后长度最多19)

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。

/**
 * 描述: Twitter的分布式自增ID雪花算法snowflake (Java版)
 *
 * @author yanpenglei
 * @create 2018-03-13 12:37
 **/
public class SnowFlake {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1480166465631L;

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
    private final static long DATACENTER_BIT = 5;//数据中心占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId;  //数据中心
    private long machineId;     //机器标识
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次时间戳

    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
                | datacenterId << DATACENTER_LEFT       //数据中心部分
                | machineId << MACHINE_LEFT             //机器标识部分
                | sequence;                             //序列号部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(2, 3);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            System.out.println(snowFlake.nextId());
        }

        System.out.println(System.currentTimeMillis() - start);


    }
}
   this.datacenterId = datacenterId;
    this.machineId = machineId;
}

/**
 * 产生下一个ID
 *
 * @return
 */
public synchronized long nextId() {
    long currStmp = getNewstmp();
    if (currStmp < lastStmp) {
        throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
    }

    if (currStmp == lastStmp) {
        //相同毫秒内,序列号自增
        sequence = (sequence + 1) & MAX_SEQUENCE;
        //同一毫秒的序列数已经达到最大
        if (sequence == 0L) {
            currStmp = getNextMill();
        }
    } else {
        //不同毫秒内,序列号置为0
        sequence = 0L;
    }

    lastStmp = currStmp;

    return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
            | datacenterId << DATACENTER_LEFT       //数据中心部分
            | machineId << MACHINE_LEFT             //机器标识部分
            | sequence;                             //序列号部分
}

private long getNextMill() {
    long mill = getNewstmp();
    while (mill <= lastStmp) {
        mill = getNewstmp();
    }
    return mill;
}

private long getNewstmp() {
    return System.currentTimeMillis();
}

public static void main(String[] args) {
    SnowFlake snowFlake = new SnowFlake(2, 3);

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        System.out.println(snowFlake.nextId());
    }

    System.out.println(System.currentTimeMillis() - start);


}

}


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码神附体

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值