DDL-脏数据层的实现

在我们的项目中,经常会有一些数据会涉及到频繁更改。如果每次都从数据库中读取再修改,这样不仅浪费时间,而且还更加危险。那此时我们究竟该如何解决这个问题呢?此时,DDL(脏数据层)就出现了。

首先说一下为什么操作不能保证原子性就会危险,因为这时就很有可能出现同时修改的情况,最终的结果极有可能并不是你所希望的(除非这些操作都是幂等性,但这种情况应该比较少)。如果是利用数据库中的锁,一来我在项目中用的比较少,二来也增加了维护难度。当然,有人说可以利用CAS,那针对一些复杂的情况(比如类里面属性的修改会有一些相关性,你的一次更改需要涉及几个属性等),可能你还是需要单独设计一套系统,而且还会有经典的ABA问题。如果你是利用CAS解决的,希望能够在下方评论区告知,就当互相学习。

那现在来说说DDL层具体是什么。DDL全称是Dirty Data Layer,即脏数据层。针对那些在系统运行经常会更改的domain类,我们将其再做一次封装,组成一个类似map的形式。单独由一组线程来管理这些map,每当有数据改动时,我们就往这个map中添加内容,而我们的线程则定期向数据库中写入内容。这样做的好处,首先是让你的每一次操作都没有IO的参与,提高了相应速度,而且定时提交意味着你可以把原本的几次提交变成为1次,减少了和数据库的交互。当然,缺点也是存在的,如果你的系统是分布式,那么你的这个DDL层的实现可能就没有那么方便,因为这些数据你可能需要存储在类似Redis这种共享缓存中,因此每次的拿和取就需要封装一下(这个应该算是小问题,因为原本就算你用的是本地缓存,所有操作依旧是需要封装的,只不过你的IO消耗由原本的数据库变成了共享缓存)。接下来,我就针对本地缓存的情况来具体实现一个DDL。

定义操作

这是我定义出的一些操作:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public interface IDirtyEntity {

//region manage content

/**
* 获取entity的内容。
*/
Object getContent();

/**
* 获取entity的内容。 获取的内容是复制的对象,属性值是调用该方法时的属性值。
*/
Object copyContent();

//endregion

//region persisting flag

/**
* 是否正在进行持久化
*/
boolean isPersisting();

/**
* 设置正在持久化标志
*/
void setPersistingFlag();

/**
* 清除正在持久化标志
*/
void clearPersistingFlag();

//endregion

//region persist state

/**
* 设置为脏数据状态
*/
void setDirtyState();

/**
* 清除脏数据状态
*/
void clearDirtyState();

/**
* 当前持久化状态。
*
* @see PersistState
*/
PersistState currentPersistState();

//endregion

//region get/set field

/**
* 获取属性值。
*/
Object getField(String fieldName);

/**
* 设置属性值。
*/
void setField(String fieldName, Object value);

/**
* 设置多个属性的值。
*/
void setFields(List<EntityField> fields);

/**
* 增加int类型属性的值。
*/
void addInt(String fieldName, int delta);

/**
* 增加long类型属性的值。
*/
void addLong(String fieldName, long delta);

//endregion

//region manage dirty field

/**
* 标记脏数据字段
*/
void addDirtyField(String fieldName);

/**
* 获取修改过的属性。
*/
List<EntityField> getAndClearDirtyFields();

//endregion

//region wrapper implement

/**
* 返回id的属性名。
*/
String getIdFieldName();

/**
* 返回id
*/
String getId();

/**
* 返回DATA的class
*/
Class getDataClass();

//endregion
}

分类

DDL解决的是数据频繁更改的问题,其实这里的更改说的并不准确,并不仅仅只是update,还有insert。用过mongodb的应该清楚有一种叫upsert的操作,就是找到就修改,找不到就添加。我们这里就需要将我们的数据分成两类:Detachable(可拆分的)、Nondetachable(不可拆分的)

可拆分的,就意味着你针对这个数据的修改最小可以精确到其中的一个属性,项目中大多数都属于这种情况。

不可拆分的,即每次都是以一个整体添加,比如一次交易,每次添加都是一个整体,不可能说你先提交买方,再提交卖方,后面还会修改买方。这种类型大多都是一条记录,整体存入数据库。

因此,我们来定义一下这两种结构:
可拆分的类型:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
public abstract class DetachableDirtyEntityAdapter implements IDirtyEntity {

private static final Logger log = LoggerFactory.getLogger(DetachableDirtyEntityAdapter.class);

/**
* 数据属性的map引用
*/
private BeanMap beanMap;

private final BeanCopier beanCopier;


public DetachableDirtyEntityAdapter(Object content, BeanCopier beanCopier) {
Preconditions.checkNotNull(content);
Preconditions.checkNotNull(beanCopier);

this.content = newEmptyContentInstance();
this.beanCopier = beanCopier;
this.beanCopier.copy(content, this.content, null);
this.beanMap = BeanMap.create(this.content);
}

//region manage content

/**
* 数据的内容。
*/
private Object content;

@Override
public Object getContent() {
return content;
}

private Object newEmptyContentInstance() {
Class cls = getDataClass();
try {
return cls.newInstance();
} catch (Exception e) {
log.error("initiate {} failed: {}", cls.getSimpleName(), e.getMessage());
return null;
}
}

@Override
public synchronized Object copyContent() {

Object copy = newEmptyContentInstance();
beanCopier.copy(this.content, copy, null);
return copy;
}

//endregion

//region persisting flag

private volatile boolean persisting = false;

@Override
public boolean isPersisting() {
return persisting;
}

@Override
public void setPersistingFlag() {
this.persisting = true;
}

@Override
public void clearPersistingFlag() {
this.persisting = false;
}

//endregion

//region persist state

@Override
public void setDirtyState() {
throw new UnsupportedOperationException();
}

@Override
public void clearDirtyState() {
throw new UnsupportedOperationException();
}

@Override
public synchronized PersistState currentPersistState() {
int dirtySize = dirtyFieldNames.size();

if (dirtySize == 0) {
return PersistState.PERSISTED;
} else {
return PersistState.DIRTY;
}
}

//endregion

//region get/set field

@Override
public synchronized Object getField(String fieldName) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

return beanMap.get(fieldName);
}

@Override
public synchronized void setField(String fieldName, Object value) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

beanMap.put(fieldName, value);
dirtyFieldNames.add(fieldName);
}

@Override
public synchronized void setFields(List<EntityField> fields) {
Preconditions.checkNotNull(fields);

for (EntityField f : fields) {
beanMap.put(f.getName(), f.getValue());
dirtyFieldNames.add(f.getName());
}
}

@Override
public synchronized void addInt(String fieldName, int delta) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

int origin = (int) beanMap.get(fieldName);
beanMap.put(fieldName, origin + delta);
dirtyFieldNames.add(fieldName);
}

@Override
public synchronized void addLong(String fieldName, long delta) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

long origin = (long) beanMap.get(fieldName);
beanMap.put(fieldName, origin + delta);
dirtyFieldNames.add(fieldName);
}

//endregion

//region manage dirty fields

/**
* 当前entity的包含脏数据的属性名列表。
*/

private final HashSet<String> dirtyFieldNames = new HashSet<>(16);

@Override
public void addDirtyField(String fieldName) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));
dirtyFieldNames.add(fieldName);
}

@Override
public synchronized List<EntityField> getAndClearDirtyFields() {
ArrayList<EntityField> list = new ArrayList<>();

for (String f : dirtyFieldNames) {
list.add(new EntityField(f, beanMap.get(f)));
}

// 清空dirtyFieldNames, 记录上一次持久化的事件
dirtyFieldNames.clear();
return list;
}

//endregion
}

不可拆分的类型:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
public abstract class NonDetachableDirtyEntityAdapter implements IDirtyEntity {

private static final Logger log = LoggerFactory.getLogger(NonDetachableDirtyEntityAdapter.class);

/**
* 数据属性的map引用
*/
private BeanMap beanMap;

private final BeanCopier beanCopier;


public NonDetachableDirtyEntityAdapter(Object content, BeanCopier beanCopier) {
Preconditions.checkNotNull(content);
Preconditions.checkNotNull(beanCopier);

this.content = newEmptyContentInstance();
this.beanCopier = beanCopier;
this.beanCopier.copy(content, this.content, null);
this.beanMap = BeanMap.create(this.content);
}

//region manage content

/**
* 数据的内容。
*/
private Object content;

@Override
public Object getContent() {
return content;
}

private Object newEmptyContentInstance() {
Class cls = getDataClass();
try {
return cls.newInstance();
} catch (Exception e) {
log.error("initiate {} failed: {}", cls.getSimpleName(), e.getMessage());
return null;
}
}

@Override
public synchronized Object copyContent() {

Object copy = newEmptyContentInstance();
beanCopier.copy(this.content, copy, null);
return copy;
}

//endregion

//region persisting flag

private volatile boolean persisting = false;

@Override
public boolean isPersisting() {
return persisting;
}

@Override
public void setPersistingFlag() {
this.persisting = true;
}

@Override
public void clearPersistingFlag() {
this.persisting = false;
}

//endregion

//region persist state

private volatile PersistState persistState = PersistState.DIRTY;

@Override
public void setDirtyState() {
persistState = PersistState.DIRTY;
}

@Override
public void clearDirtyState() {
persistState = PersistState.PERSISTED;
}

@Override
public PersistState currentPersistState() {
return persistState;
}

//endregion

//region get/set field

@Override
public synchronized Object getField(String fieldName) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

return beanMap.get(fieldName);
}

@Override
public synchronized void setField(String fieldName, Object value) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

beanMap.put(fieldName, value);
}

@Override
public synchronized void setFields(List<EntityField> fields) {
Preconditions.checkNotNull(fields);

for (EntityField f : fields) {
beanMap.put(f.getName(), f.getValue());
}
}

@Override
public synchronized void addInt(String fieldName, int delta) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

int origin = (int) beanMap.get(fieldName);
beanMap.put(fieldName, origin + delta);
}

@Override
public synchronized void addLong(String fieldName, long delta) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

long origin = (long) beanMap.get(fieldName);
beanMap.put(fieldName, origin + delta);
}

//endregion

//region manage dirty fields
@Override
public void addDirtyField(String fieldName) {
throw new UnsupportedOperationException();
}

@Override
public synchronized List<EntityField> getAndClearDirtyFields() {
throw new UnsupportedOperationException();
}

//endregion
}

两种类型最大的不同在于真正往数据库中存储时,前者是可以单独字段存储,后者是整体存储,因此最后和DirtyField相关的操作便需要注意,NondetachableDirtyEntityAdapter不需要记录DirtyFields。

针对原本类中属性的复制和存储,我这儿用的是spring提供的BeanCopier,如果你有什么更高效的工具,欢迎在下方留言。(我一直在找一种深度克隆高效的组件,试过kryo,但只有在实现序列化接口的前提下,其效率才能和正常的set/get大概相差10倍,如果有好的组件,希望一并告知)。

以上就是DDL的准备工作,其实后面的工作就是将具体的类做一个封装,再封装针对该类的所有操作,然后另写一个线程组执行往数据库的写入操作。这个工作其实针对各个项目都有其特殊的地方,LZ在这儿就不具体展示了,有兴趣的话大家可以在下方留言。

健健 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
如果您感觉文章不错,也愿意支持一下作者的话