工作中遇到的问题总结

工作问题总结

总结了工作中遇到的一些问题及bug。

Mysql group by 不区分大小写的问题

在Mysql中,如果不特意指定字段,字段数据在group by时是不区分大小写的。

如下图:

对于user表,我们有4个用户,通过user_name group by之后我们只得到了两条数据。

upload successful

可以看到,如果不特殊处理的话,Mysql在group by时是不区分大小写的。

解决方案有两个,都是通过BINARY关键字解决:

BINARY 运算符将紧随其后的string 转换为 二进制字符串。

主要用来强制进行按字节进行比较的数据。这使得字符串比较是区分大小写的, 不管原始的列定义是否是 BINARY 或者 BLOBBINARY 也对字符串末尾的空格敏感。

  1. 对于查询,我们使用BINARY 关键字。如下图效果:

    1
    select user_name,COUNT(1) from `user` GROUP BY BINARY user_name;

    upload successful

    可以看到得到了我们想要的结果。

  2. 当然也可以给表结构字段添加BINARY来解决,这会改变表的字符集及排序规则。

    1
    2
    ALTER TABLE `user`
    MODIFY COLUMN `user_name` varchar(100) BINARY DEFAULT NULL COMMENT '用户名';

    得到的表结构如下,我们再进行group by查询,可以看到指定字段已经区分了大小写。

    upload successful

    当然对于大小写敏感的字段数据,我们可以在建表的时候直接指定字段的字符集及排序规则。

List去重问题

  1. 当然我们可以使用Set进行去重。

  2. 对于Java 8之后,我们可以使用流进行去重,如下语法:

1
list.stream().distinct().collect(Collectors.toList());

对于对象列表的话,我们应重写对象的hashCode()equals()方法,再使用流的distinct()方法来进行去重。

distinct()方法不提供按照属性对对象列表进行去重的直接实现。它是基于hashCode()equals()工作的。

我们如果想按照属性对对象列表进行去重,除了使用普通循环处理外,还可以构建一个我们自己的Predicate实例,通过流过滤来实现,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Data
class Phone{
private String phone;
private LocalDateTime createDate;
public Phone(String phone, LocalDateTime createDate) {
this.phone = phone;
this.createDate = createDate;
}
}

public class Test {
public static void main(String[] args) {
List<Phone> list = new ArrayList<>();
list.add(new Phone("11111", LocalDateTime.of(2020,7,6,11,00,00)));
list.add(new Phone("22222", LocalDateTime.of(2020,7,7,11,00,00)));
list.add(new Phone("44444", LocalDateTime.of(2020,7,8,11,00,00)));
list.add(new Phone("11111", LocalDateTime.of(2020,7,10,11,00,00)));
list.add(new Phone("22222", LocalDateTime.of(2020,7,2,11,00,00)));

list.stream().filter(distinctByKey(Phone::getCreateDate)).collect(Collectors.toList());
}

static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}

其关键方法就是distinctByKey方法,这个方法原理很简单,我们将对象属性放入HashMap中,通过它的putIfAbsent来判断是否放入成功,不成功返回false,filter自动过滤此对象。

上面的例子中,我们如果在对phone号码进行去重的时候,只想要时间比较新的怎么办呢?

很简单,我们只需在处理是先对流按照时间进行排序即可,如下:

1
2
list.stream().sorted(Comparator.comparing(Phone::getCreateDate).reversed())
.filter(distinctByKey(Phone::getCreateDate)).collect(Collectors.toList());

Mysql字符集问题

我在插入Mysql数据库表情等特殊符号时遇到了如下错误:

1
......Cause: java.sql.SQLException: Incorrect string value: '\xF0\x9D\x91\x80\xF0\x9D...' for column 'xxxxx' at row 1......

如果我们的目标表字符集设置的是CHARSET=utf8mb4但仍是有这种问题,可能是Mysql数据库连接的字符集不正确。

我们可以通过SHOW VARIABLES LIKE 'character_set_%';来查看当前Mysql数据库连接字符集。如下:

upload successful

其中:

  • character_set_client:指客户端字符集属性
  • character_set_connection:指连接字符集属性
  • character_set_database:指数据库字符集属性
  • character_set_filesystem:指文件系统字符集属性
  • character_set_results:指返回结果字符集属性
  • character_set_server:指服务端字符集属性
  • character_set_system:指系统字符集属性
  • character_sets_dir:指字符集路径

影响我们保存和读取的主要为character_set_clientcharacter_set_connectioncharacter_set_results这三个属性。

比如下面图示,我们保存数据失败了。

upload successful

这时候我们设置一下这三个属性为utf8mb4即可。如下:

1
2
3
SET character_set_client = utf8mb4;
SET character_set_results = utf8mb4;
SET character_set_connection = utf8mb4;

upload successful

PS:这时候该表的CHARSET需要为utf8mb4

这三个设置也可以用一条设置代替。

1
SET NAMES 'utf8mb4';

需要注意的是这种设置只在本次连接中有效,当连接中断时,需要重新设置。

我们在开发时,程序调用,可以通过jdbcUrl进行设置。如下:

1
jdbc.url=jdbc:mysql://xx:xx:xxx:xxx/test?useUnicode=true&amp;characterEncoding=utf8mb4

出现上面异常一般的解决步骤:

  1. 检查数据表字符集是不是utf8mb4,不是的话需要通过如下语句进行修改。

    1
    ALTER TABLE tbl_name [[DEFAULT] CHARACTER SET charset_name] [COLLATE collation_name]
  2. 检查character_set_clientcharacter_set_connectioncharacter_set_results这三个属性的设置,一般情况下我们在程序里配置characterEncoding=utf8mb4即可。

Java 过滤 utf8mb4 字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 过滤utf8mb4字符
* @param text
* @return
*/
public static String filterOffUtf8Mb4(String text){

byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
int i = 0;
while (i < bytes.length) {
short b = bytes[i];
if (b > 0) {
buffer.put(bytes[i++]);
continue;
}
// 去掉符号位
b += 256;

if (((b >> 5) ^ 0x6) == 0) {
buffer.put(bytes, i, 2);
i += 2;
} else if (((b >> 4) ^ 0xE) == 0) {
buffer.put(bytes, i, 3);
i += 3;
} else if (((b >> 3) ^ 0x1E) == 0) {
i += 4;
} else if (((b >> 2) ^ 0x3E) == 0) {
i += 5;
} else if (((b >> 1) ^ 0x7E) == 0) {
i += 6;
} else {
buffer.put(bytes[i++]);
}
}
buffer.flip();
return new String(buffer.array(), StandardCharsets.UTF_8);
}

该方法可以过滤掉utf8mb4字符(4字节的UTF-8字符)。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道