java日常.md

Java Spring

Spring 中进行Junit测试时,PageHelper未被消耗

如果mapper被mock掉,会导致Mapper之前的PageHelper的线程存储未被消耗。导致后续出现错误。

// 源码
// .....
void q() {
    PageHelper.startPage(1, 1);
	someMapper.query();
}

// .....    

// 问题单测
@Mock
private SomeMapper someMapper;
@Test
void testQ() {
    q(); // 错误 由于mapper被mock掉,PageHelper又被正常调用,此处会导致PageHelper留存在线程里的数据未被消耗
}

// 正确单测
@Mock
private SomeMapper someMapper;
@Test
void testQ() {
    q(); 
    PageHelper.clearPage(); // 手动消耗掉即可
}

MyBatis时间数据只有日期

​ Mybatis框架中jdbcType="DATE" 和 jdbcType="TIMESTAMP" 两种类型的有区别。DATE属性只有年月日,而TIMESTAMP属性有年月日时分秒

下面这种会导致时间仅被截取到日,如果startTime2023-09-11 11:17:09,则实际查询日期为2023-09-11

<select id="function" resultType="xxx.xxx.xxx.xxxDao">
  select * from table
  where start_time &gt;= #{startTime,jdbcType=DATE} and start_time &lt; #{endTime,jdbcType=DATE}
</select>

修复:将DATE修改为TIMESTAMP

<select id="function" resultType="xxx.xxx.xxx.xxxDao">
  select * from table
  where start_time &gt;= #{startTime,jdbcType=TIMESTAMP} and start_time &lt; #{endTime,jdbcType=TIMESTAMP}
</select>

Spring中读取文件

// 读取文件
@SneakyThrows
private void skipErrorIp(HashSet<String> localIps) {
    // 1. 获取上下文,读取文件Resource
    org.springframework.core.io.Resource resource = SpringContextUtils.getApplicationContext().getResource("file:C:\\3.txt");
    // 2. 使用buffer reader读取
    BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
    // 3. 按行读取
    String line;
    while ((line = br.readLine()) != null) {
        line = line.trim();
        if (!line.isEmpty()) {
            localIps.remove(line);
        }
    }
}

Spring和Junit测试

使用mock时,需要注意junit的版本不能混用。

// 这是Jnit4的例子
@RunWith(MockitoRunner.class)
class OneClassTest {
    
    // 注意这里必须导入junit4的Test
    @Test
    void getOne() {
        
    }
}

// 这是Jnit5的例子
@ExtendWith(MockitoExtension.class)
class OneClassTest {
    
    // 注意这里必须导入junit5的Test
    @Test
    void getOne() {
        
    }
}

Spring事务

​ 在spring cloud使用事务,一般都是直接添加注解: @Transactional(rollbackFor = Exception.class)。这个函数内部调用的所有数据库相关操作都会在一个事务内,这会导致在该函数执行完成之前,其他线程(函数)是看不到这里面的数据变动。如果需要在该函数调用的某个函数内,让其他线程看到数据变动,则对这个内部函数添加 @Transactional(propagation = Propagation.NOT_SUPPORTED)

// 执行主要逻辑操作
// 该函数所有数据库操作在一个事务之内
@Transactional(rollbackFor = Exception.class)
void mainOperate() {
    Utils.updateData();
    Utils2.updateData();
}

// 被mainOperate调用的方法
class Utils {
    static void updateData() {
        // 更新一些数据
        dataMapper.update();
    }
}

// 被mainOperate调用的方法
// 该注解会检查当前是否已有事务,如果有,则事务挂起(暂停),等函数执行完成之后,再将事务恢复
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class Utils2 {
    static void updateData() {
        // 更新一些数据
        dataMapper.update();
    }
}

// 想要观察mainOperate内部的数据更改
void watchDataChange() {
    // 在mainOperation执行完成之前(事务提交之前),是无法观察到Utils对updateData对数据的改动
    // 但是可以观察到Utils2对updateData对数据的改动
    // 观察数据变动
    // ...........
}

FastJson

反序列化含泛型类型的类时,需要使用TypeReference

class Macro<T> {
    T a;
}

// xxxxxxxxxxxxx
Macro<List> macro = JSON.parseObject("{xxxx}", new TypeReference<Macro<List>>(){});
// xxxxxxxxxxxxx

SpringBoot最大返回数据

SpringBoot一次http回应最大可以返回多少数据呢?可以做个测试

@GetMapping("/getLongResponseData")
public String longResponseData() {
    StringBuilder sb = new StringBuilder();
    sb.append("12");
    for (int i = 0; i < 25; i++) {
        sb.append(sb);
    }
    return sb.toString();
}

如上代码,长度为\(2^{25}\)大小,也就是32MB大小。

使用Postman请求,postman长度过大报错。使用curl请求,可以访问。

所以应该判断,springboot对响应数据长度没有大小限制,至少32MB是完全没问题的。

redis存入long类型的取出为Integer

Object obj =  redisTemplate.opsForValue().get(redisTestLongKey);
Long result = null;
if (obj instanceof Integer){
	result = ((Integer)obj).longValue();
}else {
	result = (Long) obj;
}

Enum 类型在jackson下的序列化和反序列化

@Getter
@AllArgsConstructor
public enum SecurityProductTypeEnum {
    APPALE(1, "apple"),
    VIVO(2, "vivo")
    ;
    /**
     * 对应的值
     */
    // @JsonValue 序列化时,使用此getter代替整个类
    @JsonValue
    Integer value;

    /**
     * 描述
     */
    String description;

    /**
     * 反序列化时,使用value值。调用此接口进行转换
     * @param value
     * @return
     * mode.DELEGATING 可以在反序列化时,不再使用“key”
     */
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static SecurityProductTypeEnum fromValue(int value) {
        for (SecurityProductTypeEnum typeEnum : SecurityProductTypeEnum.values()) {
            if (typeEnum.value == value) {
                return typeEnum;
            }
        }
        return null;
    }
}

@Test
@SneakyThrows
void t() {
    ObjectMapper objectMapper = new ObjectMapper();
    String s = objectMapper.writeValueAsString(SecurityProductTypeEnum.APPLE);
	assert(s == "1");

    String param = "1";
    SecurityProductTypeEnum typeEnum = objectMapper.readValue(param, SecurityProductTypeEnum.class);
    assert(typeEnum == SecurityProductTypeEnum.APPLE);

}

RetrofitClient的序列化

RetrofitClient不会进行 @Query @Path @Url 等的json序列化,仅用于请求体、响应体的编码和解码。如果要序列化,使用自定义转换器。

@RetrofitClient(baseUrl = "${ability.platform.gateway-url}", poolName = "diasp", logStrategy = LogStrategy.BODY, converterFactories = SecurityProductionOrderApi.EnumRetrofitConverterFactory.class)
public interface SecurityProductionOrderApi {

    @GET("/text.php")
    Result query(@Query("productType") SecurityProductTypeEnum productTypeEnum);

    /**
     * 由于retrofitClient默认不会进行 @Query @Path @Url 等的json序列化,仅用于请求体、响应体的编码和解码
     * 这里转换器将Enum类型也进行序列化
     */
    class EnumRetrofitConverterFactory extends Converter.Factory {
        @Override
        public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            Converter<?, String> converter = null;
            if (type instanceof Class && ((Class<?>) type).isEnum()) {
                ObjectMapper objectMapper = new ObjectMapper();
                converter = objectMapper::writeValueAsString;
            }
            return converter;
        }
    }
}

jackson和lombok使用,忽略某字段

@Data
public class OneClass {

    /**
     * 年龄
     */
    private Integer age;

    /**
     *类型 固定值 == 2
     */
    // 不再对type该字段实现set方法,这样反序列化时将会忽略该字段。
    // 序列化同理,不实现get方法,即可在序列化时忽略该字段
    @Setter(AccessLevel.NONE)
    private Integer type = Constants.ONE;
}

SpringBoot忽略请求参数中的某字段

@Data
public class OneClass {

    /**
     * 年龄
     */
    private Integer age;

    /**
     *类型 固定值 == 2
     */
    // 此注解会导致json反序列化不进行值的设置
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    private Integer type = Constants.ONE;
}

Mock单测 mock静态变量或静态函数

/**
*  静态类
*/
public StaticClass1 {
    public static boolean getData() {
        // ......
    }
}

public StaticClass2 {
    public static boolean getData2(int a) {
        // ......
    }
}

// 想mock StaticClass1和StaticCalss2这个两个类  支持junit5,不支持junit4
import org.mockito.MockedStatic;

// @Test中
try (MockedStatic<StaticClass1> mock1 = Mockito.mockStatic(StaticClass1.class);
     MockedStatic<StaticClass2> mock2 = Mockito.mockStatic(StaticClass2.class)) {
    mock1.when(StaticClass1::getData)
        .thenReturn(false);
	mock2.when(() -> StaticClass2.getData2(Mockito.anyInt()))
        .thenReturn(false);
	// xxxxxxx  单测代码
}

Mock单测问题

mock函数参数有基础类型时,不应使用Mockito.any(),否则会报Null异常。

// 待测试
class A {
  	int add(Long a, Object b) {
    	// something
	}  
}


// 错误单测
Mockito.mock(aClass.add(Mockito.any(), Mockito.any()))
    .doReturn(1);

// 正确单测
Mockito.mock(aClass.add(Mockito.anyLong(), Mockito.any()))
    .doReturn(1);

mockito在mock集合对象时怎样进入for循环或foreach

java中常用的for循环大致有三种:
1:最基本的for(int i = 0; i < list.size; i++)。
2:java提供的foreach写法for(Integer i : list)。
3:java 8以后比较常见的流式写法 list.forEach(Consumer<? super E> action)
在使用mockito编写单元测试的过程中
1: 如果遇到第一种for循环,可以先mock出List的mock对象,然后去修改list.size()的返回值。具体代码如下:

	 List listMock = Mockito.mock(List.class);
	 Mockito.when(listMock.size()).thenReturn(1);
12

2:如果遇到第二种foreach循环,需要mock出来List和Iterator(因为java提供的foreach写法是基于iterator实现的)。具体代码如下:

	List listMock = Mockito.mock(List.class);
	Iterator iteratorMock = Mockito.mock(Iterator.class);
	Mockito.when(listMock.iterator()).thenReturn(iteratorMock);
	//这里笔者只循环一次,所以第一次hasNext返回true,第二次返回false(thenReturn的参数指定第几次执行iteratorMock.hasNext()返回什么结果,第一个参数对应第一次执行,第二个参数对应第二次执行,依次类推)。
	Mockito.when(iteratorMock.hasNext()).thenReturn(true,false);
	Mockito.when(iteratorMock.next()).thenReturn(1);
123456

3:遇到第三种流式forEach稍微麻烦点,需要用到doAnswer方法。然后回调自己传入的匿名内部类,具体代码如下

	List listMock = Mockito.mock(List.class);
	Mockito.doAnswer(new Answer() {
                    @Override
                    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    	//获取的参数与when()条件后调用的方法参数一一对应(该例中对应的forEach方法的参数,getArgument(0)就是Consumer对象,翻看jdk的源代码ArrayList.forEach可知晓)
                        Object argument = invocationOnMock.getArgument(0);
                        //调用传递过来Consumer的对象的accept方法(为什么调用accept,也是从jdk源代码ArrayList.forEach得知的),accept方法最终会调入到list.forEach(s->{...})的处理中。
                        //下面invoke(argument,1)中的第二个参数1,就是给list.forEach(s->{...})中s变量赋的值,具体传递什么要看自己的情况。
                        Consumer.class.getDeclaredMethod("accept", Object.class).invoke(argument,1);
                        return argument;
                    }
                }).when(mockList).forEach(Mockito.any());
123456789101112

本文用list举例,其他的集合框架思路一样。

java打包jar之后运行出现中文乱码

idea中显示正常,且http响应也正常,但使用jar包在windows下运行,显示及响应都会出现乱码:

  1. 调整终端编码为utf8

  2. 运行jar包指定编码格式utf8

    # powershell 下运行会出错
    java -Dfile.encoding=utf-8 -jar ***.jar
    

Mysql不区分大小写

在 MySQL 中,字符串的查询默认是区分大小写的?待考证。不过,你可以通过几种方法来实现不区分大小写的字符串查询。

使用 COLLATE 子句: 你可以使用 COLLATE 子句来指定一个不区分大小写的字符集排序规则(collation)。例如,使用 utf8_general_ci(其中 ci 表示 case-insensitive,即不区分大小写

SELECT * FROM your_table  
WHERE your_column COLLATE utf8_general_ci = 'your_string';

Collection.sort()不生效

有时对一个已实现Comparable类数组进行排序,使用Collection.sort()不生效,原因:未确定

直接使用stream排序

var result = datas.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

redis

删除某些key

# 修改 *key* 可以进行正则匹配
# 执行至count返回0
local cursor = "0"
local count = 0
repeat
    local result = redis.call("scan", cursor, "match", "*key*", "count", 1000)
    cursor = result[1]
    local keys = result[2]
 
    for i=1,#keys do
        count = count + 1
        redis.call("del", keys[i])
    end
until cursor == "0"
return count
posted @ 2023-08-08 09:15  nsfoxer  阅读(24)  评论(0)    收藏  举报