使用第三方组件步骤:
1、导入相应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力
步骤
1、创建数据库 User
表
id | name | age | |
---|---|---|---|
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] |
其对应的数据库 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, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
创建一个springboot的工程
可以使用 Spring Initializer 快速初始化一个 Spring Boot 工程
引入spring boot starter 父工程依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.0.RELEASEversion>
<relativePath/>
parent>
引入 spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
、lombok
、mysql
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.0.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
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
自增主键
//使用注解添加主键策略
@TableId(type = IdType.AUTO)//主键自增策略
private Long id;
注意:使用自增主键必须数据表设置自增
策略 | 说明 |
---|---|
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
//修改测试
@Test
public void UpdateTest() {
User user = new User();
user.setId(6L);
user.setName("一颗小土豆");
user.setAge(24);
user.setEmail("[email protected]");
int update = userMapper.updateById(user);//根据主键id修改
System.out.println(update);
}
/*打印输出的日志
==> Preparing: UPDATE user SET name=?, age=?, email=? WHERE id=?
==> Parameters: 一颗小土豆(String), 24(Integer), [email protected](String), 6(Long)
<== Updates: 1
*/
//这里我们只修改name不修改其他的参数
@Test
public void UpdateTest2() {
User user = new User();
user.setId(6L);
user.setName("一颗小辣椒");
// user.setAge(24);
// user.setEmail("[email protected]");
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("[email protected]");
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), [email protected](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
*/
官方的自动填充的文档
示例工程:
mybatis-plus-sample-auto-fill-metainfo
@TableField(.. fill = FieldFill.INSERT)
生成器策略部分也可以配置!public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
private String fillField;
....
}
@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
}
再面试过程中,我们经常被问到悲观锁和乐观锁!
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现问题,再次更新值测试
乐观锁:顾名思义非常悲观,他总是认为总是会出现问题,无论干什么都上锁!再去操作!
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
乐观锁: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), [email protected](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), [email protected](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), [email protected](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, [email protected], 2020-05-28 07:00:32, 2020-05-28 06:55:47, 1
<== Row: 8, 一堆小西瓜222222, 24, [email protected], 2020-05-28 07:24:07, 2020-05-28 08:29:42, 3
<== Row: 9, 一颗小土豆, 25, [email protected], 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, [email protected], 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, [email protected], 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<== Row: 2, Jack, 20, [email protected], 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<== Row: 3, Tom, 28, [email protected], 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<== Row: 4, Sandy, 21, [email protected], 2020-05-28 06:55:03, 2020-05-28 06:55:03, 1
<== Row: 5, Billie, 24, [email protected], 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, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 2, Jack, 20, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 3, Tom, 28, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 4, Sandy, 21, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 5, Billie, 24, [email protected], 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
则在map
的value
为null
时调用 isNull 方法,为false
时则忽略value
为null
的
allEq({id:1,name:"老王",age:null})
—>id = 1 and name = '老王' and age is null
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
: 过滤函数,是否允许字段传入比对条件中
params
与 null2IsNull
: 同上
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})
—>name = '老王' and age is null
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, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 2, Jack, 20, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 3, Tom, 28, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 4, Sandy, 18, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 5, Billie, 24, [email protected], 1, 0, 2020-05-28 06:55:03, 2020-05-28 06:55:03
<== Row: 7, 一颗小辣椒, 24, [email protected], 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");
//生成 @RestController
控制器
strategyConfig.setRestControllerStyle(true);
//驼峰转连字符 localhost:8080/hello_id_2
strategyConfig.setControllerMappingHyphenStyle(true);
//数据库表配置
generator.setStrategy(strategyConfig);
//3.执行代码生成器
generator.execute();
}
}
注意点:
在最新的3.x版本,实现二级缓存的配置也有了一些改变。
官方建议在service使用缓存,但是你也可以直接在mapper层缓存,这里的二级缓存就是直接在Mapper层进行缓存操作
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) 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;
}
}
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);
}
}
@Repository
@CacheNamespace(implementation=MybatisRedisCache.class,eviction=MybatisRedisCache.class)
public interface CarouselMapper extends BaseMapper {
}
当我们重新配置过 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能够按照时间有序生成。
而twitter
的snowflake
解决了这种需求,最初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);
}
}