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
属性有年月日时分秒
。
下面这种会导致时间仅被截取到日,如果startTime
是2023-09-11 11:17:09
,则实际查询日期为2023-09-11
。
<select id="function" resultType="xxx.xxx.xxx.xxxDao">
select * from table
where start_time >= #{startTime,jdbcType=DATE} and start_time < #{endTime,jdbcType=DATE}
</select>
修复:将DATE
修改为TIMESTAMP
<select id="function" resultType="xxx.xxx.xxx.xxxDao">
select * from table
where start_time >= #{startTime,jdbcType=TIMESTAMP} and start_time < #{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下运行,显示及响应都会出现乱码:
-
调整终端编码为
utf8
-
运行
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