MyBatisPlus

概述

官网:https://baomidou.com/

MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

MyBatisPlus可以节省我们大量的工作时间,所有的CRUD可以自动化完成。

JPA,tk-mapper,MyBatisPlus。

特性:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

快速入门

使用第三方插件:

  1. 导入对应的依赖
  2. 研究依赖如何配置
  3. 编写代码,拓展

步骤

  1. 创建数据库,mybaits_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)
    );
    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初始化,导入依赖)

    <!--数据库驱动-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    <!--mybatis-plus-->
    <!--mybatis-plus是自己开发的,并非官方的-->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
    </dependency>

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

  4. 连接数据库,和mybatis相同。

    application.properties

    # mysql5
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #mysql8 驱动不同com.mysql.cj.jdbc.Driver(可兼容mysql5),需要配置时区 serverTimezone=GMT%2B8
  5. 传统方式:pojo-dao(连接mybatis,配置mapper.xml)-service-controller

  6. 使用mybatis-plus后,pojo-mapper接口-使用

    pojo

    package com.zr.pojo;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User { private Long id;
    private String name;
    private Integer age;
    private String email;
    }

    mapper接口

    package com.zr.mapper;
    
    //在对应的mapper上实现基本的类 BaseMapper
    @Repository //代表持久层的
    public interface UserMapper extends BaseMapper<User> { //所有的CRUD操作已经编写完成了
    //不需要配置其它文件了 }

    主启动类

    package com.zr;
    
    //扫描mapper文件夹
    @MapperScan("com.zr.mapper")
    @SpringBootApplication
    public class MybatisPlusApplication { public static void main(String[] args) {
    SpringApplication.run(MybatisPlusApplication.class, args);
    } }

    测试

    package com.zr;
    
    @SpringBootTest
    class MybatisPlusApplicationTests {
    //继承了BaseMapper,所有的方法都来自于父类
    //我们也可以编写自己的拓展方法
    @Autowired
    private UserMapper userMapper; @Test
    void contextLoads() {
    //参数是一个 Wrapper,条件构造器,这里先不用 null
    //查询全部用户
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println); }
    }

注意点:主启动类上扫描mapper下的所有接口。

配置日志输出

我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,就必须看日志。

application.properties中增加

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

日志输出结果:

CRUD拓展

插入

package com.zr;

@SpringBootTest
class MybatisPlusApplicationTests {
//继承了BaseMapper,所有的方法都来自于父类
//我们也可以编写自己的拓展方法
@Autowired
private UserMapper userMapper; //测试插入
@Test
public void testInsert(){ User user = new User();
user.setName("周周");
user.setAge(20);
user.setEmail("813794474@qq.com"); userMapper.insert(user); //自动生成ID
System.out.println(user.toString());
} }

测试结果:

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

主键生成策略

可查询分布式系统唯一id生成解决方案。

雪花算法:SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法(long型的ID)。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id,其中41bit作为毫秒数,10bit作为机器的id(5bit是数据中心,5bit是机器id),12bit作为毫秒内的流水号(意味着每个节点在每秒可产生4096个ID),最后有一位符号位,永远是0。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。

主键配置:

字段上加 @TableId(type = IdType.AUTO)

数据库字段一定要是递增。

package com.zr.pojo;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User { //对应数据库中的主键(uuid,自增id,雪花算法,redis,zookeeper)
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}

测试插入。

源码:

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

更新操作

测试方法中添加:

//更新
@Test
public void testUpdate(){
User user = new User();
user.setId(22L);
user.setName("周周zzzz");
user.setAge(20); userMapper.updateById(user);
}

sql自动动态配置

自动填充

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

阿里巴巴开发手册:所有的数据库,gmt_creat,gmt-modified几乎所有的表都要配置上,而且需要自动化。

方式一:数据库级别(工作中不允许修改数据库的,不建议使用)

  1. 在表中新增字段,create_time,update_time(create_time不勾选根据当前时间更新)

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

    private Date createTime;
    private Date updateTime;
  3. 再次测试更新操作。

方式二:代码级别

  1. 删除时间的默认值

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

    //字段插入填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
  3. 编写处理器来处理注解

    package com.zr.handler;
    
    @Slf4j
    @Component //加到IOC容器中
    public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
    log.debug("start insert....");
    this.setFieldValByName("createTime",new Date(),metaObject);
    this.setFieldValByName("updateTime",new Date(),metaObject);
    } //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
    log.debug("start update....");
    this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    }
  4. 测试插入,观察时间更新。

乐观锁

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

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

乐观锁实现方式:

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

测试乐观锁MP插件:

数据库中增加version字段

同步实体类

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

注册组件(主启动类的扫描mapper放到这里)MyBatisPlusConfig

package com.zr.config;

//扫描mapper文件夹
@MapperScan("com.zr.mapper")
@EnableTransactionManagement
@Configuration //代表是一个配置类
public class MyBatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}

测试:添加以下代码

//测试乐观锁成功
@Test
public void testOptimisticLocker(){
//线程1
//查询用户信息
User user = userMapper.selectById(222L);
//修改用户信息
user.setName("zhourrr");
user.setEmail("813794474@qq.com");
//执行更新操作
userMapper.updateById(user); } //测试乐观锁失败,多线程下
@Test
public void testOptimisticLocker2(){
//线程1
User user1 = userMapper.selectById(222L);
user1.setName("zhourrr111");
user1.setEmail("813794474@qq.com"); //模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(222L);
user2.setName("zhourrr222");
user2.setEmail("813794474@qq.com");
userMapper.updateById(user2); //自旋锁来多次尝试提交
userMapper.updateById(user1); //如果没有乐观锁就会覆盖插队线程的值
}

查询操作

//测试批量查询
@Test
public void testSelectById(){
User user = userMapper.selectById(222L);
System.out.println(user);
} @Test
public void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
// System.out.println(users);
users.forEach(System.out::println);
} //按条件查询 Map
@Test
public void testSelectByMap(){
HashMap<String,Object> map = new HashMap();
//自定义查询
map.put("name","周周zzzz"); List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}

分页查询

分页在网站的使用非常多。

  1. 原始用limit分页
  2. pageHelper 第三方插件
  3. MP内置了分页插件

使用:

  1. 配置拦截器组即可(官网示例)

    MyBatisPlusConfig中添加

     //分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor;
    }
  2. 直接使用page对象即可

    //测试分页查询
    @Test
    public void testPage(){
    //参数1:当前页
    //参数2:页面的大小
    Page<User> page = new Page<>(1,5);
    userMapper.selectPage(page,null);
    page.getRecords().forEach(System.out::println);
    System.out.println(page.getTotal());
    }

删除操作

根据id删除

//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1342445919922073602L);
}
//通过id批量删除
@Test
public void testDeleteBatchByIds(){
userMapper.deleteBatchIds(Arrays.asList(1342445919922073602L,222));
}
//通过Map删除
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","zhourrr222");
userMapper.deleteByMap(map);
}

逻辑删除

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

逻辑删除:在数据库中没有被移除,而是通过一个变量让它失效

应用:管理员可以查看被删除的内容,防止数据的丢失,类似于回收站!

测试:

  1. 在数据库中增加一个deleted字段

  2. 实体类中增加属性

    @TableLogic  //逻辑删除注解
    private Integer deleted;
  3. 配置 MyBatisPlusConfig

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

    application.properties中添加

    #配置逻辑删除
    # 逻辑已删除值(默认为 1)
    # 逻辑未删除值(默认为 0)
    mybatis-plus.global-config.db-config.logic-delete-value=1
    mybatis-plus.global-config.db-config.logic-not-delete-value=0
  4. 测试

观察数据库中id为111的用户deleted的值变为1.

再次查询id为111的用户,显示为空(只会查询deleted值为0的用户)。

性能分析插件

在开发中,我们会遇到一些慢sql。测试,durid....

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

  1. 导入插件

    //sql执行效率插件
    @Bean
    @Profile({"dev","test"}) //设置dev,test环境开启
    public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(100); //设置sql能够执行的最大时间
    performanceInterceptor.setFormat(true); //是否格式化sql语句
    return performanceInterceptor;
    }

    在application.properties中配置环境为测试或者开发环境。

    #设置开发环境
    spring.profiles.active=dev
  2. 测试使用

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

sql执行的时间和格式化的sql

执行的时间超过了设定的时间就会抛出异常。

使用性能分析插件可以帮助我们提高效率!

条件构造器

AbstractWrapper.

我们写一些复杂的sql就可以使用它来替代。

新建一个测试类 WrapperTest。

以下的测试结合日志的 SQL 语句来分析。

测试一:查询name不为空的用户,并且邮箱也不为空,且年龄大于12的用户

package com.zr;

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

测试二:查询名字为周周zzzz的用户

@Test
void test2(){
//查询名字为周周zzzz的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","周周zzzz");
User user = userMapper.selectOne(wrapper); //查询一个数据,查询多个用list或者map
System.out.println(user);
}

测试三:查询年龄在10-20之间的用户

@Test
void test3(){
//查询年龄在10-20之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",10,20);
userMapper.selectCount(wrapper); //查询结果数
}

测试四:查询名字中没有字母e,且邮箱是以t开头的(t%)

@Test
void test4(){
//模糊查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.notLike("name","e")
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

测试五:子查询

@Test
void test5(){
// id 在子查询中查出来
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}

测试六:通过 id 降序排序

@Test
void test6(){
// 通过 id 降序排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

其它更多测试参考mybatis-plus官方文档。

代码自动生成器

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

package com.zr;

public class ZrCode {
public static void main(String[] args) {
//代码自动生成
AutoGenerator mpg = new AutoGenerator(); //全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("zhour");
gc.setOpen(false);
gc.setFileOverride(false); //是否覆盖
gc.setServiceName("%sService"); //去Service的 i 前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true); mpg.setGlobalConfig(gc); //数据源配置
DataSourceConfig ds = new DataSourceConfig();
ds.setUrl("jdbc:mysql://localhost:3306/mybaits_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT");
ds.setDriverName("com.mysql.cj.jdbc.Driver");
ds.setUsername("root");
ds.setPassword("123456");
ds.setDbType(DbType.MYSQL); mpg.setDataSource(ds); //包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog");
pc.setParent("com.zrcode");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setController("controller");
pc.setService("service"); mpg.setPackageInfo(pc); //策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user"); //设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel); //转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel); //转驼峰命名
strategy.setEntityLombokModel(true);
strategy.setLogicDeleteFieldName("deleted"); //逻辑删除 //自动填充
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> list = new ArrayList<>();
list.add(createTime);
list.add(updateTime);
strategy.setTableFillList(list); //乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_3,下划线命名 mpg.setStrategy(strategy); mpg.execute();
}
}

本文项目结构目录:

MyBatisPlus入门学习的更多相关文章

  1. Mybatis-plus入门学习]

    需要的数据库建表语句: #创建用户表 CREATE TABLE user ( id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键', name VARCHAR ...

  2. Mybatis-Plus入门学习笔记(一)

    本文内容 了解Mybatis-Plus 整合Mybatis-Plus 1.了解Mybatis-plus 1.1.Mybatis-Plus介绍 MyBatis-Plus(简称 MP)是一个 MyBati ...

  3. Java开发学习(四十)----MyBatisPlus入门案例与简介

    一.入门案例 MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发.提供效率. SpringBoot它能快速构建Spring开发环境用以整合其他技术,使用起来 ...

  4. 【原创】SpringBoot & SpringCloud 快速入门学习笔记(完整示例)

    [原创]SpringBoot & SpringCloud 快速入门学习笔记(完整示例) 1月前在系统的学习SpringBoot和SpringCloud,同时整理了快速入门示例,方便能针对每个知 ...

  5. vue入门学习(基础篇)

    vue入门学习总结: vue的一个组件包括三部分:template.style.script. vue的数据在data中定义使用. 数据渲染指令:v-text.v-html.{{}}. 隐藏未编译的标 ...

  6. Hadoop入门学习笔记---part4

    紧接着<Hadoop入门学习笔记---part3>中的继续了解如何用java在程序中操作HDFS. 众所周知,对文件的操作无非是创建,查看,下载,删除.下面我们就开始应用java程序进行操 ...

  7. Hadoop入门学习笔记---part3

    2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...

  8. PyQt4入门学习笔记(三)

    # PyQt4入门学习笔记(三) PyQt4内的布局 布局方式是我们控制我们的GUI页面内各个控件的排放位置的.我们可以通过两种基本方式来控制: 1.绝对位置 2.layout类 绝对位置 这种方式要 ...

  9. PyQt4入门学习笔记(一)

    PyQt4入门学习笔记(一) 一直没有找到什么好的pyqt4的教程,偶然在google上搜到一篇不错的入门文档,翻译过来,留以后再复习. 原始链接如下: http://zetcode.com/gui/ ...

随机推荐

  1. WPF -- DataTemplate与ControlTemplate结合使用

    如深入浅出WPF中的描述,DataTemplate为数据的外衣,ControlTemplate为控件的外衣.ControlTemplate控制控件的样式,DataTemplate控制数据显示的样式,D ...

  2. 基于docker快速搭建hbase集群

    一.概述 HBase是一个分布式的.面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文"Bigtable:一个结构化数据的分布式存储系统".就像Bigt ...

  3. 用java输出"Hello,World!"

    Hello,World! 一般步骤 随便新建一个文件夹,存放代码 新建一个java文件 新建.txt文档 文件后缀名改为.java Hello.java [注意]系统可能没有显示后缀名,我们需要手动打 ...

  4. springboot源码解析-管中窥豹系列之BeanFactoryPostProcessor(十一)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  5. [Redis知识体系] 一文全面总结Redis知识体系

    本系列主要对Redis知识体系进行详解.@pdai Redis教程 - Redis知识体系详解 知识体系 学习资料 知识体系 知识体系 相关文章 首先,我们通过学习Redis的概念基础,了解它适用的场 ...

  6. 《数据持久化与鸿蒙的分布式数据管理能力》直播课答疑和PPT分享

    问:hi3861开发板支持分布式数据库吗? 目前,分布式数据库仅支持Java接口,因此Hi3861没有现成的API用于操作分布式数据库. 问:分布式数据管理包括搜索吗? 分布式数据管理包括融合搜索能力 ...

  7. python-实现输出乘法口诀表

    list1 = [1,2,3,4,5,6,7,8,9] 2 def number(num): 3 for i in list1[:num]: 4 result = 1 * i 5 print(&quo ...

  8. IPFS挖矿原理介绍

    随着近几年区块链行业迅速发展,虚拟货币交易机制逐渐成熟,作为「区块链新贵」的 IPFS渐渐走入广大投资者的视线. IPFS 与其激励层的运作原理是投资者们必须要了解的.所以今天我就来和大家讲讲 IPF ...

  9. Android中的TaskStack及启动模式

    目录 前言 如何观察ActivityStack? 几个问题 关键类介绍 ActivityStack的创建与种类 不同启动模式 launchMode Standard SingleTop SingleT ...

  10. PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642 题目描述: A number that will ...