MAVEN 中 JDK 版本的两个小问题

公司项目目前用的 JDK 版本还是 1.7,前一段时间同事想把本地的开发环境生成 1.8 想玩玩,结果发现两个小问题,特此记录一下: jar deploy 到 nexus,生成 javadoc 的时候报的有个错,一些注释不认识: 错误: 未知标记: date * @date:2019/8/27 18:40 这个很简单,maven plugin 修改一下就好: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.1</version> <configuration> <aggregate>true</aggregate> <additionalJOptions> <additionalJOption>-Xdoclint:none</additionalJOption> </additionalJOptions> </configuration> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> 其实就是添加了: <additionalJOptions> <additionalJOption>-Xdoclint:none</additionalJOption> </additionalJOptions> 不过要注意的是,如果要是再改回 1.7,这个还要去掉,不然同样报错,这个至于原因为什么,也没深究,知道的可以留下评论 javac: 无效的目标发行版: 1.8 同时安装好了 1.8,java -version 也显示是 1.8,但是就是用 maven 编译的时候报这玩意,在网上看了一堆人说了一个方法: <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> 还有什么修改 path 路径的等等,其实都并没有什么卵用,因为 javac 已经是 1.8 编译了,又不是还是 1.7,所以这些人连问题出在哪都不知道,真是天下文章一大抄,你抄我来我抄他,但是你们也测试一些行不行啊,行了咱们再抄行不?其实这个问题也简单,在 mvn 文件中有这么一个注释: ...

December 15, 2019 · 1 min · 87 words · Bridge Li

MySQL 中 NOT IN 的坑 — 列为 null 的问题

前一段时间在公司做一个小功能的时候,统计一下某种情况下有多少条数据,然后修改的问题,当时感觉很简单,写了一个如下的 SQL: SELECT COUNT(*) FROM t1 where tl.c1 not IN (SELECT t2.c1 FROM t2); 预期的结果是:有多少条数据在 t1 中,同时不在 t2 中,结果为:0,也就是 t1 中数据都在 t2 中,但是很容易就发现某些数据在 t1 中不在 t2 中,所以就感觉很奇怪,这个 SQL 看着也没问题啊。经过一番查询原来是因为 t2 的 c1 字段包含了 null 值,修改如下两种形式都可以得到预期的结果: SELECT COUNT(*) FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE t2.c1 IS NULL OR t2.c1 = &#8221;; 或者 select COUNT(*) from t1 where t1.c1 not in ( select t2.c1 from t2 where t2.c1 is not null AND t2.c1 != &#8221; ); 所以都是 null 引起的(为了避免错误我把空串也加上了),原因是 not in 的实现原理是,对每一个 t1.c1 和每一个 t2.c1 (括号内的查询结果)进行不相等比较(!=)。 ...

November 24, 2019 · 1 min · 114 words · Bridge Li

MySQL 系统参数 sql_safe_updates 小结

前一段时间,公司某个项目组某个项目因为失误,出现了一个严重 bug,动态 SQL 导致没有 where 条件,就把数据库某张表里面的数据全部更新了,虽然事后 DBA 同学很给力的恢复了,但是运维的同学讨论,让所有的项目都不许写动态 SQL,必须根据 ID 更新,并写了一个 sonar 插件,扫描代码,发现有动态 SQL 就报 bug,不过还好暂时没有强制要求改,个人认为如果强制要求,这不就是典型的因噎废食吗?没有动态 SQL,这代码量得增加多少?我们研发要不要按照代码量算钱?前一段时间,看《即刻时间》丁奇(原名林晓斌)的 《MySQL 实战 45 讲》里面有一句话关于 sql_safe_updates 的知识点一笔带过,小小研究一下,刚好可以解决这个问题,这是一个小结。 作用:防止忘记添加 WHERE 条件,导致数据被误更新或误删的情况和另外为了提高 SQL 性能,避免更新或删除的时候 WHERE 条件不走索引的情况;不过默认值是:关闭(值为0),所以可能更多的防止忘记添加 WHERE 条件吧,另外这个参数分为会话级别和全局级别。 查看 sql_safe_updates 的值和修改 show variables like &#8216;sql_safe_updates&#8217;; &#8212; 会话 show global variables like &#8216;sql_safe_updates&#8217;; &#8212; 全局 set sql_safe_updates=1; &#8212; 会话 set global sql_safe_updates=1; &#8212; 全局 具体的测试比较简单,就不一张张的贴图了,直接写结论了,大家可以自己测试一下就好了 操作 Delete Update NO WHERE No No NO WHERE + LIMIT No Yes WHERE KEY Yes Yes WHERE KEY + LIMIT Yes Yes WHERE NOKEY No No WHERE NOKEY+ LIMIT Yes Yes WHERE CONSTANT No No WHERE CONSTANT + LIMIT No Yes 其中 KEY 不仅是主键,同样也包括索引字段,CONSTANT 表示 where 1= 1(有些程序员的知识没有更新喜欢这么写,其实可以不用的),Yes 是可以,No 是不可以。 ...

October 27, 2019 · 1 min · 110 words · Bridge Li

Apache Commons Codec — 加密与编码

明天就是十一假期了,公司也没多大事,刷知乎,看到有人吐槽曾经的一个合作伙伴连 md5 都写不对,告诉对方写错了,对方顾头不顾腚的修,还是没修对,然后测了一下自己写的想亏写对了,不然又遗留 bug 了,不过看下面评论,有人提到 Apache Commons Codec 里面都已经写好了,看了一下确实,反正也无聊,也写不了大文章,写着玩玩。 先看自己手写的 md5 // 今后大家不要这么写了,太傻了 package cn.bridgeli.demo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * Created by bridgeli on 2019/7/12. */ public class EncryptUtils { private static Logger logger = LoggerFactory.getLogger(EncryptUtils.class); private EncryptUtils() { } public static String getMD5(String content) { if (null == content) { return ""; } MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("md5"); } catch (NoSuchAlgorithmException e) { logger.error("md5 error", e); } if (null == messageDigest) { return ""; } messageDigest.update(content.getBytes()); byte[] bytes = messageDigest.digest(); StringBuilder stringBuilder = new StringBuilder(); for (byte b : bytes) { String str = Integer.toHexString(b & 0xFF); if (str.length() == 1) { stringBuilder.append("0"); } stringBuilder.append(str); } String result = stringBuilder.toString(); return result; } } 借助别人写好的工具类实现 先引入依赖 ...

September 30, 2019 · 2 min · 272 words · Bridge Li

关于 alibaba fastjson 的两个小知识点

json 转 JavaBean 大小写不敏感 在工作中,我个人经常使用的 json 的工具类是 Google 的 gson,前几天做一个需求的也自然而然的使用这个,但是在和其他部门联调的时候,发现他的属性全是小写,而不是刚开始约定的小驼峰,所以导致他传过来的字符串,我这边转不成 Java bean,在和他讨论的时候,他说他一直就这么写,而且别人使用的时候是没有问题的,然后就找到其他的使用的同学,他竟然说他没在意过这个问题,线上测试了一下,确实没有问题,然后就看一下怎么实现的,然后突然发现,他使用的是 alibaba 的 fastjson 转 Java bean,然后测试了一下,发现 alibaba fastjson 转 Java bean 竟然是大小写不敏感的,特此记录一下,测试代码入校: <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.37</version> </dependency> package cn.bridgeli.demo; import com.alibaba.fastjson.JSON; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class FastJsonTest { @Test public void testFastJsonParseObject() { String jsonStr = "{\"USERNAME\":\"BridgeLi\"}"; User user = JSON.parseObject(jsonStr, User.class); System.out.println(user.getUsername()); } } class User implements Serializable { private static final long serialVersionUID = 4202834388700617773L; private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } 我们看到测试的例子中 Java bean 的属性是小写的,json 字符串中的属性是全大写的,但转换一点问题都没有。 ...

August 25, 2019 · 1 min · 184 words · Bridge Li

关于 error message 一点个人看法

做技术转眼很多年了,也参与过很多系统的开发了,见过各种同事写的 error message,在我看来有些是非常典型的程序员思维,自己感觉一点问题都没,孰不知用户一头雾水,每次提出修改的时候,有些人还振振有词改不了,我提出自己修改意见之后,还有人说不行,前几天和我们半路出家的产品经理聊这个问题,今天闲来无事写一篇文章简单总结一下自己的想法,供大家讨论。 首先开宗明义:个人认为 error messge,不仅是提示用户为什么报错,他遇到了什么问题,而且是告诉用户如何解决他遇到的这个问题,所以个人最好的 error message 应该具备以下要素: 准确 这个不是废话吗?如果不准确,甚至是误导,这 error message 不仅不能解决问题,还添乱,所以这个应该是第一位的,切记一定要准确,没有做不到,那就和没有 error message 没啥区别,甚至不如不提示,毕竟这样不提示不会让人误入歧路。 简明扼要 说到的是简明扼要,我们大家都知道注册的时候那一大串协议吧,你有认真看完吗?哪次不是拉到最后然后点击同意?当然谁也不会把 error message 写那么复杂,在此仅是举个例子而已,大家记得一定要简明扼要,能一句话说明白的就不要两句话。多说一句,注册的协议写那么一大串也是没办法的事,谁可以一句话把注册协议写明白?任何人都做不到,此时就是以准确为先了。 在前两个要素的基础上:提示用户为什么报错以及解决方案 个人以为这个才是最优秀的 error message,告诉用户为什么报错,这个其实不是必须的,但是很多时候告诉用户原因不会让用户迷惑,还是应该的,这个等下再举例说明。告诉用户解决方案,个人以为这个才是最核心的,当用户拿到这个解决方案的时候:不用任何思考,他不需要任何思考,只需要按照解决方案一步一步的做,就可以完美解决他的问题。之前看过的一篇文章,说乔布斯可以一秒钟把自己变成傻子,我认为是同理的,这才是真正做产品的思路,把自己当成傻子想象傻子怎么解决问题,所以做产品的时候就想象如果给傻子写 error message,告诉傻子解决方案,让他一步一步照做就可以解决问题,所以错误的提示的时候就把用户当成傻子,这样写出来的错误提示一般不会有大问题。 提示为什么报错 个人以为这个提示没错,但很多时候是有问题,唯一没有问题的时候,就是对于一些简单的问题可以告诉用户原因,但必须保证用户一看到原因就知道解决方案是什么,举个例子,表单提交,提示某某字段为空,用户一看就知道解决方案原来是这个字段不可以为空,填上就好了,所以这个告诉用户原因没问题,但是有些东西,用户拿到原因并不知道怎么解决,例如之前同事做的一个验车的时候遇到的问题,error message 提示:订单 ID 为空,这个提示有错吗?没有错,这就是典型的程序员的思维,原因就是这个,我找不到订单,就获取不到订单 ID,然后不能往下走,所以报了这个错。但是这确实是有问题的,用户拿到这个原因有啥用,他能找到订单吗?他不能,然后他知道怎么找到订单吗?也不知道。所以当时看到这个问题之后,我就力推优化这个提示,然后当时一堆同事说:没有办法优化。我当时就说为什么不能优化?同事都说,因为就是找不到订单。然后我就问:那让用户找某某某,怎么解决的,最后我力排众议,连产品经理反对也不管了,反正我是程序员,我能决定代码怎么写,就得按照我的想法来写。最后改成了告诉用户解决方案:请先联系客服绑定订单。然后看到这个解决方案他们就知道了怎么做了,而不是像以前那样遇到这个问题,就找到开发咋回事,然后开发再说你找某某某,然后某某某在告诉用户你找客服绑定订单,终于就再也没有用户因为这个问题来找我了,我也终于感到可以清闲一会了,那个某某某也可以轻松一会了。这就是典型的告诉用户原因,一堆人脑子转不过来弯的例子。 最后再说一下,前面说的一个问题如果只告诉用户解决方案呢?这个确实能解决用户的问题,但是个人以为有些时候用户会迷惑。举例就是:假设用户在支付的时候,如果余额不足,你不告诉用户原因,而是直接告诉用户不能用此支付方式,用户换个支付方式也能解决问题,但是用户会迷惑:为什么我不能用这个支付方式,需要换一个?最后用户也许自己能发现原因,但是用户可能会一试再试,因为用户不一定一定会听你的,所以你可以告诉用户余额不足,请换个支付方式支付,这个时候用户既知道了原因,又有了解决方案,可以更快速的解决问题。所以综上所述,个人认为最好的 error message 应该是: 在准确简明扼要的基础上:告诉用户原因以及解决方案,其次是告诉用户解决方案,再次是告诉用户原因,当做一个东西的时候,千万不要局限于程序员思维,多站在用户的角度上想想,而且一定要把用户当成傻瓜,当一个傻瓜用你的系统的时候,你的这个 error message 能不能帮他解决问题,如果不能,就说明不能说明有错,但一定说明还有优化的空间。 最后的最后,想说也许这么文章看起来很乱,但意思肯定表单的很清楚,如果你按照这篇文章的指导思想去写 error message,不敢多说,但一定会让用户感觉你的系统易用性提高了十倍,入门简单了十倍。

July 14, 2019 · 1 min · 46 words · Bridge Li

关于 MySQL 和 MyBatis 易错的几个点

由于某些不可抗拒力原因,自从开博以来断更了一个月,昨天晚上突然发现竟然解封了,今天立即写一篇小文章感谢党感谢政府感谢人民。话说,这一周有一个实习的同学,在写一个小东西的时候,发现一个问题,排序没有生效,刚好之前我也看过另外一个问题,现在算是总结一下。 ORDER BY 不生效 代码大概就是: SELECT * FROM t ORDER BY "id DESC"; 我们其实可以很明显的看出来这个 SQL 有问题,ORDER BY 的后面多了引号,关键是这个 SQL 报错吗?如果 id 是随便写的一个不存在的列报错吗?答案是都不报错,排序也不生效,大家可以测试一些,这有时候就比较坑了,具体为什么会出现这个错误,后面再说,先说第二个。 分页问题 代码如下: /** * 开始页码 */ private String pageSize; /** * 分页量 */ private String offset; <if test="pageSize != null"> <if test="offset != null"> limit #{offset}, #{pageSize} </if> <if test="offset == null"> limit #{pageSize} </if> </if> 这个错误就比较明显了,一般人也不会犯,为什么要单独说一下呢?因为某同事说,分页用 # 会出错,但是我记得我一直用 # 没问题啊,刚好在遇到上面这个问题的时候,顺便测试了一些,还真有错,奇怪啊,其实原因很简单,就是分页的对象的属性有误,我们知道分页的属性,一般都是用 Integer,这个地方却写了 String,真是没事找事啊,一般谁会这么写?个人认为除非脑子有病。下面看第三个问题,也是一个比较诡异的问题。 UPDATE UPDATE t SET username = "BridgeLi" AND passwd = "BridgeLi" WHERE id = 1; 这个 SQL 有问题吗?会报错吗?这个问题有时候真不太看得出来,其实也很明显,SET 后面的各个列应该是 “,” 相连,而这个用的是 AND,有时候脑子一抽,还真有可能写错。但是会报错吗?这个问题还真不好答复,因为这个 SQL 这么写,相当于: ...

July 8, 2019 · 1 min · 186 words · Bridge Li

Redis GeoHash 的一个小示例

上周产品经理提了一个类似于 LBS 的应用,第一时间想到了忘记了之前什么时候看 Redis 的 API,发现 Redis 自 3.2 版本之后,新增了一类关于地理位置相关的 API,于是拿来测试一下,发现特别好用,写一个小例子作为笔记。 首先需要说明的是,由于我们公司的 JDK 的版本是 1.7,所以我采用的 spring-data-redis 的版本是:1.8.20.RELEASE,最新二点几的版本已经不支持 JDK 1.7,而一点几和二点几的版本的 API 有略微的差异(下面会说明,还有一点点我的小感悟),废话不多说,直接看例子: @Override public Long geoAdd(String key, List<Entity> entities) { redisTemplate.delete(key); GeoOperations geoOperations = redisTemplate.opsForGeo(); Map<String, Point> map = new HashMap<>(); Point point = null; for (Entity entity : entities) { point = new Point(entity.getLongitude(), entity.getLatitude()); map.put(gson.toJson(entity), point); } Long add = geoOperations.geoAdd(key, map); return add; } 参数就两个很简单,一个是 key,一个是数据集,我们将在这个集合中找出符合条件的数据,需要说明的是: 第一行我先调用了一个 delete 方法,是将上次放进去的数据删除,因为这个命令是 add,也就是新增,但是 redis 并没有提供直接删除这个 key 的命令(有一个 remove 的方法,但是需要传入删除哪些数据,也就是不能只给一个 key,把这个 key 对应的数据都删除,个人感觉不太好用),当然你也可以在计算后取得相应的数据之后删除,个人感觉都一样,不要忘记清理数据就行,另一个方法就是设置过期时间,也都行; 为什么要清理数据?因为这个 add,不同的情况,放进去的数据应该不同的,如果 entities 已经发生变更,而一直 add,那么数据将会乱掉,所以先把之前的数据删掉再说; 我个人采用的是把数据放到了 Map 中,其中 key 是对象序列化之后的 json 串,目的是为了下面找到对应的数据之后,直接反序列化成对象进行返回,当然也可以采用其他的方案,还有就是 add 还有一些其他的 API,这个大家可以自己看文档,选择合适的就行; 数据放进去之后就是计算了,我们的需求就是算一个人旁边几公里内有多少符合条件的数据,代码如下: ...

May 19, 2019 · 2 min · 323 words · Bridge Li

分享 Guava 的一些常见方法

前几天同事分享了一些关于 Guava 的一起基础用法,我之前没用过,感觉挺好的,所以记一些常见的方法。 一. 基础工具类,字符串相关的 其实这些在 apache commons-lang3,算是重复造轮子吧,简单说一下。 判断字符串是否为空,之前看到很多人自己定义,这些可能是一些老程序员吧,apache commons-lang3,Guava 的如下: boolean nullOrEmpty = Strings.isNullOrEmpty(""); 补全字符串(在前面补全和后面补全) String padStart0 = Strings.padStart("3", 2, &#8216;a&#8217;); System.out.println("padStart0 = " + padStart0); String padStart1 = Strings.padStart("333", 2, &#8216;a&#8217;); System.out.println("padStart1 = " + padStart1); 拆分和合并字符串 List<String> list = Splitter.on(",").splitToList("Denny,BridgeLi,CCC"); System.out.println("list = " + list); String join = Joiner.on(",").join(list); System.out.println("join = " + join); 对象相等判断和 ToStringHelper 类 boolean equal = Objects.equal("", ""); MoreObjects.toStringHelper(); 二. 集合类相关的,这些个人感觉还是非常常用的 ...

April 30, 2019 · 2 min · 366 words · Bridge Li

关于 CAP 理论 和 BASE 理论

一、CAP 理论 CAP 理论是分布式计算领域公认的一个定理,是分布式架构师必须掌握的理论,目前网上关于这块的资料也很多,各种说法,其实 CAP 理论自身也是一个不断发展的过程,相比之下比较准确的说法应该是:在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。也就是说必须是相关连接并且共享数据的分布式系统才是我们讨论 CAP 的基础,另外就是 CAP 理论关注的是对数据的读写操作,而不是分布式系统的所有功能。明白了这些之后,下面对 CAP 逐一讲解: 一致性(Consistency) 很多资料都说,对一致性的讲解时所有节点在同一时刻都能看到相同的数据,其实更准确的应该是:对某个客户端来说,读操作保证能够返回最新的写操作结果,是否数据一定一直呢?还真不一定,对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致,因为事务在执行过程中,client 是无法读取到未提交的数据的,只有等到事务提交后,client 才能读取到事务写入的数据,而如果事务失败则会进行回滚,client 也不会读取到事务中间写入的数据。 可用性(Availability) 一般的解释是,每个请求都能得到成功或者失败的响应,更准确的是,非故障的节点在合理的时间内返回合理的响应(不是错误和超时的相应),其实什么是成功和失败?超时报错算不算失败的响应?所以应该是合理的响应,另外故障节点肯定是不能返回结果了。 分区容错性(Partition Tolerance) 一般说法是,出现消息丢失或者分区错误时系统能够继续运行,其实更准确的是,当出现网络分区后,系统能够继续“履行职责”。因为关于运行,只要不宕机就可以说在运行。 综上,其实 CAP 理论也是一个自身不断发展的历程,虽然以前的理论也能说明问题,但是仔细深究起来发现有些问题,下面会有说明。 CAP 应用 CAP 理论开明宗义就说我们必须放弃一个,但是其实仔细想想我们可以放弃 P 吗?因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 结果,但是需要说明的是,按照通常解释,是可以 CA 的,因为通常的解释 A 是返回失败或者成功的结果,而不允许写入,其实就是失败的结果,满足 A 的定义 CP 为了保证一致性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C“系统现在发生了错误”,这种处理方式违背了 A 的要求,因此 CAP 三者只能满足 CP ...

March 31, 2019 · 1 min · 149 words · Bridge Li