MyBatis延迟加载的简易实现

原理

其实延迟加载的实现原理很简单,其实就是对方法进行拦截

假设现在有两个表格kfwallpaper,这两个表存在关联关系(这里是我强行绑定的),通过注解@Collection来进行关系的标注

kf 表格对应的java对象

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
@Table(name = "kf")
public class Kf {

@Id(name = "kf_id")
@Column(name = "kf_id")
private int id;
@Column(name = "kf_name")
private String kfName;
@Column(name = "kf_account")
private String kfAccount;
@Column(name = "kf_qcard")
private String kfQcard;
@Collection(sub = WallPaper.class)
private List<WallPaper> wallPapers;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getKfName() {
return kfName;
}

public void setKfName(String kfName) {
this.kfName = kfName;
}

public String getKfAccount() {
return kfAccount;
}

public void setKfAccount(String kfAccount) {
this.kfAccount = kfAccount;
}

public String getKfQcard() {
return kfQcard;
}

public void setKfQcard(String kfQcard) {
this.kfQcard = kfQcard;
}

public List<WallPaper> getWallPapers() {
return wallPapers;
}

public void setWallPapers(List<WallPaper> wallPapers) {
this.wallPapers = wallPapers;
}

@Override
public String toString() {
return "Kf{" +
"id=" + id +
", kfName='" + kfName + '\'' +
", kfAccount='" + kfAccount + '\'' +
", kfQcard='" + kfQcard + '\'' +
", wallPapers=" + wallPapers +
'}';
}
}

wallpaper 表格对应的java对象

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
@Table(name = "wallpaper")
public class WallPaper {

@Id(name = "id")
@Column(name = "id")
private int id;
@Column(name = "back_image_url")
private String backImageUrl;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getBackImageUrl() {
return backImageUrl;
}

public void setBackImageUrl(String backImageUrl) {
this.backImageUrl = backImageUrl;
}

@Override
public String toString() {
return "WallPaper{" +
"id=" + id +
", backImageUrl='" + backImageUrl + '\'' +
'}';
}
}

然后想在查询kf的时候把对应的wallpaper查询出来,这个时候,就需要做一些小小的操作,我们将数据库查询出来的kf表格的数据,不再直接简单的分装成Kf对象,而是采用CGLib字节码库,对Kf对象进行代理,生成一个Kf的代理对象返回给用户,这样,当用户需要执行kfgetWallPapers时,会被拦截,这个时候我们就可以在这里继续加载被关联表格的数据,然后将查询出来的数据装入obj中,

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
@SuppressWarnings("unchecked")
public T newInstance(Map<String, Object> values) {
final Map<String, Field> columnFieldMapper = this.columnFieldMapper;
T t = null;
try {
// 判断时候存在@Collection注解或者@Association注解,如果存在,返回一个代理对象,进行延迟加载
if (hasCascadeRelations()) {
t = (T) CglibProxy.getProxy(tableCls);
for (Map.Entry<Field, CascadeRelations> entry : cascadeMapper.entrySet()) {
Field field = entry.getKey();
field.setAccessible(true);
field.set(t, CglibProxy.getProxy(field.getType()));
}
} else {
t = tableCls.newInstance();
}
for (Map.Entry<String, Object> entry : values.entrySet()) {
Field field = columnFieldMapper.get(entry.getKey());
field.setAccessible(true);
field.set(t, entry.getValue());
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return t;
}

延迟加载实现

对方法进行拦截,当拦截到get方法时,通过方法获取对应的Field信息,对Field进行判断,如果Field是另一个Table的话,就进行数据查询

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
@SuppressWarnings("unchecked")
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object invokeResult = proxy.invokeSuper(obj, args);
String methodName = method.getName();
if (invokeResult == null) {
return null;
} else if (methodName.startsWith("get")) {
Class<?> invokeResultClass = invokeResult.getClass();
Class<?> superClass = invokeResultClass.getSuperclass();
if (invokeResultClass.equals(superClass)) {
return invokeResult;
}
Field field = getFieldBy(method);
field.setAccessible(true);
CascadeRelations cascadeRelations = CascadeRelations.type(field);
// 判断是否被相关注解所注解,没有的话直接返回原方法执行结果
if (cascadeRelations == null) {
return invokeResult;
}
Class<?> relationClass;
if (cascadeRelations == CascadeRelations.ASSOCIATION) {
relationClass = field.getAnnotation(Association.class).sub();
} else {
relationClass = field.getAnnotation(Collection.class).sub();
}
// 根据注解的值找到对应的Table信息进行数据库查询
Sql sql = new Select().getSql(relationClass);
// 数据库信息查询出来后根据sql对象以及期望的包装类型`Class<Wrap>`对数据结果进行对象话(这里直接采用了Map容器)
List list = SqlSessionFactory.getInstance().openSession().getJdbcTemplate().selectByRelation(sql, Map.class);
field.set(obj, list);
return list;
}
return invokeResult;
}

不足

目前该代码的实现很粗糙,其实只是探究了MyBatis延迟加载的原理而已,还有很多数据库细节还没有去完整的考虑