外键约束的应用层维护
1.前言
一般来说 对于不同表格之间的属性约束 我们通常直接使用数据库已经实现好的外键来完成 但是数据库底层实现的外键他的性能很差 这是因为在执行数据库修改操作时 他需要遍历其他所有的表来找出其中可能相关联的属性 一并进行数据库修改(应用层的维护则只需要遍历所有关联外键所在的表) 当然 性能差还有其他很多的原因 这里就提供一个思路 所以要求我们可以通过应用层(业务层)来自行维护一个外键约束
2.实现思路
aop+注解
我们会通过aop去拦截所有形式的数据库修改业务 因为不同形式的数据库修改操作(比如removeById和removeByIds)都有可能影响到其他的约束属性 所以不能够遗漏任何情况 对于任何形式的数据库修改操作而言 都需要对可能存在的约束属性进行检查
同时 也会根据注解来标识外键/主键 同时储存一些相关信息(约束主键所在的表以及属性等等) 注解内部的属性也会提供一些默认值
3.注解实现
1.级联枚举类
所谓级联 其实就是在对某个表格的某条记录执行数据库修改操作时 他的影响会传递给其他所有相关联的记录
有两种策略 一种就是只提醒用户 让用户去做具体决定 另一种就是绕过用户 直接进行当前记录以及相关联的所有记录的数据库修改操作
我们可以设计一个枚举类型来储存有限个的级联策略
/**
* 级联策略 所谓级联 其实就是对某条记录的影响会传递给其他相关联的记录
* DEFAULT表示默认策略 即提示用户 让用户做具体决定
* DELETE表示激进策略 不问用户 直接删除/更新当前记录以及有关联的所有记录
*/
public enum ForeignCascade {
DEFAULT, DELETE
}
2.ForeignField注解
利用该可选注解我们可以用于标识外键属性/主键属性
在真正应用该注解时 如果主表属性为id 就可以直接全部采取默认值 则不需要为主表属性作用该注解 这就是可选注解的理解
/**
* Documented注解在于会将该注解加入到javadoc文档中
* Target注解在于设置该注解的作用类型 比如类、方法、属性等等
* Retention注解在于设置该注解的作用周期 取值为RUNTIME表示可以作用于编译运行完整的阶段
* Repeatable注解作用是该注解可以在同一个地方同时设置多次 并且需要依赖于一个容器储存 本质上 多次使用就相当于在外部封装一层容器
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ForeignField.ForeignFields.class)
public @interface ForeignField {
// 主表类
Class<?> value() default Object.class;
// 主表类
Class<?> mainTable() default Object.class;
// 主表属性名
String mainField() default "id";
// 对应字段
String column() default "";
// 级联策略
ForeignCascade cascade() default ForeignCascade.DEFAULT;
// 容器
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface ForeignFields {
ForeignField[] value() default {};
}
}
3.ForeignTable注解
利用该可选注解我们可以用于标识外键/主键所在表格
/**
* Documented注解的作用在于会将该注解加入到javadoc文档中
* Target注解的作用在于该注解只能作用于类上
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ForeignTable {
// 表名
String value() default "";
// 表名
String name() default "";
}
3.属性信息类/表格信息类实现
1.属性信息类
/**
* 该类是属性信息类
*/
@Getter
@Setter
public class ForeignFieldInfo {
// 当前属性
private Field field;
// 当前属性对应字段
private String column;
// 当前属性所在的表格信息类
private ForeignTableInfo table;
// 引用的属性集合
private List<ForeignFieldInfo> mainFields;
// 被引用的属性集合
private List<ForeignFieldInfo> subFields;
// 级联策略
private ForeignCascade cascade;
// 设置属性 如果属性是私有属性的话 那么就不可以直接由外界访问 这时候 我们应该开放权限
public void setField(Field field) {
// 表明任意权限的变量都可以访问 即使是private也行
field.setAccessible(true);
this.field = field;
}
// 追加引用属性
public void addMainField(ForeignFieldInfo mainField) {
// 如果集合不存在的话 那么就先创建集合 如果参数已经存在的话 那么就不追加 否则的话 就直接追加
if (mainFields == null) {
mainFields = new ArrayList<>();
} else if (mainFields.contains(mainField)) {
return;
}
mainFields.add(mainField);
}
// 追加被引用属性
public void addSubField(ForeignFieldInfo subField) {
// 如果集合不存在的话 先创建集合 如果已经存在 考虑参数是否存在于集合 如果存在 那就不添加 反之则需要添加
if (subFields == null) {
subFields = new ArrayList<>();
} else if (subFields.contains(subField)) {
return;
}
subFields.add(subField);
}
}