今儿不小心在群里看到一位朋友发了下面的代码:
通过输出可以看到,这段代码的作用是把驼峰命名格式的字符串替换成下划线分割,这个功能比较简单,但是吸引我的却是他的代码。
"createTime".replaceAll("([A-Z]+)","_$1")
这行代码其实很简单,就是调用了String类的replaceAll方法,方法的第一个参数是正则表达式,第二个参数是将要被替换成的新值。让我惊奇的是他代码中,replaceAll的第二个参数,也就是JDK文档中名为replacement的参数,竟然是”_$1”,这是什么鬼?还支持类似占位符这样的东西?我一直都不知道。。
由于之前研究过一段正则表达式,通过观察replaceAll的第一个参数([A-Z]+)
,我猜想,这个应该是用到了正则表达式的分组,对应JDK中,就是java.util.regex.Matcher
类的group()
方法。
于是看了下String.replaceAll方法是如何实现的。
JDK源码如下:
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
哦,原来它底层就是用了Matcher,只不过用的是Matcher自己的replaceAll方法。
去看它的文档:
原来这个方法的参数果然有鬼,看下实现代码:
public String replaceAll(String replacement) {
reset();
boolean result = find();
if (result) {
StringBuffer sb = new StringBuffer();
do {
appendReplacement(sb, replacement);
通过MULTI命令开始事务,MULTI命令把执行该命令的客户端从非事务态转为事务态。该命令服务端始终返回OK
开启事务之后,客户端向服务端发送的命令不会被立即执行,服务端会将这些命令放入一个事务队列,同时响应给客户端QUEUED。
客户端向服务端发送EXEC命令之后,服务端事务队列中的事务会被按顺序执行 ,最后执行命令所得结果全部返回客户端 。
数据库事务的ACID特性在Redis中是否都满足呢?
Redis事务满足原子性。事务队列中的命令要么全部执行,要么全部放弃。
需要注意的是,如果在事务队列中的命令执行的时候,某些命令执行出错,那么也不会影响其他命令的执行。比如命令A,B,C,D是在一个事务中,假如B执行出错了,那么ACD三个命令依然会执行,并且执行结果也会生效。这一点与MySQL等关系型数据库不同,原因在于Redis事务不支持回滚,而不支持回滚的原因是Redis的开发者认为这会使redis复杂。
Redis是单线程的,并且服务端保证在执行事务期间不会对事务进行中断,所以Redis的事务总是以串行方式运行的,所以,Redis的事务也是具有隔离性的。
数据库的一致性指的是数据库中数据的状态变化总是从一个一致性的状态变化到另外一个一致性的状态。即事务执行前是一致的,事务执行之后,状态也应该是一致的。
Redis事务也具备一致性,从以下三个方面进行保证:
事务开启后,如果一个命令入队时出错(比如命令的参数个数不对或者命令不存在),那么收到客户端的EXEC命令之后,Redis将拒绝执行事务。
PS:入队错误,不会返回给客户端QUEUED,所以客户端可以知道命令出错,最后通
如果你有仔细阅读过HashMap的源码,那么你一定注意过一个问题:HashMap中有两个私有方法。
private void writeObject(java.io.ObjectOutputStream s) throws IOException
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
这两个方法有两个共同点:
1. 都是私有方法
2. 虽然是私有方法,但是在HashMap内部却找不到任何调用它们的地方
答:readObject和writeObject方法都是为了HashMap的序列化而创建的。
首先,HashMap实现了Serializable接口,这意味着该类可以被序列化,而JDK提供的对于Java对象序列化操作的类是ObjectOutputStream,反序列化的类是ObjectInputStream。我们来看下序列化使用的ObjectOutputStream
,它提供了不同的方法用来序列化不同类型的对象,比如writeBoolean,wrietInt,writeLong等,对于自定义类型,提供了writeObject方法。
ObjectOutputStream的writeObject方法会调用下面的方法:
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slot
项目中有一个接口ConfigService
,该接口有一个实现类RedisDBConfigService
,实现类中有个方法使用了Spring的事务注解@Transactional
,代码大致如下:
@Service
public class RedisDBConfigService implements ConfigService {
@Autowired
private DataMapper mapper;
//为了做单元测试,提供一个set方法
public void setDataMapper(DataMapper dataMapper) {
this.mapper = dataMapper;
}
//基于Spring事务控制
@Transactional(rollbackFor = Exception.class)
@Override
public void updateConfig(Data data) throws Exception {
doSomething();
}
}
Spring的事务配置如下:
<!-- 注解方式配置事物 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
这种方式有一个隐含条件就是:Spring为需要事务控制的类创建代理的时候,默认使用JDKProxy来创建。
以上是前提条件。这里要说明一下,程序并没有任何问题,上面的设置也是正确的,可以正确无误运行。那么这样的设置出现了什么问题呢?
问题出在单元测试上。如果仅仅测试一下updateConfig方法能否正确运行,是很简单的,也不会有问题。但是单元测试的意义不仅于此,有时候我们需要测试一些异常情况。
比如这个例子中,我需要测试一下DataMapper执行出错抛异常的时候程序的运行结果。为了让程序抛异常,我的思路是自己实现一个DataMapper,然后在方法中直接抛出异常,然后将该DataMapper赋值给RedisDBConfigServ