你认为的:我学习了某种技术或者思想,掌握了之后运用到我们的项目中。实现了某个优化,达到了某种欧标。面试官听到的:这个人技术尺度很窄,只会自己项目中的东西。
分类:未分类
spring事务传播属性?
xml | 注解 | 实际 |
REQUIRES_NEW | REQUIRED | REQUIRES_NEW |
REQUIRED | REQUIRES_NEW | REQUIRES_NEW |
REQUIRED | NOT_SUPPORTED | REQUIRED |
NOT_SUPPORTED | REQUIRED | NOT_SUPPORTED |
慎用for update 悲观锁
在无事务环境下,锁不会释放
springboot初体验
-
启动和junit默认加载主类下级的包,主类务必放在最上层。
-
刚开始启动的时候为了无数据源启动,加上了exclude={DataSourceAutoConfiguration.class
导致jdbcTemplate注入失败
这个富文本编辑器有点难用了,考虑换掉
redis学习日记(一)-在CentOS上部署redis服务
之前公司项目用过redis做消息队列,但是没有深入探究过,趁着现在有空重新系统学习一下。
第一步先在服务器上安装一下redis服务,这个没啥技术含量,直接转一下别人的链接,我照着安装的没有问题。
https://www.cnblogs.com/heqiuyong/p/10463334.html
接下来还需要配置一下外网访问
1.确认配置文件bind的ip是否正确,一般想要外网能访问,需要填写为0.0.0.0,表示监听任何ip
注意任何人都能访问,一定要开启密码 requirepass 你的密码
2.确认protected-mode 是否为 no
3.修改完配置文件后重启Redis
service redis restart
另外为了避免redis密码被暴力破解,我在服务器上配置了6379端口入站策略,只有我的测试机器可以访问。
Spring事务异常rollback-only深入探究
首先rollback-only出现的原因先简单带过一下吧:在使用了Propagation.REQUIRED的事务传递中,若本层的service捕获了下层service的异常,则本层中的事务也无法提交,在方法结束,事务尝试提交时会报出Transaction rolled back because it has been marked as rollback-only错误。这是因为REQUIRED是同一个事务,具有原子性。当内层的方法出现异常的时候,会标记一个rollback-only,这样外层方法提交的时候,判断rollback-only为true,那么整个事务都不允许提交。参考代码如下:(以下代码全部基于service层方法级别注解,且事务传播机制为REQUIRED)
service1 public String getName(String name) { try { demoService2.insertUser(); }catch (Exception e) { e.printStackTrace(); } demoService3.insertUser(); return "1"; } service2 public void insertUser() { userDao.insertUserError();//这里报错,字段超长 } service3 public void insertUser() { userDao.insertUser1(); //这里正常插入一条数据 }
这样执行service1.getName后,service3的insertUser也是无法提交的。原因就是上边说的。
接下来对service2进行一下修改
service2 public void insertUser() { try { userDao.insertUserError();//这里报错,字段超长 }catch (Exception e) { e.printStackTrace(); } }
这样执行一下,可以看到service3提交了。这里就不再分析源代码了。研究一下原因是:DAO没有标记事务,只是使用了当前的连接,因此当DAO异常,没有标记当前事务的roll-back。而异常在service2里边进行catch了,因此在service1中没有异常,因此对于整个事务来说,spring认为这个事务没有出现异常,因此事务正常提交。
在修改一下
service2 public void insertUser() { try { demoService4.insertUser(); userDao.insertUserError();//这里报错,字段超长 }catch (Exception e) { e.printStackTrace(); } } service4 public void insertUser() { userDao.insertUserError();//这里报错,字段超长 } 这样再执行service1.getName还是和第一种情况一样,roll-back only了。
考虑到spring的事务传播机制,我们再对代码进行try catch时,应该多一些思考,事务传播往往和异常有着千丝万缕的关系。check exception不用多说了,主要是对于runtimeException,一定要慎重。 比如,在最内层catch异常,会对整个事务的原子性造成污染,即try的DAO可能执行失败了,但是外层方法的事务还是提交了。 当存在需要单独开辟事务的场景时,需要考虑其他的事务传播属性。
实际开发中,对于异常的处理更是一门学问,另找时间研究。
又一次栽倒arraylist浅拷贝的坑
先记下两次的解决方法。
-
实现Cloneable 接口,重写clone方法。
场景:现有一个类Person[sex,age],存放在ArrayList中。要求得到一个ArrayList,存放sex=男和sex=女的年龄总和。
思路:重写Person的hashCode和equals方法,使用sex判断。遍历ArrayList l1,将ArrayList存到Set中,再转回ArrayList l2,获取去重后的性别。再遍历l2,内部遍历l1,判断sex相等的,将age加到l2中。
for(int i=0;i<l2.size();i++){
for (int j=0;i<l1.size();f++){
if(l2.i.sex=l1.j.sex){
l2.age=l2.age+l1.age;
}
}
}
问题:在改变l2中person对象的age时,l1的person对象也发生了变化。
原因:经查看HashSet的add方法,内部调用了HashMap的put方法。
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
查看HashMap的put方法
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
可以看到上述代码,在hashmap中找到hash相同的Entry,然后判断key相等的,对value进行交换,即hashMap后边put的会覆盖之前put的value。而对于hashSet而言,传入的value是空的Object,因此Key没有发生变化,即遍历ArrayList l1,将ArrayList存到Set中,这一步,Set中存放的sex=男 的这个对象是l1中第一条sex=男 的对象,后边的add直接跳过。而再Set转回l2后,对l2的person对象的age属性进行操作的同时,l1中的相同的person对象也发生了变化。
错误思路:使用Collections.copy(newList, oldList);创建一个新的l3代替l1,然后进行转set。结果:无效,经查看
Collections.copy创建了新的list对象,但是list中存放的person对象还是原person对象,因此无效。
正确思路:重写person的clone方法。在set转l2的时候,存入l2的存放person的克隆对象,这样修改l2的
person对象的时候不会影响到l1的person对象。
注意:默认的clone方法,其实现还是person层的浅拷贝,若person中有引用类型的属性,而对这个
引用类型的属性进行操作时,还应注意。由此引出下一条。
2.new 一个新对象,挨着set值。
不再赘述,一句话总结:浅拷贝,包含clone的默认实现或者Collections.copy,获取的对象是
新的对象,但是对象内部的引用类型属性的地址是相同的。可以重新指定新对象的引用对象的地址,但是
直接修改引用对象的属性,则原对象的引用对象的属性也会改变。
cron表达式 每个月的最后一个工作日的另一种解法
近日有个需求,要求某项定时任务在每个月的最后一个工作日执行(仅考虑周末的情况),但是没有好的cron表达式写法,最后在stackoverflow找到了思路
cron每天执行一次,然后在执行定时任务的方法加个判断
public static boolean checkLastWorkDay() { Calendar cal1 = Calendar.getInstance(); int month = cal1.get(Calendar.MONTH) + 1; int year = cal1.get(Calendar.YEAR); int day = cal1.get(Calendar.DATE); LOG.warn(year + "年" + month + "月" + day + "日"); int week = 0; Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);// 获取该月最大一天 cal.set(Calendar.DAY_OF_MONTH, lastDay); week = cal.get(Calendar.DAY_OF_WEEK) - 1 == 0 ? 7 : cal.get(Calendar.DAY_OF_WEEK) - 1;// 获得最后一天是星期几 if (week == 7) { lastDay = lastDay - 2; } else if (week == 6) { lastDay = lastDay - 1; } if (day == lastDay) { LOG.warn("今天是本月的最后一个工作日"); return true; } else { LOG.warn("今天不是是本月的最后一个工作日"); return false; } }
问题解决。
悲观锁
悲观锁简而言之就是查询的时候就把数据锁上,修改完提交后释放锁。这样如果别的session有事务没提交的话,查询就会处在等待状态。锁一旦释放,这个session争夺到资源后立即上锁。的确暴力而有效啊。