开发工具

数据库相关

MyBatisPlus代码生成器(旧版)

3.5.1(不含)以下的版本
MP代码生成器是模板+数据,MP有Velocity(默认)、FreemarkerBeetl三个模板引擎。

  1. 新建一个SpringBoot项目。
  2. pom.xml导依赖。
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- MP代码生成器,3.5.1(不含)之前为旧版 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
		<!-- MP代码生成器模板 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>

        <!-- swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
            <exclusions>
                <!--屏蔽旧版本的swagger-models-->
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--引入指定版本的swagger-annotations和swagger-models解决出现加载swagger时候For input string: ““问题-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--knife4j , swagger-ui增强-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.10</version>
        </dependency>
  1. 在启动类同级目录下新建一个类,在该类的main方法中写自定义配置,主要改包名、类名命名方式和MySQL连接部分,以及生成表名
package com.hisi;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.LikeTable;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Scanner;

public class MPGenerator {


    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
//    public static String scanner(String tip) {
//        Scanner scanner = new Scanner(System.in);
//        StringBuilder help = new StringBuilder();
//        help.append("请输入" + tip + ":");
//        System.out.println(help.toString());
//        if (scanner.hasNext()) {
//            String ipt = scanner.next();
//            if (StringUtils.isNotBlank(ipt)) {
//                return ipt;
//            }
//        }
//        throw new MybatisPlusException("请输入正确的" + tip + "!");
//    }
    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("FN");
//        代码生成是不是要打开文件夹
        gc.setOpen(false);
        gc.setSwagger2(true); //实体属性 Swagger2 注解
//        会在mapper.xml文件中生成一个基础的<resultmap>会映射所有的字段
        gc.setBaseResultMap(true);
        //覆盖掉原先生成的同文件
        gc.setFileOverride(true);
        gc.setDateType(DateType.TIME_PACK);
//        实体类名:直接用表名,  %s=表名
        gc.setEntityName("%s");
//        mapper接口名
        gc.setMapperName("%sMapper");
//        mapper.xml文件名
        gc.setXmlName("%sMapper");
//        业务逻辑接口名
        gc.setServiceName("%sService");
//        业务逻辑实现类名
        gc.setServiceImplName("%sServiceImpl");
//        将全局配置设置到AutoGenerator
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/hisi_pex?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
//        模块名
        pc.setModuleName("hisi");
//        包名
        pc.setParent("com");
//        完整的包名:com.example.quickstart.pms
        mpg.setPackageInfo(pc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        // 表名的生成策略:下划线转驼峰 pms_product -- PmsProduct
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 列名的生成策略: 下划线转驼峰 last_name -- lastName
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);

//        strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
//        在controller类上是否生成RestController
        strategy.setRestControllerStyle(true);
        // 公共父类
//        strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
//        strategy.setSuperEntityColumns("id");
//        要生成的表名,多个用逗号分隔
        strategy.setInclude("hisi_note_order");
//        使用模糊前缀
//        strategy.setLikeTable(new LikeTable("pms_"));
//        strategy.setControllerMappingHyphenStyle(true);
//        设置 表的过滤替换前缀
//        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
//        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
//       进行生成
        mpg.execute();
    }
}

注意:

  1. 生成的mapper没有@Mapper注解,自行添加(@Service这些有)
  2. mapper.xml文件被自动生成在mapper目录下,自行更改到resources文件夹下
  3. 实体类中有MP自动填充时间的话,自行添加@TableField(fill = FieldFill.INSERT_UPDATE)
  1. 以上代码生成效果如下:
    在这里插入图片描述
    参考链接:
    Mybatis-Plus之代码生成器(模板,可直接套用超级好用,但是建议初学者少用)
    参考视频
    官方文档

MyBatisPlus代码生成器(新版)

MP 3.5.1及以上的版本

package com.hisi.fourk;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;

import java.util.Collections;

/**
 * MyBatis-Plus 代码生成器模板
 */
public class CodeGenerator {

    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/hisi-fourk?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "123456";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author("FN") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
//                            .outputDir(System.getProperty("user.dir") + "\\src\\main\\java") // 指定输出目录
                            // 再加上当前模块名,在子工程时使用该配置
                            .outputDir(System.getProperty("user.dir") + "/hisi-4ksurveillance-task" + "\\src\\main\\java")
                            .commentDate("yyyy-MM-dd"); //设置时间格式(类注释作者下面那个时间)
                })
                .packageConfig(builder -> {
                    builder.parent("com.hisi") // 设置父包名
                            .moduleName("fourk") // 设置父包模块名
                            .entity("model")    // 设置实体类包名
                            .service("service")
                            .controller("controller")
                            .mapper("mapper")
                            .xml("mapper")
//                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/hisi-4ksurveillance-task" + "\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("hisi_business_result") // 设置需要生成的表名
//                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀,如hisi_test,生成的类名只需要test
                            .serviceBuilder()   // service策略配置
                            .formatServiceFileName("%sService") // service类名,%s表示替换为表名
                            .formatServiceImplFileName("%sServiceImpl") // service实现类的类名
                            .entityBuilder()    // 实体类策略配置
                            .enableLombok()     // 开启Lombok
//                            .logicDeleteColumnName("deleted")   // 说明逻辑删除是哪个字段
                            .enableTableFieldAnnotation()   // 属性加上说明注解
                            .controllerBuilder()    // controller策略配置
                            .formatFileName("%sController")
                            .enableRestStyle()      // 开启@RestController
                            .mapperBuilder()
                            .superClass(BaseMapper.class)   // 继承哪个父类
                            .formatMapperFileName("%sMapper")
                            .enableMapperAnnotation()   // 加上@Mapper注解
                            .formatXmlFileName("%sMapper");  // xml名称
                })
//                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

}

多数据源

springboot多数据源JdbcTemplate和SessionFactory两种方式学习
springboot多数据源配置-通过SqlSessionFactory指定的数据源来操作指定目录的XML文件的方式
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})

MP乐观锁插件

  1. MP配置文件配置乐观锁插件。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
  1. 在实体类字段上添加@Version注解,表示乐观锁字段(可以是Integer version 或者Localdatetime updateTime等字段)。
  2. 和以前一样直接写代码就行,在Update时自动会拦截SQL设置乐观锁。
        boolean update = this.updateById(warehouseLeadBox);
        return Result.success(update);

参考链接:
MyBatis-Plus 实现乐观锁
Mybatis Plus 乐观锁插件(手把手教学)

MySQL锁死处理

  1. 新建查询 , 查出当前的所有进程,找出那些卡住的进程
    show full processlist;
  2. 将对应的进程杀掉
    kill 1527

注:Time数值过大或者State含Lock、Warn等字符表示异常。

MySQL存储JSON

参考文章
场景:需要在一个表的某个字段存储对象(包括自定义类、List、Map等)。
思路:表中使用json类型字段存储数据,Java Entity取出时用转换器转换为指定对象。
以存储一个List<Double>为例

  • Json字段类型到Java类中指定为List<Double>
    然后使用MP@TableField注解的typeHandler属性指定为自定义的拦截器,拦截器用于json到指定对象类型之间的转换。
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseDeviceManage extends TenantEntity {
	@ApiModelProperty(value = "经纬度")
	// ListDoubleTypeHandler是自定义的拦截器,用于Json到指定类型的转换
	@TableField(value = "lng_and_lat", typeHandler = ListDoubleTypeHandler.class)
	private List<Double> lngAndLat;

	@ApiModelProperty(value = "图标")
	private String icon;

	@ApiModelProperty(value = "备注")
	private String remark;

	@ApiModelProperty(value = "图片URI")
	private String pic;

	@ApiModelProperty(value = "是否标记,默认false")
	private Boolean isMark;
}
  • 自定义类型拦截器ListDoubleTypeHandler,用于Json到java对象的互转。
    如果需要转换为其他Java对象,将类中的List<Double>改为需要的对象类型就行。
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @Author: 
 * @Date: 2024/5/28 13:54
 * @Description: 
 * 数据库存储json,获取数据转为List<Double>
 **/
public class ListDoubleTypeHandler extends BaseTypeHandler<List<Double>> {

	private static final ObjectMapper objectMapper = new ObjectMapper();

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, List<Double> parameter, JdbcType jdbcType)
		throws SQLException {
		try {
			String jsonString = objectMapper.writeValueAsString(parameter);
			ps.setString(i, jsonString);
		} catch (JsonProcessingException e) {
			throw new SQLException("Error converting List<Double> to JSON string", e);
		}
	}

	@Override
	public List<Double> getNullableResult(ResultSet rs, String columnName) throws SQLException {
		String jsonString = rs.getString(columnName);
		return parseJsonString(jsonString);
	}

	@Override
	public List<Double> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		String jsonString = rs.getString(columnIndex);
		return parseJsonString(jsonString);
	}

	@Override
	public List<Double> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		String jsonString = cs.getString(columnIndex);
		return parseJsonString(jsonString);
	}

	private List<Double> parseJsonString(String jsonString) throws SQLException {
		if (StrUtil.isBlank(jsonString)) {
			return null;
		}
		try {
			return objectMapper.readValue(jsonString, new TypeReference<List<Double>>() {
			});
		} catch (JsonProcessingException e) {
			throw new SQLException("Error converting JSON string to List<Double>", e);
		}
	}
}
  • 如果是xml手写SQL,查询返回值需要使用resultMap指定json字段的typeHandler。
    <resultMap id="deviceManageResultMap" type="com.dongfang.hope.manage.vo.DeviceManageVO">
        <id property="id" column="id"/>
        <result property="deviceName" column="device_name"/>
        <result property="deviceCode" column="device_code"/>
        <result property="deviceType" column="device_type"/>
        <!-- lng_and_lat是需要json转对象的字段,指定typeHandler -->
        <result property="lngAndLat" column="lng_and_lat"
                typeHandler="com.dongfang.hope.manage.handler.ListDoubleTypeHandler"/>
        <result property="icon" column="icon"/>
        <result property="remark" column="remark"/>
        <result property="pic" column="pic"/>
        <result property="isMark" column="is_mark"/>
    </resultMap>

    <select id="pageDevice" resultMap="deviceManageResultMap">
        SELECT id,
        device_name,
        device_code,
        device_type,
        lng_and_lat,
        icon,
        remark,
        pic,
        is_mark
        FROM test
    </select>

json字段存储高精度小数,导致精度丢失问题

  • 环境描述:java中使用List<List<List<BigDecimal>>>存储地图区域坐标信息。
    数据库使用json类型进行存储。
    使用mybatisplus的typeHandler来进行两者的转换。
	@ApiModelProperty(value = "地图-绘制区域")
	@TableField(value = "draw_region", typeHandler = TwoListDoubleTypeHandler.class)
	private List<List<List<BigDecimal>>> drawRegion;

存储到mysql后,格式是这样的:
[[[104.04460443658631,30.24296857808761],[104.0615093091583,30.2387778743828],[104.04709044725873,30.24296857808761],[104.04460443658631,30.24296857808761]]]

  • 问题描述:接收前端的传入值没有问题。
    使用mybatisplus的.saveOrUpdate()方法,生成的SQL语句没有问题,注入的参数正常。
    mysql执行sql语句后,因mysql的原因导致精度丢失。
  • 问题摸排:mybatisplus生成的更新json字段sql是set draw_region = "[[[省略内容]]]"
    但是推荐的更新json字段语句是用函数更新draw_region = JSON_SET( draw_region, '$', '[[[。。。]]]' )
    再但是,用函数进行更新后虽然精度没问题了,但存储在数据库的内容被""引号包含了,导致mybatisplus在取出来准备解析成java类型时报错了。
    所以需要在mybatisplus的typeHanlder中,插入/更新数据时将内容加上""包含保证到mysql后不会精度丢失;查询数据时将""去掉让MP能成功转换为java类型。
  • 下面是具体代码
    service:
	@Override
	public R upsertPlot(SmartPlantPlot smartPlantPlot) {
		if (!CollectionUtils.isEmpty(smartPlantPlot.getDrawRegion())) {
			// 如果在地图上绘制了区域,设置为已标记状态
			smartPlantPlot.setIsMark(true);
		}
		// drawRegion在数据库中是二维数组json类型;
		// 如果使用普通SET语句,二位数组json的新旧值比对会相同,所以必须使用SET JSON_SET(draw_region,'$' '内容')函数来更新json字段
		// 此处也可以使用mybatisplus的sql拦截器解决(懒得研究,所以采用先set为null的方法)
		if (smartPlantPlot.getId() != null) {
			this.lambdaUpdate()
				.set(SmartPlantPlot::getDrawRegion, null)
				.eq(SmartPlantPlot::getId, smartPlantPlot.getId()).update();
		}
		boolean update = super.saveOrUpdate(smartPlantPlot);
		return update ? R.success("新增/编辑成功") : R.fail("新增/编辑失败");
	}

SmartPlantPlot实体类:

	@ApiModelProperty(value = "地图-绘制区域")
	@TableField(value = "draw_region", typeHandler = TwoListDoubleTypeHandler.class)
	private List<List<List<BigDecimal>>> drawRegion;

类字段的typeHandler:

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @Author: 
 * @Date: 2024/5/28 16:16
 * @Description: 地图管理-地块二维经纬度
 * 数据库存储json,获取数据转为List<List<List<Double>>>
 **/
public class TwoListDoubleTypeHandler extends BaseTypeHandler<List<List<List<Double>>>> {

	private static final ObjectMapper objectMapper = new ObjectMapper();

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, List<List<List<Double>>> parameter, JdbcType jdbcType)
		throws SQLException {
		try {
			String jsonString = objectMapper.writeValueAsString(parameter);
			// mysql json存储高精度数字时,如果不用""包含去存储会导致精度丢失(数据库层面的)
			if (StrUtil.isNotBlank(jsonString)) {
				jsonString = "\"" + jsonString + "\"";
			}
			ps.setString(i, jsonString);
		} catch (JsonProcessingException e) {
			throw new SQLException("Error converting List<List<Double>> to JSON string", e);
		}
	}

	@Override
	public List<List<List<Double>>> getNullableResult(ResultSet rs, String columnName) throws SQLException {
		String jsonString = rs.getString(columnName);
		return parseJsonString(jsonString);
	}

	@Override
	public List<List<List<Double>>> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		String jsonString = rs.getString(columnIndex);
		return parseJsonString(jsonString);
	}

	@Override
	public List<List<List<Double>>> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		String jsonString = cs.getString(columnIndex);
		return parseJsonString(jsonString);
	}

	private List<List<List<Double>>> parseJsonString(String jsonString) throws SQLException {
		if (StrUtil.isBlank(jsonString)) {
			return null;
		}
		try {
			// 取出给mybatisPlus解析时,去掉""
			if (StrUtil.isNotBlank(jsonString) &&
				jsonString.startsWith("\"") &&
				jsonString.endsWith("\"")) {
				jsonString = jsonString.substring(1, jsonString.length() - 1);
			}
			return objectMapper.readValue(jsonString, new TypeReference<List<List<List<Double>>>>() {
			});
		} catch (JsonProcessingException e) {
			throw new SQLException("Error converting JSON string to List<List<List<Double>>>", e);
		}
	}

}

MP自动映射枚举类

定义一个接口,用于增强枚举类(可选)

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.IEnum;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * 枚举类型基类
 */
public interface DictionaryEnum<T extends Serializable> extends IEnum<T> {

    /**
     * 描述信息
     *
     * @return 描述
     */
    String getDesc();


    /**
     * 获取枚举编码
     *
     * @return 编码
     */
    default String getCode() {
        return String.valueOf(this.getValue());
    }


    /**
     * 枚举数组转集合
     *
     * @param dictionaries 枚举
     * @return 集合
     */
    static List<BaseDictionary> getList(DictionaryEnum<?>[] dictionaries) {
        if (dictionaries == null) {
            return null;
        }
        return Arrays.stream(dictionaries).map(dictionary -> BaseDictionary.builder()
                .code(dictionary.getCode()).desc(dictionary.getDesc())
                .build()).collect(Collectors.toList());
    }

    /**
     * 获取指定类型枚举映射
     *
     * @param enumClass 枚举类
     * @param type      类型
     * @param <E>       包装类
     * @return 枚举值
     */
    static <E extends DictionaryEnum<?>> E of(Class<E> enumClass, Serializable type) {
        E[] enumConstants = enumClass.getEnumConstants();
        for (E e : enumConstants) {
            final Serializable value = e.getValue();
            if (value == type) {
                return e;
            }
        }
        return null;
    }

    char SEPARATOR = ',';


    /**
     * 转换成字符串
     *
     * @param dictionaries 枚举
     * @return 转换结果
     */
    static <E extends DictionaryEnum<?>> String of(List<E> dictionaries) {
        if (CollectionUtil.isEmpty(dictionaries)) {
            return null;
        }
        return dictionaries.stream()
                .filter(Objects::nonNull)
                .map(DictionaryEnum::getCode).collect(Collectors.joining(","));
    }

    /**
     * 转换成集合枚举
     *
     * @param enumClass    枚举类
     * @param dictionaries 枚举
     * @return 转换结果
     */
    static <E extends DictionaryEnum<?>> List<E> of(Class<E> enumClass, String dictionaries) {
        if (StrUtil.isBlank(dictionaries)) {
            return null;
        }
        final List<String> split = StrUtil.split(dictionaries, SEPARATOR);
        return split.stream().filter(Objects::nonNull)
                .map(type -> of(enumClass, Integer.parseInt(type)))
                .collect(toList());
    }

}

创建枚举类

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonValue;
import com.hisi.cloud.db.mybatis.DictionaryEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "(0:正常;1:我厂回收;)")
@JsonFormat
public enum SaleOrderTypeEnum implements DictionaryEnum<Integer> {

    NORMAL(0, "成品"),

    OUR_FACTORY_RECYCLES(1, "我厂回收")

    ;
    @EnumValue
    @JsonValue
    private Integer type;

    @Schema(description = "描述")
    private String desc;

    @JsonCreator
    public static SaleOrderTypeEnum of(Integer type) {
        if (type == null) {
            return null;
        }
        for (SaleOrderTypeEnum info : values()) {
            if (info.type.equals(type)) {
                return info;
            }
        }
        return null;
    }

    public boolean eq(String val) {
        return this.name().equalsIgnoreCase(val);
    }

    public boolean eq(SaleOrderTypeEnum val) {
        if (val == null) {
            return false;
        }
        return eq(val.name());
    }

    @Override
    public Integer getValue() {
        return this.type;
    }

    @Override
    public String toString() {
        return String.valueOf(type);
    }
    
}

MyBatis返回Map<String,List<Entity>> 格式数据

前言:@MapKey("")只能查Map<key,value>格式的数据(目前还没找到value可以=List<Entity>的方法),现在介绍的方法可以做到Map<key,List<Entity>>
思路:

  • 重点是把数据查出来在java里做map分组,不是sql里分组。
  • 查数据库时将查询数据放到Entity的List里,然后在Entity额外定义一个字段存放map的key。
  • mapper返回值是List<Entity>的数据,通过stream流根据Entity里额外的map key字段做分组。
@Data
public class SemiFinishedLibraryMapDTO {

    /**
     * key_id
     */
    private Long containerId;

    /**
     * list
     */
    private List<SemiFinishedLibrary> semiFinishedLibraries;
}
  1. mapper
   List<SemiFinishedLibraryMapDTO> getSemiFinishedMapByContainerIds(@Param("containerIds") List<Long> containerIds);

注意:<collection> </collection>里的<result/>必须包含select查的所有字段,否则会封装不到List里。

    <resultMap id="semifinishedMap" type="com.cngt.common.model.dto.SemiFinishedLibraryMapDTO">
        <result column="container_id" jdbcType="BIGINT" property="containerId"/>
        <collection property="semiFinishedLibraries" ofType="com.cngt.common.model.SemiFinishedLibrary">
            <result column="container_id" jdbcType="BIGINT" property="containerId"/>
            <result column="source_name" property="sourceName"/>
            <result column="source_id" property="sourceId"/>
        </collection>
    </resultMap>

    <select id="getSemiFinishedMapByContainerIds" resultMap="semifinishedMap">
        SELECT container_id, source_name, source_id
        FROM cngt_semi_finished_library csfl
        WHERE deleted = 0
        AND type = 1
        AND container_id IN
        <foreach collection="containerIds" item="containerId" open="(" close=")" separator=",">
            #{containerId}
        </foreach>
    </select>
  1. service
        List<SemiFinishedLibraryMapDTO> semiFinishedLibraryMapDTOS = baseMapper.getSemiFinishedMapByContainerIds(containerIds);
        Map<Long, List<SemiFinishedLibrary>> semiFinishedMap = semiFinishedLibraryMapDTOS.stream()
                .collect(Collectors.toMap(SemiFinishedLibraryMapDTO::getContainerId, SemiFinishedLibraryMapDTO::getSemiFinishedLibraries));

参考链接

Spring相关

Spring Boot定时任务

注:spring boot版本需3.0以上。
SpringBoot使用@Scheduled注解实现定时任务
以下提供一个定时任务的模板(注:一般习惯建一个quartz包来放定时任务):

  1. 定义一个名为(不强制)QuartzConfiguration类,用于配置定时任务。
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfiguration {
    /**
     * 定时任务1
     *
     * @param taskHa
     * @return
     */
    @Bean(name = "taskHaDetail")
    public MethodInvokingJobDetailFactoryBean taskHaDetail(TaskHa taskHa) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        // 是否并发执行
        jobDetail.setConcurrent(false);
        // 为需要执行的实体类对应的对象
        jobDetail.setTargetObject(taskHa);
        // 需要执行的方法
        jobDetail.setTargetMethod("task");
        return jobDetail;
    }

    // 配置定时任务1的触发器
    @Bean(name = "taskHaTrigger")
    public CronTriggerFactoryBean taskHaTrigger(JobDetail taskHaDetail) {
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(taskHaDetail);
		// 定时任务每隔多少时间执行一次
        trigger.setCronExpression("0 3/20 * * * ?");
        return trigger;
    }

    /**
     * 定时任务2
     *
     * @param sobeyK8sTask
     * @return
     */
    @Bean(name = "sobeyK8sTaskDetail")
    public MethodInvokingJobDetailFactoryBean sobeyK8sTaskDetail(SobeyK8sTask sobeyK8sTask) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        // 是否并发执行
        jobDetail.setConcurrent(false);
        // 为需要执行的实体类对应的对象
        jobDetail.setTargetObject(sobeyK8sTask);
        // 需要执行的方法
        jobDetail.setTargetMethod("task");
        return jobDetail;
    }

    // 配置定时任务2触发器
    @Bean(name = "sobeyK8sTaskTrigger")
    public CronTriggerFactoryBean sobeyK8sTaskTrigger(JobDetail sobeyK8sTaskDetail) {
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(sobeyK8sTaskDetail);
        // 定时任务每隔多少时间执行一次
        trigger.setCronExpression("0 1/10 * * * ?");
        return trigger;
    }

    // 配置Scheduler
    @Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger sobeyK8sTaskTrigger, Trigger taskHaTrigger) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        // 延时启动,应用启动1秒后
        bean.setStartupDelay(1);
        // 注册触发器
        bean.setTriggers(sobeyK8sTaskTrigger, taskHaTrigger);
        return bean;
    }
}
  1. 定义具体的定时任务内容。
@Slf4j
@Component
@EnableScheduling
public class SobeyK8sTask {

    public void task() {
    	// 最好try catch一下
        try {
        	// 任务内容
        } catch (Exception e) {
            e.printStackTrace();
            log.error("执行定时任务失败:{}", e);
            return;
        }
    }
}
  1. 配置定时任务详解:
    在这里插入图片描述

定时任务的时间含义:
trigger.setCronExpression("0 0 0/1 * * ?")为例,表示定时任务将每小时执行一次。

Cron表达式的格式是秒 分 时 日 月 周 年,其中各个字段的含义如下:
秒(Seconds):0-59
分钟(Minutes):0-59
小时(Hours):0-23
日(Day of month):1-31
月(Month):1-12 或者 JAN-DEC
周(Day of week):1-7 或者 SUN-SAT
年(Year):可选字段,留空或者使用 “*” 表示每年都执行,也可以指定特定年份,例如:2022, 2023, 2024。
=================
对于*?的解释:
在日字段中,*表示每月的任意一天,而?表示该字段的设定是不确定的,只关注其他参数的设定。
在周字段中,*表示每周的任意一天,而?同样表示该字段的设定是不确定的,只关注其他参数的设定。
其余字段用*,表示每天、每分的任意时间。
=================
对于0/1的解释:
固定值:使用具体的数字表示该时间单位,例如 1 表示每小时的第1小时。
递增/间隔值:使用 /n 或 n/m 的形式表示时间单位的递增或间隔。其中,/n 表示从最小值开始,每隔 n 个时间单位触发一次,而 n/m 表示从 n 开始,每隔 m 个时间单位触发一次。

复制Spring Boot模块后改名问题

原文章链接
确保彻底改成功而不损坏原项目,严格按下面步骤来:
1、重命名根目录名称,修改.idea目录下所有.xml文件里面的项目名称、包名
2、重命名根目录下.iml文件名
3、打开pom文件,修改groupId、artifactId、name等项目名、包名
4、idea打开改好的项目,选中要修改的包名,右键refactor-rename。
5、Application文件右键refactor-rename。
6、CTRL+SHIFT+R全局搜索旧包名,需要的地方修改。ok!

Validation参数校验

使用方法参考
注解参考01
注解参考02

@Valid和@Validated区别:

  • @Valid在Controller形参处,需配合BindingResult使用,在Controller方法里对BindingResult做处理。
  • @Validated在Controller形参处,无需BindingResult,但需要全局异常处理器拦截其异常进行处理(否则会直接抛出异常不再执行方法下文)。

校验注解作用域:

  • @Validated @Valid —— entity(实体)
  • @NotBlank —— String
  • @NotNull —— Integer
  • @NotEmpty —— java.util.Collection(集合)

从Nacos拿配置的方式

参考文章

跨域

注意:请求头一定设置允许跨域,因为浏览器询问服务器是否允许跨域时,这个询问请求是在请求头上的(Origin字段,向服务器声明请求的来源)。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Bean
    public CorsFilter corsFilter()
    {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置访问源地址
        config.addAllowedOriginPattern("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        // 有效期 1800秒
        config.setMaxAge(1800L);
        // 添加映射路径,拦截一切请求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        // 返回新的CorsFilter
        return new CorsFilter(source);
    }
}

Excel导出自定义单元格

参考文章

SpringBoot + Socket通信

参考文章1(推荐)
参考文章2

Spring异步实现

自定义异步线程池,注册Bean交给Spring管理。

/**
* 线程池参数配置,多个线程池实现线程池隔离,@Async注解,默认使用系统自定义线程池,可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async("taskName")
*/
@Configuration
@EnableAsync
public class SyncConfiguration {
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor executor() {
        // 返回可用处理器的Java虚拟机的数量
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("系统最大线程数  : " + i);
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(10);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(100);
        //缓存队列
        taskExecutor.setQueueCapacity(50);
        //许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(200);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("async-");
        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略:
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

在需要使用异步的方法上加@Async(value = "asyncPoolTaskExecutor")注解,value就是线程池的Bean名。

@Async(value = "asyncPoolTaskExecutor")
public void syncMethod(String param) {
}

其它实现方式参考链接

Java相关

如何封装第三方工具

全文以OSS为例

  1. .yml写配置
  2. config.xxxConfig读取配置文件
@Component
@Getter
@Setter
public class OSSCloudConfig {
    @Value("${OSS.endpoint}")
    private String endPoint;

    @Value("${OSS.accessKeyId}")
    private String accessKeyId;

    @Value("${OSS.accessKeySecret}")
    private String accessKeySecret;

    @Value("${OSS.bucketName}")
    private String bucketName;
}
  1. client.xxxClient工具类,封装第三方工具的各种方法
@Component
public class OSSCloudClient {
    @Autowired
    private OSSCloudConfig ossCloudConfig;

    /**
     * @return 获取OSS客户端
     */
    public OSS getOSSClient() {
        return new OSSClientBuilder().build(ossCloudConfig.getEndPoint(), ossCloudConfig.getAccessKeyId(),
                ossCloudConfig.getAccessKeySecret());
    }

    /**
     * 关闭OSS客户端
     *
     * @param ossClient
     */
    public void destroy(OSS ossClient) {
        ossClient.shutdown();
    }

    /**
     * 断点续传
     *
     * @param fileName  文件在OSS中的名字
     * @param ossClient OSS连接客户端
     */
    public void breakpointResume(String fileName, OSS ossClient) {
    	//使用完后在这里关闭
        this.destroy(ossClient);
    }
  1. 创建Controller接口,使用该工具类
@RestController
@RequestMapping("/download")
@Api(value = "OSS对象存储")
public class OSSController {
    @GetMapping("/{url}")
    @ApiOperation(value = "单个文件下载")
    public HisiResult<String> downloadFile(String url) {
        return null;
    }
}

参考文章:
文章1
文章2

对List进行MP分页

需求:现在有一个List,需要做成MP的Page分页传给前端,直接使用该工具类。

package com.hisi.pex.common.util;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: fn
 * @Date: 2023/5/22 10:07
 * @Description:
 **/
public class PageListUtils {
    /**
     * 分页函数
     *
     * @param currentPage 当前页数
     * @param pageSize    每一页的数据条数
     * @param list        要进行分页的数据列表
     * @return 当前页要展示的数据
     */
    public static Page getPages(Integer currentPage, Integer pageSize, List list) {
        Page page = new Page();
        int size = list.size();

        if (pageSize > size) {
            pageSize = size;
        }

        // 求出最大页数,防止currentPage越界
        int maxPage = size % pageSize == 0 ? size / pageSize : size / pageSize + 1;

        if (currentPage > maxPage) {
            currentPage = maxPage;
        }

        // 当前页第一条数据的下标
        int curIdx = currentPage > 1 ? (currentPage - 1) * pageSize : 0;

        List pageList = new ArrayList();

        // 将当前页的数据放进pageList
        for (int i = 0; i < pageSize && curIdx + i < size; i++) {
            pageList.add(list.get(curIdx + i));
        }

        page.setCurrent(currentPage).setSize(pageSize).setTotal(list.size()).setRecords(pageList);
        return page;
    }
}

List的.add()添加元素被覆盖问题

  1. 问题重现,在循环里面添加对象到外部List中,List中的所有元素会被最后一个对象覆盖:
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            integerList.add(i);
        }
        //TODO List的.add()深浅拷贝问题
        List<Student> studentList = new ArrayList<>();
        Student student = new Student(3, "张三");
        for (int i = 0; i < integerList.size(); i++) {
        	//源码里是MP的查询对象出问题,循环多次查询并不会new多个wordOne,而是共用一个:HisiWordOne wordOne = this.getById(hisiWordTwo.getOneId());
            student.setId(i);
            student.setName(Integer.toString(i));
            studentList.add(student);
        }
        System.out.println(studentList);
    }

上面程序的结果

[Student{id = 3, name = 3}, Student{id = 3, name = 3}, Student{id = 3, name = 3}, Student{id = 3, name = 3}]
  1. 出现的原因:循环里的student没有新new也就没有新开辟内存地址,ArrayList集合里存的是一个对象的引用。当我们改变student时,因为ArrayList.add的是student的引用(虽然List里面有多个student),之前的元素都指向了同一个对象student,所以在改变student时,之前添加的也会随之改变。

  2. 解决办法:在循环里面新new一个Student,然后BeanUtils.copeproperties()拷贝两个对象的信息,最后.add(新new的student)。
    参考链接:java中list.add添加元素覆盖之前的问题

自定义全局异常处理器

参考文章01
如何包装自定义异常

思路:

  1. 自定义一个异常类,继承Runtime Exception(此处思考应该继承Exception还是Runtime Exception?)。
  2. 自定义一个全局异常处理器类,类用@RestControllerAdvice注解修饰,拦截异常并统一处理。
  3. 在全局异常处理器中定义方法,使用ExceptionHandler(异常类.class)修饰,表明该方法用于拦截并处理哪一类具体的异常。
  4. 比如现在的业务中经常出现某一种该业务才有的异常,那就自定义一个异常,然后在全局异常处理器中写一个方法拦截该异常并进行处理,那么在写业务的时候如果有这个异常直接throw new 抛出,全局异常处理器就能统一处理,简化了代码。

下面给出一个简单示例:
自定义异常类

/**
 * 自定义业务级异常
 */
public class BusinessException extends RuntimeException {
    private Integer code;

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

全局异常处理器

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

    /**
     * 处理业务级异常
     *
     * @param be
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    public Result<String> doBusinessException(BusinessException be) {
        log.info("捕捉到异常:{}", be.getMessage());
        log.info("=============================");
        if (be.getMessage().contains("Duplicate entry"))
            return Result.error("该字段已存在,请重新命名");
        return Result.error(be.getMessage());
    }

    /**
     * 处理系统级异常
     *
     * @param se
     * @return
     */
    @ExceptionHandler(SystemException.class)
    public Result<String> doSystemException(SystemException se) {
        return null;
    }

    /**
     * 处理其他异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result<String> doException(Exception e) {
        return null;
    }
}

其他

IntelliJ IDE 使用 Codeium 注意项

链接

工具类

Hutool

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
  1. 将Json转为Bean:JSONUtil.toBean(shopJson, Shop.class);
  2. 将Bean转为Json:JSONUtil.toJsonStr(shop);
  3. 将Bean转为Map,Bean的字段名为key,值为value
        //6.2 将User对象转为Hash存储(使用hutool的拷贝工具)
        //此处需要将UserDTO的Long id转为String
        //CopyOptions.create()表示设置需要忽略的某些字段,setIgnoreNullValue()空字段是否忽略,setFieldValueEditor()处理字段
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create()
                .setIgnoreNullValue(true)
                .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
  1. Boolean自动拆箱为bollean时,可能引发空指针问题,这时就需要Boolean.TRUE.equals(success);
    或者Hutool工具BooleanUtil.isTrue(success);
  2. 保存用户到Redis,这里使用hutool的UUID工具String token = UUID.randomUUID().toString(true);

MD5加密

参考文章

安装配置

Nacos鉴权

安装nacos后没有登录界面,开启鉴权方法:docker安装新版nacos鉴权(登陆密码)问题解决

使用Nginx解决跨域

找到Nginx的配置文件。

  • 方式一,在http块中配置允许跨域。
add_header Access-Control-Allow-Origin '*';		# 指定发起请求的哪些域名允许跨域,*表示过来的所有域名都允许跨域
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
  • 方式二,在server块的location块中配置允许跨域。
location /hisi-pex/ {
       proxy_pass http://web-pex.brtvcloud.com:9200/;

       add_header Access-Control-Allow-Origin '*';
       add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
       add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
       if ($request_method = 'OPTIONS') {
            return 204;
       }
}

二者的区别,方式一是允许所有过来的请求允许跨域;方式二是允许过来的指定请求路径允许跨域。
配置含义解释

Stream流

根据list的某个字段去重

public List<WmsMemoryExcelVo> repetitionRepetitions(List<WmsMemoryExcelVo> excelListl) {

        //对list数据 根据某个字段去掉重复数据 这里用的根据vin去除重复值,两个结果相同值去最后一条
        List<WmsMemoryExcelVo> newList = excelListl.stream().collect(Collectors.collectingAndThen
                (Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing
                        (WmsMemoryExcelVo::getVin))), ArrayList::new));
        return newList;
    }

根据List某个字段排序

        noteVoList = noteVoList.stream().filter(NoteVo -> NoteVo != null && NoteVo.getUpdateTime() != null)
                .sorted(Comparator.comparing(NoteVo::getUpdateTime).reversed()).collect(Collectors.toList());

多字段排序

根据list中的某个字段分组

场景:通过UserIds查出一个地址List,现在需要根据userId将List分组为多个List,方便后续set等。

Map<Long, List<AddressVO>> addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));

// map的key就是分组依据userId,后续根据key获取对应的List

统计List中数据的出现次数

场景1:现在有一个List<Long> 需要统计这个List里数字出现的次数,放在Map里,Map的key是数字,value是出现次数。

List<Long> primaryIds = Arrays.asList(1L, 4L, 3L, 2L, 1L, 4L);
Map<Long, Integer> countMap = new HashMap<>();

for (Long id : primaryIds) {
    countMap.put(id, countMap.getOrDefault(id, 0) + 1);
}

System.out.println(countMap);
  • .getOrDefault(Object key, V defaultValue)方法用于获取指定键对应的值,如果找不到该键,则返回设置的默认值。
  • 如果Object key存在,则返回这个key对应的value;如果不存在,则返回默认值V defaultValue

使用map计数

姿势1:

countMap.merge(radioactiveName, leadBox.getRemaining(), Integer::sum);

说明:

  • 指定的键已存在于 HashMap 中,merge() 方法将根据提供的合并函数对键的当前值和指定的值进行合并。
  • 键不存在,则直接将指定的键和值添加到 HashMap 中。

姿势2:

Integer remain = countMap.get(radioactiveName);
if (remain == null) {
    countMap.put(radioactiveName, leadBox.getRemaining());
} else {
    countMap.put(radioactiveName, remain + leadBox.getRemaining());
}

参考链接

Centos命令

端口&防火墙

  • 查看端口占用情况
    lsof -i :88
  • 查看端口占用情况,及对应的PID;参考链接
    netstat -tunlp | grep 8080
  • 查看firewalld规则
    firewall-cmd --list-all
  • 查看简版firewalld开放的端口
    firewall-cmd --zone=public --list-ports
  • 开放指定端口(如88)
    firewall-cmd --zone=public --add-port=88/tcp --permanent
    –permanent 为永久生效,不加为单次有效(重启失效)
  • 修改防火墙规则后需重载
    firewall-cmd --reload

后台运行

  • 安装screen
    yum install -y screen
  • 创建一个新的窗口
    screen -S test
  • 退出当前窗口
    ctrl+a+d
    screen -d
  • 重新连接窗口
    screen -r id或窗口名称
  • 停止该窗口
    Ctrl + C
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值