Luo Hao

后端——问题小书(2023)

rehoni / 2023-12-24


1、MyBatis注解-动态SQL解决方案

在一个表中有id,name,email我们查询的时候,希望name不为null的时候,作为查询条件,如果email不为null的时候,作为查询条件。

方案1:<script>

@Select("<script> " +  
            "SELECT * " +  
            "from Demo " +  
            " <where> " +  
            "  1=1" +  
            " <if test=\"name != null\">and name=#{name}</if> " +  
            " <if test=\"email != null\"> and email=#{email}</if> " +  
            " </where> " +  
            " </script> ")  
public List<Demo> select4(Demo demo);  

方案二:@Provider

对于创建动态的查的语言。MyBatis提供了多个注解如:@InsertProvider,@UpdateProvider,@DeleteProvider和@SelectProvider,这些都是建立动态语言和让MyBatis执行这些语言。

2、SpringBoot 实现 Websocket 通信详解

SpringBoot 实现 Websocket 通信详解

3、Spring的BeanUtils.copyProperties(obj1, obj2),复制对象时字段类型不一致,导致赋值不上

当用spring的BeanUtils.copyProperties(obj1, obj2);对象进行复制时,因为在obj1 类型的对象中定义的属性类型不同(set方法中用的是Integer类型,get方法中返回的是int类型):

public int getNumber() {
    return number;
  }

public void setNumber(Integer number) {
    this.number = number;
}

这里是因为:spring在利用反射复制对象时,是以javaBean的规范来使用get/set方法进行设置值的。同时set方法的参与类型,在classLoader中,也会被计为函数名的一部分。 所有这里必须都是int类型或者都是integer类型。否则spring就不会对该属性进行复制相应的值。

public int getNumber() {
    return number;
  }

public void setNumber(int number) {
    this.number = number;
}

第二个知识点: 应用场景:当我修改一个工程里的jar包内的一个类时(其实改动,就是把上一个知识点里的Integer类型修改成了int类型),然后到去替换服务器上对应的jar包后,重启工程会出现:NoSuchMethodError这样的错。 原因是:基本数据类型与包装类型的解包封包是由编译器提供的(这在jdk 1.5引入)。void setNumber(int a)与 void setNumber(Integer a); 对于编译器来说是不同的方法,他们的有着不同的方法签名。 这里就涉及到Java编辑器对Java方法编译的问题:在Java中,编辑器会把函数的参数列表与函数名称一起作为内存中标记函数唯一性的标识,这也是Java代码可以重载的原因。

3、map struct 高级用法

1、doTrim函数,toJsonString函数、自定义java函数(工具类见附录)

import com.dm.bs.model.vo.BsFieldTransResult;
import com.dm.bs.model.vo.BsFieldTransVO;
import com.dm.bs.repository.entity.BsFieldTrans;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
 * BsFieldTrans转换器
 */
@Mapper(uses = {TypeConversionWorker.class})
public interface BsFieldTransMapping {
    BsFieldTransMapping INSTANCE = Mappers.getMapper(BsFieldTransMapping.class);

    @Mapping(target = "targetUrl",source = "targetUrl",qualifiedByName = "doTrim")
    @Mapping(target = "transKey",source = "transKey",qualifiedByName = "toJsonString")
    @Mapping(target = "transVal",source = "transVal",qualifiedByName = "toJsonString")
    @Mapping(target = "relatedField", expression = "java(typeConversionWorker.generateRelatedField(bsFieldTransVO.getTransKey(), bsFieldTransVO.getTransVal()))") //使用工具类处理
    BsFieldTrans transToBsFieldTrans(BsFieldTransVO bsFieldTransVO);

    @Mapping(target = "relatedField",source = "relatedField",qualifiedByName = "listStr2CommaStr")
    BsFieldTransResult transToBsFieldTransResult(BsFieldTrans bsFieldTrans);
}

附录:

1、转换工具类

import com.dm.bs.model.vo.TransKeyVO;
import com.dm.bs.model.vo.TransValVO;
import com.dm.bs.util.JacksonUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Mapping通用转换
 */
@Component
@Named("TypeConversionWorker")
public class TypeConversionWorker {
    /**
     * 对象转json字符串
     *
     * @param obj
     * @return
     */
    @Named("toJsonString")
    public String toJsonString(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return JacksonUtil.toJsonString(obj);
    }

    /**
     * 去空格
     *
     * @param str
     * @return
     */
    @Named("doTrim")
    public String doTrim(String str) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        return str.trim();
    }

    /**
     * 字符串转List对象
     *
     * @param str
     * @return
     */
    @Named("toStrList")
    public List<String> toStrList(String str) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        return JacksonUtil.jsonToObjByTypeRf(str, new TypeReference<List<String>>() {
        });
    }

    /**
     * json字符串转换为Map
     *
     * @param obj
     * @return
     */
    @Named("toStrObjMap")
    public Object toStrObjMap(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return JacksonUtil.jsonToObjByTypeRf(obj.toString(), new TypeReference<Map<String, Object>>() {
        });
    }

    /**
     * jsonLsit转换为逗号隔开形式
     *
     * @param obj
     * @return
     */
    @Named("listStr2CommaStr")
    public String listStr2CommaStr(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        List<String> strings = JacksonUtil.jsonToObjByTypeRf(obj.toString(), new TypeReference<List<String>>() {
        });
        if (strings != null) {
            return String.join(",", strings);
        }
        return null;
    }

    /**
     * BsFieldTransMapping生成relatedField内容
     */
    @Named("generateRelatedField")
    public String generateRelatedField(List<TransKeyVO> transKey, List<TransValVO> transVal) {
        List<String> relatedFieldList = new ArrayList<>();
        if (transKey != null && transKey.size() > 0) {
            transKey.forEach(k -> relatedFieldList.add(k.getCurrentKey()));
        }
        if (transVal != null && transVal.size() > 0) {
            transVal.forEach(v -> relatedFieldList.add(v.getTargetKey()));
        }
        return JacksonUtil.toJsonString(relatedFieldList);
    }

    /**
     * BsFieldTransMapping生成relatedField内容
     */
    @Named("getParentScope")
    public String getParentScope(String targetScope) {
        String[] split = targetScope.split("\\.");
        if (split.length == 1) {
            return "";
        }
        StringBuilder parentScope = new StringBuilder();
        for (int i = 0; i < split.length - 1; i++) {
            parentScope.append(split[i]);
            if (i < split.length - 2) {
                parentScope.append(".");
            }
        }
        return parentScope.toString();
    }
}

2、工具类

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JacksonUtil {
    private static ObjectMapper mapper = new ObjectMapper();

    /**
     * 对象转json
     *
     * @param object
     * @return
     */
    public static String toJsonString(Object object) {
        try {
            return mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            log.error("...Err...Jackson转换字符串(String)过程失败:::", e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * json转对象
     *
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T jsonToBean(String json, Class<T> clazz) {
        try {
            return mapper.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            log.error("...Err...Jackson转换对象(Object)过程失败:::", e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 字符串转换为List
     *
     * @param listStr
     * @param typeReference new TypeReference<List<Object>>() {}
     * @param <T>
     * @return
     */
    public static <T> T jsonToObjByTypeRf(String listStr, TypeReference<T> typeReference) {
        try {
            return mapper.readValue(listStr, typeReference);
        } catch (JsonProcessingException e) {
            log.error("...Err...Jackson转换Object过程失败:::", e);
            e.printStackTrace();
        }
        return null;
    }
}

4、Java 两个List按照其中一个List元素顺序对另一个List元素进行排序

根据orderList顺序排序,orderList不存在的元素放置在targetList最后面

// 根据orderList顺序排序,orderList不存在的元素放置在targetList最后面
private static void sort1(List<String> targetList, List<String> orderList) {
    targetList.sort(((o1, o2) -> {
        int io1 = orderList.indexOf(o1);
        int io2 = orderList.indexOf(o2);
        if (io1 != -1) {
            io1 = targetList.size() - io1;
        }
        if (io2 != -1) {
            io2 = targetList.size() - io2;
        }
        return io2 - io1;
    }));
}


//如果targetList为list<实体>,则需要 get出 key 相同字段进行比较
private static void sort1(List<实体> targetList, List<String> orderList) {
    targetList.sort(((o1, o2) -> {
        int io1 = orderList.indexOf(o1.get出targetList与orderList相同的key);
        int io2 = orderList.indexOf(o2.get出targetList与orderList相同的key;
        if (io1 != -1) {
            io1 = targetList.size() - io1;
        }
        if (io2 != -1) {
            io2 = targetList.size() - io2;
        }
        return io2 - io1;
    }));
}

根据orderList顺序排序,orderList不存在的元素放置在targetList最前面

 // 根据orderList顺序排序,orderList不存在的元素放置在targetList最前面
  private static void sort2(List<String> targetList, List<String> orderList) {
    targetList.sort(((o1, o2) -> {
        int io1 = orderList.indexOf(o1);
        int io2 = orderList.indexOf(o2);
        return io1 - io2;
    }));
}


 //如果targetList为list<实体>,则需要 get出 key 相同字段进行比较
  private static void sort2( List<实体> targetList, List<String> orderList) {
    targetList.sort(((o1, o2) -> {
        int io1 = orderList.indexOf(o1.get出targetList与orderList相同的key);
        int io2 = orderList.indexOf(o2.get出targetList与orderList相同的key);
        return io1 - io2;
    }));
}

5、Java线程池中创建 ThreadFactory 设置线程名称

1. CustomizableThreadFactory

Spring 框架提供的 CustomizableThreadFactory

ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");


ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
exec.submit(() -> {
    logger.info("--记忆中的颜色是什么颜色---");
});

2. ThreadFactoryBuilder

Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。

ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("retryClient-pool-").build();


ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10),guavaThreadFactory );
exec.submit(() -> {
    logger.info("--记忆中的颜色是什么颜色---");
});

3. BasicThreadFactory

Apache commons-lang3 提供的 BasicThreadFactory.

ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
        .namingPattern("basicThreadFactory-").build();

ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10),basicThreadFactory );
exec.submit(() -> {
    logger.info("--记忆中的颜色是什么颜色---");
});

4. 三种方式总结

最终本质都是 给 java.lang.Thread#name 设置名称,详情源码感兴趣的可以自行查看。

final Thread thread = new Thread();
thread.setName(name);

6、创建超链接 in Apache POI Word

There is XWPFHyperlinkRun but not a method for creating a such until now (March 2018, apache poi version 3.17). So we will need using underlaying low level methods.

The following example provides a method for creating a XWPFHyperlinkRun in a XWPFParagraph. After that the XWPFHyperlinkRun can be handled as a XWPFRun for further formatting since it extents this class.

import java.io.*;

import org.apache.poi.xwpf.usermodel.*;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink;

public class CreateWordXWPFHyperlinkRun {

 static XWPFHyperlinkRun createHyperlinkRun(XWPFParagraph paragraph, String uri) {
  String rId = paragraph.getDocument().getPackagePart().addExternalRelationship(
    uri, 
    XWPFRelation.HYPERLINK.getRelation()
   ).getId();

  CTHyperlink cthyperLink=paragraph.getCTP().addNewHyperlink();
  cthyperLink.setId(rId);
  cthyperLink.addNewR();

  return new XWPFHyperlinkRun(
    cthyperLink,
    cthyperLink.getRArray(0),
    paragraph
   );
 }

 public static void main(String[] args) throws Exception {

  XWPFDocument document = new XWPFDocument();

  XWPFParagraph paragraph = document.createParagraph();
  XWPFRun run = paragraph.createRun();
  run.setText("This is a text paragraph having ");

  XWPFHyperlinkRun hyperlinkrun = createHyperlinkRun(paragraph, "https://www.google.de");
  hyperlinkrun.setText("a link to Google");
  hyperlinkrun.setColor("0000FF");
  hyperlinkrun.setUnderline(UnderlinePatterns.SINGLE);

  run = paragraph.createRun();
  run.setText(" in it.");

  paragraph = document.createParagraph();

  paragraph = document.createParagraph();
  run = paragraph.createRun();
  run.setText("This is a text paragraph having ");

  hyperlinkrun = createHyperlinkRun(paragraph, "./test.pdf"); //path in URI is relative to the Word document file
  hyperlinkrun.setText("a link to a file");
  hyperlinkrun.setColor("0000FF");
  hyperlinkrun.setUnderline(UnderlinePatterns.SINGLE);
  hyperlinkrun.setBold(true);
  hyperlinkrun.setFontSize(20);

  run = paragraph.createRun();
  run.setText(" in it.");
  
  FileOutputStream out = new FileOutputStream("CreateWordXWPFHyperlinkRun.docx");
  document.write(out);
  out.close();
  document.close();

 }
}

7、Spring框架中@AfterReturning的returning返回值未成功修改String类型

现象:

String类型的参数值在方法中改变,返回值没有改变。

自定义对象类型的参数值在方法中改变,返回值改变。

在这里插入图片描述

在这里插入图片描述

结论:

@AfterReturning注解的方法,在执行结束后,会将目标类的返回值写入returning,最后其做为目标返回值返回给proxy对象的方法调用返回值,而如何识别目标类的返回值,是用其内存地址进行判断的而不是传入的形参,String的str的内存地址发生了改变,但是没有改变实际参数的内存地址,所以最后写入returning的参数依旧是实参。而Student的内存地址全程没有改变,所以方法执行结束后,实际参数被改变了,但因为内存地址没有改变,所以被returning写入,最后就像我们看到的输出结果一样,String的test方法执行后没有发生改变,而Student的test方法执行后参数被改变了。

总结:

因此要考虑不改变内存地址修改字符串的内容

        String str01 = "aaa";
        String str02 = "aaa";
        String str03 = "bbbb";
        System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());
        Field field = str02.getClass().getDeclaredField("value");
        field.setAccessible(true);
        field.set(str02, new char[]{'b', 'b', 'b', 'b'});
        System.out.println(str01 + "/" + str02);
 /* jdk7以后已不包含 private final int count;
   field = str02.getClass().getDeclaredField("count");
        field.setAccessible(true);
        field.set(str02, 4);*/
 
        System.out.println(str01 + "/" + str02);
        System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());
        field = str02.getClass().getDeclaredField("hash");
        field.setAccessible(true);
        field.set(str02, 0);
        System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());
        System.out.println(str01 == str02);
        System.out.println(str02 == str03);
  1. 根据Java String的特性,str01和str02指向同一个内存地址,所以str01,str02始终保持一致。
  2. String不能修改的本质是一个final的字符数组(private final char value[];),但是final只在编译时有效,而运行期间是没有作用的,所以可以通过反射修改数组的值。
  3. 代码中修改后字符串的hashcode之所以没有变化,是因为String对象会缓存hashcode,去掉缓存就可以看到变化。
  4. 根据Java String的特性,上面代码创建的所有字符串对象都保存在String Pool中,代码中str02与str03的内容相同,地址却不同,所以String Pool中就存在两个相同的字符串。

8、jsch上传的文件文件名乱码解决方案

当使用jsch设置编码格式时

sftp.setFilenameEncoding(“GBK”);

出现如下错误:The encoding can not be changed for this sftp server。

使用的jar包是:jsch-0.1.54,经查看源码发现,版本是3-5的,都不支持设置文件名编码格式。

public void setFilenameEncoding(String encoding) throws SftpException{
    int sversion=getServerVersion();
    if(3 <= sversion && sversion <= 5 &&
       !encoding.equals(UTF8)){
        throw new SftpException(SSH_FX_FAILURE,
                                "The encoding can not be changed for this sftp server.");
    }
    if(encoding.equals(UTF8)){
        encoding=UTF8;
    }
    fEncoding=encoding;
    fEncoding_is_utf8=fEncoding.equals(UTF8);
}

解决方式:使用反射修改server_version的默认值即可。

JSch jsch = new JSch();

jsch.addIdentity("provateKey");

Session session = jsch.getSession("userName", "serverIp",“port);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
ChannelSftp channelSftp = (ChannelSftp)session.openChannel("sftp");
channelSftp.connect();

Class cl = channelSftp.class;

Field f1 =cl.getDeclaredField("server_version");
f1.setAccessible(true);
f1.set(sftp, 2); 
channelSftp.setFilenameEncoding("gbk");

其中Field是引用java.lang.reflect.Field jar包里面的类。

这样就ok了,如果想知道自己使用的jar版本是多少,可以使用getServerVersion方法来获取。具体方法如下:channelSftp.getServerVersion()

9、easypoi 使用模板导出图片不显示

题主提供的参考文档在“1.3 使用”中介绍使用的easypoi-base版本号为3.2.0,所以该教程也是在那个版本的基础上说明的。我下载了3.2.0的jar包看了一下,的确是没有问题,这在后面会解释。

现在的问题是,题主使用的肯定是更高版本的easypoi,比如我目前使用的是4.4.0,查看了一下源码,我觉得问题的确就在于colspan和rowspan上。

ImageEntity在构造时,默认colspan与rowspan都为1。而在BaseExportService.createImageCell构造ClientAnchor时,也就是找到这个图片的锚点时出现问题

img

这里直接导致XSSFClientAnchor在构造时第一和第二单元格时为同一单元格,也就是相当于将图片画到了一个点上。而在3.2.0版本中这里是没有使用colspan、rowspan变量的

img

说明在版本迭代过程中,作者虽然支持了图片占用多个单元格的方式,但是这里附加的-1会导致ImageEntity默认值被减掉后变成点,要想正常excel导出图片就必须给colspan、rowspan赋值大于2。

我又看了各个版本的jar,发现4.0.0还是与3.2.0一样只支持1个单元格图片,从4.2.0开始支持colspan、rowspan变量,而且是没有-1,也就是说4.2.0是完美支持的。但是不知道为啥,从4.3.0版本开始增加了-1,导致图片缩为点了…具体原因我想肯定是有别的考量在里面吧,我就没有继续深入研究了…

10、Java的Comparator升序降序的记法

记住java判断的规则就能轻松各种情况了,-1不交换,1交换

实现Comparator接口,必须实现下面这个函数:

@Override
public int compare(CommentVo o1, CommentVo o2) {
           return o1.getTime().compareTo(o2.getTime());
}

这里o1表示位于前面的对象,o2表示后面的对象

下面来用我们之前的结论解释为什么return o2.a - o1.a;就是降序了:

首先o2是第二个元素,o1是第一个元素。无非就以下这些情况: ①: o2.a > o1.a : 那么此时返回正数,表示需要调整o1,o2的顺序,也就是需要把o2放到o1前面,这不就是降序了么。

②:o2.a < o1.a : 那么此时返回负数,表示不需要调整,也就是此时o1 比 o2大, 不还是降序么。

这样一分析下来,就很明白了。希望对大家有用。

11、springboot2.0不返回某个字段

1.注解使用在 类名,接口头上

@JsonIgnoreProperties(value={“comid”}) //希望动态过滤掉的属性

2。该注解使用在get方法头上

@JsonIgnore

12、汉字转拼音:pinyin4j

13、把字符串解析为if语句的判断条件

脚本语言支持(Scripting):ScriptEngineManager

需要从数据库中取出varchar类型的数据,解析转化为if语句中的判断条件,

在不断的查阅资料中,找到了下面的方法,使用javax.script.ScriptEngineManager,代码如下:

public static void main(String[] args) throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();  
    ScriptEngine engine = manager.getEngineByName("js");  
    /*
		 * 字符串转算术表达式
		 */
    String str1 = "43*(2 + 1.4)+2*32/(3-2.1)";  
    Object result1 = engine.eval(str1);  
    System.out.println("结果类型:" + result1.getClass().getName() + ",计算结果:" + result1);  
    /*
         * 字符串转条件表达式
         */
    int value = 7;
    String state = "正常";
    boolean flag = true;
    String st = "test";
    String str2 = "value > 5 && st == \"test\" && state == \"正常\" && flag == true";
    engine.put("value", value);
    engine.put("state", state);
    engine.put("flag", flag);
    engine.put("st", st);
    Object result2 = engine.eval(str2);
    System.out.println("结果类型:" + result2.getClass().getName() + ",结果:" + result2);  
}