原理
其实延迟加载的实现原理很简单,其实就是对方法进行拦截
假设现在有两个表格kf
和wallpaper
,这两个表存在关联关系(这里是我强行绑定的),通过注解@Collection
来进行关系的标注
kf 表格对应的 java 对象
@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 +
", kf + kfName + '\'' +
", kfAccount='" + kfAccount + '\'' +
", kfQcard='" + kfQcard + '\'' +
", wallPapers=" + wallPapers +
'}';
}
}
wallpaper 表格对应的 java 对象
@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
的代理对象返回给用户,这样,当用户需要执行kf
的getWallPapers
时,会被拦截,这个时候我们就可以在这里继续加载被关联表格的数据,然后将查询出来的数据装入 obj 中,
@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 的话,就进行数据查询
@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 延迟加载的原理而已,还有很多数据库细节还没有去完整的考虑
来源链接:
https://www.liaochuntao.cn/2019/03/03/java-web-26/