目录
1.@JsonIgnore 注解 忽略序列化字段 jackson提供
2.@TableLogic Mybatis-Plus 忽略逻辑删除 删除为0 未删除为1
3.MybatisPlus有自动填充功能 @TableField的fill属性
① 指定填充字段
@Schema(description = "创建时间")
@JsonIgnore
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
② 指定填充字段的内容
@Component
public class MybatisMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject,"createTime",Date.class,new Date());
}
}
4.类型转换错误 字符串 《---》 枚举类型
前端 ----> WebDateBinder(处理类型转换) SpringMVC 《-----》TypeHandler(处理类型 转换) MyabtisPlus
HttpMessageConverter(类型转换) SpringMVC ---》 前端
前端-》SpringMvc 发生类型转换错误 原因:WebDateBinder类型转换器 只能把枚举对象进行转换 不能进行值传递转换 。前端必须传递 WAITING.不能传递1. 为了解决这个问题 我们要自定义消息转换器
public enum AppointmentStatus implements BaseEnum {
WAITING(1, "待看房"),
CANCELED(2, "已取消"),
VIEWED(3, "已看房");
}
消息转换器 这个类的目的就是 将字符串转换成枚举对象供WebDateBinder使用
@Component
public class StringToItemTypeConverter implements Converter<String, ItemType> {
@Override
public ItemType convert(String code) {
// 获取所有枚举类对象
ItemType[] values = ItemType.values();
// 判断是否存在该类
for (ItemType itemType : values) {
if (itemType.getCode().equals(Integer.valueOf(code))){
return itemType;
}
}
throw new IllegalArgumentException("code:"+code+"非法");
}
}
WebMvc的装配
package com.atguigu.lease.web.admin.custom.config;
import com.atguigu.lease.web.admin.custom.converter.StringToItemTypeConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private StringToItemTypeConverter stringToItemTypeConverter;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(this.stringToItemTypeConverter);
}
}
更新消息转换器:如果还有字符串转枚举的需要 我们需要再写一个相似的类。那么还有很多个这种转换需求呢? ConvertFactory集中化转换类结构。
创建一个工厂类 实现String类型到BaseEnum类型转换
package com.atguigu.lease.web.admin.custom.converter;
import com.atguigu.lease.model.enums.BaseEnum;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.stereotype.Component;
// S : 原始类型 R 目标父类 String类型到BaseEnum的每个子类类型转换
@Component
public class StringToBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
// 方法返回值 应该是 String类型 --》 目标类型的convert对象
@Override
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
// 匿名子类
return new Converter<String, T>() {
@Override
public T convert(String code) {
// 拿到枚举类型的全部实例 -- 泛型
T[] enumConstants = targetType.getEnumConstants();
for (T enumConstant : enumConstants) {
if(enumConstant.getCode().equals(Integer.valueOf(code))){
return enumConstant;
}
}
throw new IllegalArgumentException("code:"+code+"非法");
}
};
}
}
注册到webmvc容器中
package com.atguigu.lease.web.admin.custom.config;
import com.atguigu.lease.web.admin.custom.converter.StringToBaseEnumConverterFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private StringToBaseEnumConverterFactory stringToBaseEnumConverterFactory;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(this.stringToBaseEnumConverterFactory);
}
}
SpringMVC 《-----》TypeHandler(处理类型 转换) MyabtisPlus
在controller层已经将前端传来的字符串转换成枚举类型,然后和请求参数中的枚举类型进行匹配,成功将匹配参数传递到mapper层,但是TypeHandler默认的转换规则是枚举对象实例转换成实例名称即:APP.BAIDU 会转换成 "BAIDU"; MybatisPlus 提供了一个注解@EnumValue 它可以实现枚举对象到属性之间的相互映射。
public enum ItemType implements BaseEnum {
APARTMENT(1, "公寓"),
ROOM(2, "房间");
@EnumValue
@JsonValue
private Integer code;
private String name;
}
HttpMessageConverter(类型转换) SpringMVC ---》 前端
HttpMessageConverter本质是java对象和json字符串相互转换,使用jackson序列化框架 jackson序列化框架的序列化规则和TypeHandler方法一样 枚举类型默认的处理规则都是枚举实例和枚举类型的相互映射。但是提供@JsonValue进行转换 实现枚举实例和属性之间的相互映射。
5. 仅靠sql实现多表查询
处理多表查询时不见可以在service层实现 编写sql也可以实现
CTRL + SHIFT + L 格式化
关联了两个表 使用了左连接的方法 -- 想保留左表的所有属性 。 使用了and 和 where 确定数据的正确性
返回类型是一个嵌套复杂的格式 所以使用了resultmap注解进行自定义输出格式 。collection标签用作一个列表的返回类型 。 propertry为封装属性名 column为sql中查询的数据
<?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.atguigu.lease.web.admin.mapper.AttrKeyMapper">
<resultMap id="AttrKeyVoMap" type="com.atguigu.lease.web.admin.vo.attr.AttrKeyVo">
<id property="id" column="id" />
<result property="name" column="name" />
<collection property="attrValueList" ofType="com.atguigu.lease.model.entity.AttrValue">
<id property="id" column="attr_value_id" />
<result property="name" column="attr_value_name" />
<result property="attrKeyId" column="attr_key_id" />
</collection>
</resultMap>
<select id="listAttrInfo" resultMap="AttrKeyVoMap">
select k.id,
k.name,
v.id attr_value_id,
v.name attr_value_name,
v.attr_key_id
from attr_key k
left join attr_value v
on k.id = v.attr_key_id and v.is_deleted = 0
where k.is_deleted = 0
</select>
</mapper>
另外一个超级复杂sql语句
<select id="pageItem" resultType="com.atguigu.lease.web.admin.vo.apartment.ApartmentItemVo">
select
ai.id,
ai.name,
ai.introduction,
ai.district_id,
ai.district_name,
ai.city_id,
ai.city_name,
ai.province_id,
ai.province_name,
ai.address_detail,
ai.latitude,
ai.longitude,
ai.phone,
ai.is_release,
ifnull(tc.cnt,0) total_room_count,
ifnull(tc.cnt,0) - ifnull(cc.cnt,0) free_room_count
from
( select id,
name,
introduction,
district_id,
district_name,
city_id,
city_name,
province_id,
province_name,
address_detail,
latitude,
longitude,
phone,
is_release
from apartment_info
<where>
is_deleted = 0
<if test="queryVo.provinceId != null">
and province_id = #{queryVo.provinceId}
</if>
<if test="queryVo.cityId != null">
and city_id = #{queryVo.cityId}
</if>
<if test="districtId != null">
and district_id = #{queryVo.districtId}
</if>
</where>
) ai
left join
( select
apartment_id,
count(*) cnt
from room_info
where is_deleted = 0
and is_release = 1
group by apartment_id;
) tc
on ai.id = tc.apartment_id
left join
(
select
apartment_id,
count(*) cnt
from lease_agreement
where is_deleted = 0
and status in (2,5)
group by apartment_id
) cc
on ai.id = cc.apartment_id
</select>
这个sql其实是看起来复杂 实际上也复杂。基本思路:首先我们要清楚我们的目的是什么:查询基本的公寓信息+统计房间总数+统计空房总数。 基本的公寓信息 我们有apartment_info这个表,房间总数我们有room_info 这个表 ,分组统计房间的个数。空间房间数我们可以采取在租赁表中查询已经租赁的房间数 用总房间数-租赁房间数 = 空闲房间数。
代码分析:将三张表查询结果join,此处的sql还用于条件查询,因此我们也要加入where标签和if标签进行动态sql的拼接。ifnull(属性值,默认值)方法用作如果没有属性值,便变成默认值
6. 配置属性映射
法1.application.yml文件中设置配置属性 然后通过注解@Value进行在目标类中使用
minio:
endpoint: http://192.168.16.115:9000
access-key: admin
secret-key: admin123
bucket-name: lease
属性映射
@Configuration
public class MinioConfiguration {
@Value("${minio.endpoint}")
private String endpoint;
@Bean
public MinioClient minioClient(){
MinioClient.builder().endpoint(endpoint)
};
}
法2.映射的属性太多 我们可以单独写一个类存放配置属性
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
}
(在这里出现了一个新的注解 @ConfigurationProperties 他是用来指定前缀的-- 在yml文件中的根)
然后就可以使用到我们的目标类中了
@Configuration
@ConfigurationPropertiesScan("com.atguigu.lease.common.minio")
public class MinioConfiguration {
@Autowired
private MinioProperties minioProperties;
@Bean
public MinioClient minioClient(){
return MinioClient.builder().endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getAccessKey(),minioProperties.getSecretKey())
.build();
};
}
(这里也要讲两个注解一个是 @EnableConfigurationProperties(“MinioProperties.class”)它用在配置类上 单独指定配置类。意味着只有这一个配置属性类被赋值。另外一个就是代码上的@ConfigurationPropertiesScan()它是用来进行扫描配置类的,意味着可以多多个配置类被进行属性赋值)
7.minio使用
在上述步骤中 我们已经创建了Minio的客户端并把它放进io容器中,然后现在就办它运用到我们的项目中。
但是我们发现了一个问题就是我们没有对异常进行处理 我们就去掉try catch 抛出异常 在controller层捕捉异常然后返回对应的数据
@Service
public class FileServiceImpl implements FileService {
@Autowired
private MinioClient minioClient;
@Autowired
private MinioProperties minioProperties;
// 上传图片
@Override
public String upload(MultipartFile file) {
try {
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
if (!bucketExists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
//给桶写权限 只允许自己写 只允许其他人读
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(minioProperties.getBucketName()).config(createBucketPolicyConfig(minioProperties.getBucketName())).build());
}
String filename = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/" + UUID.randomUUID() + "-" + file.getOriginalFilename();
minioClient.putObject(PutObjectArgs.builder().bucket(minioProperties.getBucketName()).stream(file.getInputStream(), file.getSize(), -1).object(filename).contentType(file.getContentType()).build());
//拼装桶的名称
String url = String.join("/",minioProperties.getEndpoint(),minioProperties.getBucketName(),filename);
return url;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String createBucketPolicyConfig(String bucketName) {
return """
{
"Statement" : [ {
"Action" : "s3:GetObject",
"Effect" : "Allow",
"Principal" : "*",
"Resource" : "arn:aws:s3:::%s/*"
} ],
"Version" : "2012-10-17"
}
""".formatted(bucketName);
}
}
(这个类的思路就是 判断有没有桶 ?继续执行:创建桶继续执行。权限模块 给桶给予权限。对文件进行存储 构建了一个独一无二的图片名 存储到桶里面 )
8.全局异常处理
语法:类上添加@ControllerAdvice 在类的方法上面i添加@ExceptionHandler。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handler(Exception e){
e.printStackTrace();
return Result.fail();
}
}
(@ResponseBody 序列化数据返回)
10. @Builder注解是Lombok库的一部分可以快速建造一个对象
List<Long> feeValueIds = apartmentSubmitVo.getFeeValueIds();
if (!CollectionUtils.isEmpty(feeValueIds)) {
ArrayList<ApartmentFeeValue> apartmentFeeValues = new ArrayList<>();
for (Long feeValueId : feeValueIds) {
ApartmentFeeValue apartmentFeeValue = ApartmentFeeValue.builder()
.apartmentId(apartmentSubmitVo.getId())
.feeValueId(feeValueId).build();
apartmentFeeValues.add(apartmentFeeValue);
}
apartmentFeeValueService.saveBatch(apartmentFeeValues);
}
11.分页插件
装入依赖 -- 配置分页插件
@Configuration
@MapperScan("com.atguigu.lease.web.*.mapper")
public class MybatisConfiguration {
// 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
new一个page对象 项目中我们还需要对查询的结果进行操作 因此我们就自定义sql
Page<ApartmentItemVo> page = new Page<>(current, size);
IPage<ApartmentItemVo> result = apartmentInfoService.pageitem(page,queryVo);
我们使用了Ipage进行接收 我们不需要书写sql的时候写分页信息 spring为我们在查询的语句自动添加limit这个。用以来非常方便。
12.自定义异常
前提:异常分类:编译时异常(受检异常)和运行时异常 (不受检异常)。编译时异常:编译器需要我们对这个异常进行处理 try catch ,抛出去。 后者就不需要我们对这个异常进行立刻处理.
1.自定义异常
@Data
public class LeaseException extends RuntimeException {
private Integer code;
public LeaseException(ResultCodeEnum resultCodeEnum){
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
}
(我希望我抛出的异常 有两个属性可以带个前端 code message。RuntimeException里面自带错误信息属性,我只需要在编写构造器的时候用父类的方法就可以了)
2.异常捕获
这个环节和全局异常处理的逻辑是一致的
@ExceptionHandler(LeaseException.class)
@ResponseBody
public Result handlerLease(LeaseException e){
return Result.fail(e.getCode(),e.getMessage());
}
(我们只需要添加注解,并且指定异常类,编写异常处理的结果。妙计1,如果你的传递参数太多可以直接封装一个类进行传递,在本项目,我们要需要传递枚举类中的一个实例就可以将code和message直接传递。须知1:我们只定义的异常处理不会和我们只定义的全局异常处理Exception相撞,匹配机制是:抛出异常的找最近的异常处理类进行处理)
13.contcat 字符串的连接
select
from view_appointment va
left join apartment_info ai
on va.apartment_id = ai.id and ai.is_deleted = 0
<where>
va.is_deleted = 0
<if test="queryVo.provinceId != null">
and ai.provinc_id = #{queryVo.provinceId}
</if>
<if test = "queryVi.cityId != null">
and ai.city_id = #{queryVo.cityId}
</if>
<if test = "queryVo.districtId != null">
and ai.district_id = #{queryVo.districtId}
</if>
<if test = "queryVo.apartmentId != null">
and va.apartment_id = #{queryVo.apartmentId}
</if>
<if test = "queryVo.name != null and queryVo.name != '' ">
and va.name like concat('%',#{queryVo.name},'%')
</if>
<if test = "queryVo.phone != null and queryVo.phone != '' ">
and va.phone like concat('%',#{queryVo.phone},'%')
</if>
</where>
14. 时间返回类型
后端从数据库中获取到时间返回给前端,时间的格式是由json序列化框架决定。如果想修改时间格式,设置json序列化框架。本项目使用的是jackson。
单独配置:在指定的字段增加@jsonFormat注解 如下:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date appointmentTime;
全局配置:在application.yml中增加一下内容
Spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
序列化相关问题:时区。序列化框架会根据当前时区进行序列化,如果想要数据库时间和序列化时间保持一致,需要设置序列化框架和数据库时区保持一致。
单独配置 在指定字段增加@JsonFormat注解 如下
@JsonFormat(timezone="GMT+8")
private Date appointmentTime;
全局配置
spring:
jackson:
time-zone: GMT+8
14 MybatisPlus 的SaveUpdate方法不会将空值进行更新
15 Session 使用公共服务redis进行存储验证码
16 token
17.用户登录