1、@JsonInclude
1.1、comprehension
该标记时jackson包提供的json序列化方法,已集成与SpringBoot2.0中,此方法的配置意在可以对实体json序列化的时候进行对应的数值处理。
2、@ApiModel
用在接口相关的实体类上的注解,它主要是用来对使用该注解的接口相关的实体类添加额外的描述信息,并且常常和@ApiModelProperty注解配合使用。
2.1、Example
@ApiModel(value = "Content对象", description = "")
public class Content extends BaseEntity {
}
3、@ApiModelProperty
作用在接口相关实体类的属性(字段)上的注解,用来对具体的接口相关实体类中的参数添加额外的描述信息,除了可以和 @ApiModel 注解关联使用,也会单独拿出来用。
3.1、Example
@ApiModel(value = "Content对象", description = "")
public class Content extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
}
3.2、@ApiModel和@ApiModelProperty
二者需要导入依赖
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.13</version>
</dependency>
4、全局ID生成方式
用于id的生成策略,即设置id通过何种方式进行生成
4.1、数据库自增长序列或字段生成id
-
优点
-
利用现有数据库系统的功能实现,成本小,代码简单,性能可以接受。ID号单调递增。可以实现一些对ID有特殊要求的业务,比如对分页或者排序结果这类需求有帮助。
-
-
缺点
-
利用现有数据库系统的功能实现,成本小,代码简单,性能可以接受。ID号单调递增。可以实现一些对ID有特殊要求的业务,比如对分页或者排序结果这类需求有帮助。
-
单点故障。在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
-
数据一致性问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
-
难于扩展。在性能达不到要求的情况下,比较难于扩展。ID发号性能瓶颈限制在单台MySQL的读写性能。
-
-
解决方法
针对数据库单点性能问题,可以做高可用优化,设计成主从模式集群,而且要多主,设置起始数和增长步长。
但是随着业务不断增长,当性能再次达到瓶颈的时候,想要再扩容就太麻烦了,新增实例可能还要停机操作,不利于后续扩容。
举例
如果存在多个Master库,则将每个Master库设置的起始数字不一样,可以使Master的个数。将自增ID分别设置成1、3、5.... 另一个自增ID设置成2、4、6。
扩充:ID发号性【分布式ID需要满足的条件】
-
全局唯一:这是最基本的要求,必须保证 ID 是全局唯一的。
-
高性能:低延时,不能因为一个小小的 ID 生成,影响整个业务响应速度。
-
高可用:无限接近于100%的可用性。
-
好接入:遵循拿来主义原则,在系统设计和实现上要尽可能简单。
-
趋势递增:这个要看具体业务场景,最好要趋势递增,一般不严格要求。
4.2、UUID
常见的生成id方式,利用程序生成。
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
-
优点
-
非常简单,本地生成,代码方便,API调用方便。
-
性能非高。生成的id性能非常好,没有网络消耗,基本不会有性能问题。
-
全球唯一。在数据库迁移、系统数据合并、或者数据库变更的情况下,可以 从容应对。
-
-
缺点
-
存储成本高:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。如果是海量数据库,就需要考虑存储量的问题。
-
信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
-
不适用作为主键:ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用。UUID往往是使用字符串存储,查询的效率比较低。
-
UUID是无序的:不是单调递增的,而现阶段主流的数据库主键索引都是选用的B+树索引,对于无序长度过长的主键插入效率比较低。
-
传输数据量大
-
不可读
-
优化UUID不可读
可以使用UUID to Int64的方法
4.3、Redis生成ID
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
比较适合使用Redis来生成日切流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
-
优点
-
不依赖于数据库,灵活方便,且性能优于数据库。
-
数字ID天然排序,对分页或者需要排序的结果很有帮助。
-
-
缺点
-
如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
-
需要编码和配置的工作量比较大。
-
Redis单点故障,影响序列服务的可用性。
-
4.4、Zookeeper生成ID
zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。
很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。
4.5、Twitter的snowflake算法
snowflake(雪花算法)是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
-
优点
-
稳定性高:不依赖于数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。‘
-
灵活方便:可以根据自身业务特性分配bit位。
-
单机上ID单调自增:毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
-
-
缺点
-
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
-
ID可能不是全局递增。在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
-
5、@TableId
该注解用于将某个成员变量指定为数据表主键
5.1、AUTO
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
相当于创建数据库表时使用了AUTO_INCREMENT修饰主键。
5.2、NONE
@TableId(value = "id", type = IdType.NONE)
private Long id;
无状态,该类型为 type 属性的默认主键类型(全局属性为 IdType.INPUT)。当我们设置 @TableId 类型为NONE 时,且不手动设置主键值,MyBatis Plus 将默认给出一个 Long 类型的字符串。
在执行insert方法时,就会自己生成一个Long类型的ID值。
5.3、INPUT
@TableId(value = "id", type = IdType.INPUT)
private Long id;
在执行insert前自行set主键值,否则当我们没有设置id主键值时,MyBaits Plus并不会帮我们设置一个Long类型的ID值。
5.4、ASSIGN_ID
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
分配ID(主键类型为 Number(Long和Integer) 或 String)(since 3.3.0),使用接口 IdentifierGenerator 的方法 nextId(默认实现类为 DefaultIdentifierGenerator 雪花算法)。
5.5、ASSIGN_UUID
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
分配 UUID,主键类型为 String (since 3.3.0),使用接口 IdentifierGenerator 的方法 nextUUID(默认 default 方法)。
6、@TableLogic
@ApiModelProperty(value = "是否已删除")
@TableLogic(value = "0", delval = "1")
@TableField("is_delete")
private Integer isDelete;
调用BaseMapper的deleteById(id)或者IService的removeById(id)时
-
有该注解:从删除方法变为指定更新方法,将指定的字段进行修改为delval的值
-
无该注解:直接从表里删除
注:只对自动注入的sql有效果
-
插入:不做限制
-
查找:追加WHERE条件过滤掉已删除数据且使用wrapper.entity生成的WHERE条件会忽略该字段。
-
更新:追加WHERE条件防止更新到已删除数据且使用wrapper.entity生成的WHERE条件会忽略该字段。
-
删除:转变为更新
YML文件配置
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
7、实体字段校验@Validated
7.1、@NotBlank
只用在String上,表示传进来的值不能为null,而且调用trim()后,长度必须大于0。
7.2、@NotNull
不能为 null,但可以为 empty,一般用在 Integer 类型的基本数据类型的非空校验上,而且被其标注的字段可以使用 @size、@Max、@Min 对字段数值进行大小的控制
举例
@Length(max = 500, message = "回复内容超过{max}个字符")
@NotBlank(message = "回复内容不能为空")
@ApiModelProperty(value = "回复内容")
private String content;
7.3、@NotEmpty
不能为 null,且长度必须大于 0,一般用在集合类上或者数组上
8、@Mapper【Mapstruct】
依赖
<!-- mapstruct相关依赖 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
<scope>provided</scope>
</dependency>
8.1、用于实体类之间的转换
如果实体类Entity、DTO之间的属性名都一样,则转换接口如下
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EntityMapping {
/**
* DTO 转化为 Entity
* @param DTO
* @return
*/
Entity dtoToEntity(DTO dto);
}
如果实体类Entity、DTO之间的某些属性名不同,则转换接口如下
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EntityMapping {
/**
* DTO 转化为 Entity
* @param DTO
* @return
*/
@Mappings({
@Mapping(target = "Entity类的某一属性名", source = "相对应的DTO的某一属性名")
// 多个属性不对应可以用 "," 隔开编写多个@Mapping
// ,@Mapping(target = "Entity类的某一属性名", source = "相对应的DTO的某一属性名")
})
Entity dtoToEntity(Entity entity);
}
实体类之间的集合转换
属性名相同同理如下
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EntityMapping {
/**
* DTOs 转化为 Entitys
* @param DTOs
* @return
*/
List<Entity> dtosToEntitys(List<DTO> DTOs);
}
属性名不同同理如下
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EntityMapping {
/**
* DTOs 转化为 Entitys
* @param DTOs
* @return
*/
@Mappings({
@Mapping(target = "Entity类的某一属性名", source = "相对应的DTO的某一属性名")
// 多个属性不对应可以用 "," 隔开编写多个@Mapping
// ,@Mapping(target = "Entity类的某一属性名", source = "相对应的DTO的某一属性名")
})
Entity dtoToEntity(Entity entity);
/**
* 此时 dtosToEntitys 的实现为循环调用 dtoToEntity 并继承了 dtoToEntity 的属性映射
* DTOs 转化为 Entitys
* @param DTOs
* @return
*/
List<Entity> dtosToEntitys(List<DTO> DTOs);
}
8.2、编辑生成在target文件中的内容
如果编译失败,就去查看该文件,看是否属性都有没有加进来,如果没有,就删除该target文件再次重新编译
8.3、由于多个实体类之间都需要相互转换【设置父类】
创建父类BaseMapStruct
public interface BaseMapStruct<D,E> {
/**
* DTO转Entity
* @param dto
* @return
*/
E toEntity(D dto);
/**
* Entity转DTO
* @param entity
* @return
*/
D toDTO(E entity);
/**
* DTO集合转Entity集合
* @param dtoList
* @return
*/
List<E> toEntity(List<D> dtoList);
/**
* Entity集合转DTO集合
* @param entityList
* @return
*/
List<D> toDTO(List<E> entityList);
}
子类继承父类MapStruct
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CategoryMapStruct extends BaseMapStruct<CategoryDTO, Category> {
}
9、@Api
表示标识这个类是swagger的资源
@Api(value="用户controller",tags={"用户操作接口"})
@RestController
public class UserController {
}
10、@ApiOperation
表示一个http请求的操作,比如下图的意思就是分页查询某一实体列表
@GetMapping
@ApiOperation(value = "某列表", notes = "权限标识为:(某类category:哪种方式page)")
public R<PageResult<Category>> list(CategorySearchDTO search) {
}