[片段] Mybatis ResultSetHandler实践
这次拦截的方法是handleResultSets(Statement stmt),用来批量解密用@Encrypted注解的String字段,可能还有一些坑。
1 |
|
1 | package app.pooi.common.encrypt; |
[片段] Mybatis ResultSetHandler实践
这次拦截的方法是handleResultSets(Statement stmt),用来批量解密用@Encrypted注解的String字段,可能还有一些坑。
1 |
|
1 | package app.pooi.common.encrypt; |
职业发展的驱动力应该来自自身,工作属于公司,职业生涯属于自己。
大多数人形成的错误的心态: 认为在为公司打工,没有把自己的职业生涯当作生意来看待。铭记在心,开始积极主动的管理自己的职业生涯吧。
自己能提供什么:自己的能力就是创造软件
自己需要做什么:
持续不断地改进和完善自己的产品
传达自己的价值,和千万同行的独特之处
一头扎进工作不可能非同凡响,你应该:
无论因为何种原因你没有为自己的职业生涯设定目标, 现在都是时候设定目标了。 不是明天, 也不是下周, 就是现在。 没有明确的方向, 你走的每一步都是徒劳的。
先在心中树立一个大目标,然后分解成多个小目标
定期核对自己的目标,必要时还要调整。
如果刚刚投入工作,工作还无法完全胜任,那我的建议是多写写代码,打怪升级完成自己的年度绩效。
如果已经能够胜任当前工作,下一步应该集中在提效上。这一步和之前自己积累的经验和知识密不可分,只有真正了解代码懂代码,才能从同龄人中的“熟练工”脱颖而出,两者的提效虽然结果一样,但是本质却完全不一样。
如果已经摆脱了熟练工的称号,实际上已经完成了自我提效,提前完成自己的本职任务,下一步可以计划推动团队的效率。
如果太过专注技术,专心自己的一亩三分田,相当于给自己设限成毕业两三年的水平。这种人应该被打上不胜任的标签,在寒冬中很容易被优化掉。专注技术还有一个误区,就是容易把“实施细节”和“技术”两者混淆,特别在软件行业“实施细节”很容易随时代改变,基本三年就会大变样,而“技术”类似于“知识”不会过时。看到这里大家可以自己思考下,自己学到的到底是“实施细节”还是真正的“技术”。
恭喜你跨过了第一道坎,推动团队进步不是一个人的事,一般推动团队分为两部分:个人影响团队,团队自我驱动。
个人影响团队比较简单,就是把自我提效所积累的经验和知识共享给整个团队,完成的手段可以是博客分享,会议分享,文档分享。
团队自我驱动,实际上我把整个团队拟作了一个人,一个人找出别人的缺点很容易,但是找出一个团队的问题却没那么容易,同时也会受到公司和领导的局限,比如一些项目管理的领导就是二传手,只催你进度的那种,这时候就需要你主动找他讨论。
如何找到团队的缺点?可以通过下面两个大类的套路:管理手段和技术手段。
管理手段可以从知识管理、代码规范、需求分析三处着手:
知识管理: 建立知识库,避免重复的培训,重复的解答问题
代码规范:借助代码缺陷检查工具,具体到负责人提升代码规范
需求分析:避免低效,无效会议,避免各种妥协下产生的需求
技术手段比较简单:
重构升级:弃用老的架构,拥抱新技术,带领团队提升技术
内部造轮子:内部定制工具,帮助团队高效完成任务
找到了团队的缺点接下来可以制定度量,衡量团队的推进程度,可以从两个角度进行度量:
跟自己比较:比如这次做需求提测bug数减少,需求delay少了,满足需求不需要上线只要配置上线,等等
和别的团队比较:这个比较凶残,我也不知道用什么度量比,但是有的大公司就是这样做的。
从发现缺点到最后得到成果完成团队推进目标,是时候写个ppt在领导面前吹一波了,这个就不用我教了。
在构建大型系统时,常常会遇到各种错误。在计划构建一个系统时,定义系统的“健康状态”十分重要。
“健康状态”必须是可度量的,一般做法是使用SLAs来度量系统的“健康状态”。最常见的SLA为
可达性
从时间维度衡量(99.999%可达性,每年下线50分钟)
准确性
对于数据的丢失或失真是否可以接受?可以达到多少百分比?对于支付系统来说,不接受任何数据的丢失和失真
容量
系统支持并发
延迟
响应延迟,一般衡量95%请求的响应时间和99%请求响应时间
确保新系统比被替代系统“更好”,可以使用上面四个SLA指标来衡量,可达性是最重要的需求。
随着新业务的增长,负载也会增加。最常见的伸缩策略是垂直和水平伸缩。
水平伸缩就是增加更多的机器或节点,对于分布式系统来说水平伸缩是最常有的方式。
垂直伸缩基本上就是买更大/好的机器。
可达性对于任何系统都是很重要的,但是分布式系统一般都构建在低可达性的机器上(比如:服务的可达性要求99.999% 机器的可达性为99.9%)。简单的做法是维护一组机器组成集群,这样服务的可达性不依赖单独的机器。
一致性是在高可用系统中最需要关心的。一个一致性系统在所有的节点上看到和返回的数据在同一时间是相同的。如果使用一组机器来组成集群,它们还需要互相发送消息来保持同步,但是发送消息可能失败,这样一些节点就会因为不一致而不可达。
一致性有多个模型,在分布式系统最常用的是强一致性,弱一致性和最终一致性。一般来说,一致性要求越低,系统可以工作的更快,但是返回的数据不一定是最新的。
系统中的数据需要是一致的,但是到底是怎样的一致?对于一些系统,需要强一致性,比如一次支付必须是强一致的存储下来。对于没那么重要的部分,最终一致性是可以考虑的权衡。比如列出最近的交易。
持久性表示一旦数据成功添加到数据存储,它就永远可以访问到。不同的分布式数据库拥有不同级别的数据持久性。一般使用副本来增加数据持久性。
对于支付系统来说,数据不允许丢失。我们构建的分布式数据存储需要支持集群级别的数据持久型。目前Cassandra, MongoDB, HDFS和Dynamodb 都支持多种级别的数据持久性。
分布式系统中的节点执行计算,存储数据,互相发送消息。发送消息的关键是消息的可靠到达。对于关键系统,经常需要消息零丢失。
对于分布式系统,发送消息一般石油分布式消息服务发送,如RabbitMQ,Kafka。这些消息服务支持不同级别的消息投递可靠性。
消息保持表示当节点处理消息失败时,在错误被解决前消息一直被保持着。消息的持久性一般在消息队列层被使用。如果在消息发送的时候队列或节点下线了,那在它们重新上线是还能接收到消息。
在支付系统中我们需要每一条消息投递一次,在构建系统中保证投递一次和投递至少一次在实现上是有区别的。最后我们使用了kafka来保证投递至少一次。
在分布式系统中,很多东西都可能出错,连接会丢包或超时,客户端经常会重试这些请求。一个幂等的系统保证无论多少特定的请求被执行,一个请求实际的操作只会执行一次。比如支付请求,如果客户端请求支付并且请求已经成功,但是客户端超时了,客户端是能够重试相同的请求的。对于一个幂等的系统,一个个人的支付是不能被收取两次的。
对幂等的设计,分布式系统需要某种分布式锁机制。假设我们想要使用乐观锁来实现幂等性,这时系统需要强一致性的锁来执行操作,我们可以使用同一个版本的乐观锁来检查是否有启动了额外的操作。
根据系统的一致性和操作的类型,有很多方式来实现幂等性。在设计分布式系统时,幂等性时最容易忽略的。在支付系统中,幂等操作时最重要的,它避免了双花和双收问题。消息系统已经保证了消息至少消费一次,我们只需要保证所有重复的消息保证幂等性即可。我们选择使用乐观锁,并使用强一致性存储作为乐观锁的数据源。
分布式系统经常需要存储大量的数据,远超一台节点的容量。一般的解决方案时使用分片,数据使用某种hash算法被水平分区。尽管很多分布式数据库屏蔽了分片的实现,但是分片还是挺有意思的,特别是关于重新分片。
许多分布式系统在多个拥有数据和统计信息。为保证对数据操作的一致性,使用基于投票的方式是不行的,只有超过一定数量的节点操作成功,这个操作才是成功的,这个叫做法定人数。
描述分布式系统最普遍的做法是使用Actor模型,还有一种方法是CSP。
Actor模型基于actor互相发送消息并作出回应。每一个actor只能做少量的动作,创建其他actors, 发送消息或者决定如何处理下个消息。通过这些简单的规则,复杂的分布式系统可以被准确描述,可以在actor崩溃后自我修复。
使用akka提供了标准的分布式模型,避免我们重复造轮子。
当构建大型分布式系统时,目标常常是它们的弹性,伸缩性,和扩展性。反应式架构是在这个领域最流行和最通用的方案。
这次遇到贝壳花名的需求,需要使用最长公共子串对花名做校验。这种算法在面试题中算是必会题,各种四层循环,三层循环,两层循环的代码在我脑中闪过,但是今天就是要带你实现不一样的最长公共子串!
使用动态规划,两层循环,使用二维数组存储状态,时间复杂度O(n^2^),空间复杂度O(n^2^)或O(2n)
一张图解释原理:
1 | 先 横 向 处 理 |
从上图可以发现,在纵向累加时实际只需要左上方的计数器即可, O(n^2^)的空间白白被浪费了,最优的空间复杂度应该是O(1)。那么该如何处理呢?
一张图解释原理:
e a b c b c f
+---+ +---+---+---+---+---+---+---+
a | 0 | | 1 | 0 | 0 | 0 | 0 | 0 | a
+-------+ +-----------------------+
b | 0 | 0 | | 2 | 0 | 1 | 0 | 0 | b
+-----------+ --------------------+
c | 0 | 0 | 0 | | 3 | 0 | 2 | 0 | c
+---------------+ ----------------+
d | 0 | 0 | 0 | 0 | | 0 | 0 | 0 | d
+-------------------+ +-----------+
e | 1 | 0 | 0 | 0 | 0 | | 0 | 0 | e
+---+---+---+---+---+-------+ +---+---+
e a b c b c f
答案就是沿着等长对着线处理。
大家可以根据上面思路写一下,一般会把算法分成两部分:处理长方形的左下部分和处理长方形的右上部分,两部分都是双层循环,时间复杂度和空间负载度已经变为了O(n^2^) ,O(1)。
肯定有人已经发现自己的代码处理左下角的代码和处理右上角的代码不能复用,一个是从中间向左下角处理,一个是从中间向右上角处理,明明很类似,但是就是没发合并。
那么有没有方法把这两部分处理抽象成公共代码呢?不卖关子了,直接上图:
1 | + |
如果你想使用公共代码同时实现处理左下角和右上角是不可能的了。所以你需要把右上角的三角翻折,然后你就得到了两个三角:
1 | + |
这样就变成了处理两遍左下角了,代码也可以完美复用!!!
我的完整思考过程已经分析完毕,这样沿对着线处理还有一个小小的优点:提前结束搜索。这一点大家可以自行思考,这里不做过多解释。
直接干货上场:
1 | public class Solution { |
怎么样,经历这次优化过程是否感觉自己对最长公共子串的认识又更深了一步呢?虽然不能保证是首创(也可能是首创?),但是这次一步一步真切思考优化直到获得成果让我无比兴奋。
说了这么多,我就是要给我们贝壳招聘开发组打个广告>_>,期待更多爱思考优秀的同学加入!

UC权限系统基于角色访问控制技术RBAC(Role Based Access Control) 。具体来说,就是赋予用户某个角色,角色给与角色对应的权限能访问及操作不同范围的资源。
代表一个角色对应某个权限所能操作的数据范围,比如gitlab组管理员能看到组下的所有项目代码,我们可以这样配置:
对绝大多数简单的系统来说一个用户对应一个系统只会有一个角色,一个角色只有一个数据权限范围(即使有多个,也可以合并成一个)。但是随着产品的功能迭代,用户的变更和系统设计的原因,总有一些特殊且重要的用户在同一个系统中拥有多个角色。在多角色和数据权限的组合下,一个用户可以拥有复数的数据权限范围。
考虑到实现复杂性,大多数系统选择使用角色切换的手段简化系统实现,同时对用户暴露出了他们不熟悉的角色这一概念,造成这些用户在系统使用中的各种不便。
本文重点讨论在避免角色切换的前提下,进行多角色数据范围查询的一种思路。
我们的数据报表后台,不同的角色拥有不同的数据查看范围(不同角色所能看到的员工数据字段也各不相同),例如:
简单来说,拥有长期激励管理员和高级薪酬管理员的用户能否直接看到高职级员工数据和长期激励员工数据?至少在直觉上是可行的。
单角色单数据范围可以使用一句sql查询出结果,那多角色多数据范围是不是使用多句sql查询出结果合并就可以了?
具体体现:
1 | select * from ( |
如何解决?方法有很多!
1 | graph LR |
步骤:
1 | select id, GROUP_CONCAT(a.role) as roles from ( |
结果:
| id | roles |
|---|---|
| 1 | 薪酬管理员 |
| 5 | 薪酬管理员,长期激励管理员 |
遍历角色直到找到满足条件的权限即可。
自己不行动,等于等着被别人安排哈哈
自己想。还可以点这里
一个计算顾客租赁影片费用的程序,能容易写成面条式的代码(流水账):顾客类调用影片类和租赁时长计算费用
对机器来言只要能运行正确没有好代码和坏代码之分,但是对(维护的)人来说很难找到修改点,容易引入bug
利用单元测试保证重构正确性
不要为修改变量名感到羞耻,只有写出人能理解的代码才是好程序员
重构完可能 性能变差,但同时会带来更多的机会来优化
switch需要关心具体条件,多态具有switch不具备的优势:不需要关心具体类型
重构:不改变运行结果下 提高理解性 降低修改成本
代码结构的流失是累积性的,越难看懂代码设计意图,越难保护其设计
消除重复代码,方便修改
我们编写代码时很容易忘记读者的感受,造成他人时间的浪费
重构时犯错可以加深对代码意图的理解,可以帮助发现bug
好的结构设计是加速开发的根本
添加功能时重构,在修改过程中把结构理清,也可以更简单的添加功能
修复错误时重构
复审代码时重构
程序有两面价值,今天可以为你做什么和明天可以为你做什么为了满足明天的需要,你会遇到:
而你希望看到的是:
重构就是把程序转变为这些特征的工具
很多经理都是进度驱动,所以更加需要重构带来的好处,所以不要告诉他们 他们不会理解的
间接层优点:
允许逻辑共享
增加解释意图和实现的机会-多了类名和函数名
隔离变化
多态封装条件逻辑
未完成的重构可以称之为债务,迟早要还
重构可以节约不必要的时间精力花在预先设计上,让软件设计向简化发展
重复代码
过长函数
过大类,过长参数
修改一处程序的原因过多/一个原因修改过多的程序
数据依赖过多
重复的字段和参数
总是放在一起的字段
switch语句
平行继承关系
不是所有分支下都需要的临时变量
过度耦合调用链
不必要的委托
失血数据类
频繁重写父类方法
过多注释
自己看书吧
经济金融类:
《斯坦福极简经济学》分微观和宏观经济学 没有教科书式的介绍 比较好读,推荐
《随机漫步的傻瓜》 经验之谈 观点可以接受
《买晾衣杆的小贩为何不会倒》 贴近生活 过于简单 不推荐
《富爸爸 穷爸爸》小白入门首选 推荐
《小岛经济学》 深入浅出 最后映射中国和美国关系 后面有点看不懂 推荐
小说类:
《解忧杂货店》 个人感觉一般 后半部分剧情我都猜到了
《人间失格》 遭受到了社会的毒打 很现实 有点致郁 心里承受能力低的不推荐
《三体》 第一部可以的,后面一部不如一部 但是比起其他网上大众喜欢的爽文好多了
技术类:
《java编程的逻辑》 温故而知新,基础好的不用看了 小白推荐
《kafka权威指南》正在看 但是真的写的不错 推荐
《mybatis从入门到精通》 一般般,只有入门吧
《netty权威指南》 一般般
《redis设计与实现》 真设计与实现 推荐
《深入理解java虚拟机》多读几遍也不为过 推荐
《SpringBoot实战》 读的比较早没影响了 不推荐
《算法图解》 程序=数据结构+算法 小白推荐(我就是小白)
心理类:
《乌合之众》 人在小范围为私利行动 在民族范畴会抛开私利做出些过于崇高或粗鲁的行为 推荐
《原生家庭》 一个人受童年家庭的影响是最大的,这本书能看懂是一回事,能做好是另一回事
1 | /** |
稍微注意下在线程争用锁是才会初始化链表
1 | /** |

内部维护state和CLH队列,负责在资源争用时线程入队,资源释放时唤醒队列中线程。
而实现类只需要实现 什么条件获取资源成功 和 什么条件释放资源 成功就可以了
所以,最简单的CountDownLatch使用AbstractQueuedSynchronizer实现非常简单:
申明AbstractQueuedSynchronizer的state数量(比如十个)
await方法尝试获取资源,如果state>0表示获取失败( **什么条件获取资源成功** ,CountDownLatch实现),获取失败线程休眠(AbstractQueuedSynchronizer负责)
countDown方法state-1,如果state==0表示资源释放成功( **什么条件释放资源成功** ,CountDownLatch实现),唤醒队列中所有线程(AbstractQueuedSynchronizer负责)
顺着ReentrantLock lock、unlock看一遍我们就大致总结出AbstractQueuedSynchronizer工作原理了
先简单介绍下ReentrantLock特性:可重入,中断,有超时机制。

黄色表示ReentrantLock实现,绿色表示AbstractQueuedSynchronizer内部实现
1 | **public** final void acquire ( int arg) { |
获取的锁的逻辑:直接获取成功则返回,如果没有获取成功入队休眠(对就是这么简单)
下面我们仔细一个一个方法看
我这里贴的时非公平的所获取,公平和不公平的区别在于公平锁老老实实的会进入队列排队,非公平锁会先检查资源是否可用,如果可用不管队列中的情况直接尝试获取锁。
1 | final boolean nonfairTryAcquire ( int acquires) { |
ReentrantLock.tryAcquire读取到state==0时尝试占用锁,并保证同一线程可以重复占用。其他情况下获取资源失败。如果获取成功就没啥事了,不过关键不就是锁争用的时候是如何处理的吗?
1 | private Node addWaiter ( Node mode) { |
一旦锁争用,一定会初始化队列(因为排队的线程需要前驱节点唤醒,所以要初始化一个前驱节点),之后自旋成为队列尾节点。
简单来说就是获取不到锁就放进队列里维护起来,等锁释放的时候再用。
这里还有一个 很具有参考性的小细节 :先设置新节点的前驱结点,自旋成为尾节点后设置前驱的后驱
1 | final boolean acquireQueued ( final Node node, int arg) { |
前面只是维护下链表数据结构,这里负责找到合适的唤醒前驱,然后让线程休眠。
这里主要是一个循环过程:

1 | public final boolean release ( int arg) { |
unlock的代码特别简单: