项目实战四-元数据模块-省份、城市
省份、城市
- 经分析省份跟城市可以放在一张表中,因此将之前的做的省份模板删除:po、service、query、controller层,都删了
-
省份与城市共同的表叫做plate_region
//省份、城市放在同一张表格,城市的parent_id指向省份的id id name plate pingyin parent_id 3 广东 粤 GUANG_DONG 0 4 福建 闽 FU_JIAN 0 5 广州 A guangzhou 3
- 通过EasyCode-MybatisCodeHepler生成对应层的类
- 生成的类有po(PlateRegion)、mapper(PlateRegionMapper)、service(PlateRegionService/PlateRegionServiceImpl)、controller(PlateRegionController)
- 各层的代码示例
-
PlateRegion
//com.zh.jk.pojo.po @Data public class PlateRegion { /** * 主键 */ private Integer id; /** * 名称 */ private String name; /** * 车牌 */ private String plate; /** * 拼音 */ private String pinyin; private Integer parentId; }
-
query
//com.zh.jk.pojo.query //CityQuery @EqualsAndHashCode(callSuper = true) @Data public class CityQuery extends KeywordQuery { /** * 省份id */ private Integer parentId; } //ProvinceQuery public class ProvinceQuery extends KeywordQuery {}
-
mapper
//com.zh.jk.mapper public interface PlateRegionMapper extends BaseMapper<PlateRegion> {}
-
service
//com.zh.jk.service //PlateRegionService public interface PlateRegionService extends IService<PlateRegion> { //分页查询所有省份 void listProvinces(ProvinceQuery query); //分页查询所有城市 void listCities(CityQuery query); //不分页查询所有省份 List<PlateRegion> listProvinces(); } ////com.zh.jk.service.impl //PlateRegionServiceImpl @Service @Transactional public class PlateRegionServiceImpl extends ServiceImpl<PlateRegionMapper, PlateRegion> implements PlateRegionService { @Override @Transactional(readOnly = true) public void listProvinces(ProvinceQuery query) { MpQueryWrapper<PlateRegion> wrapper = new MpQueryWrapper<>(); wrapper.like(query.getKeyword(), PlateRegion::getName, PlateRegion::getPlate, PlateRegion::getPinyin); //查询所有的省份,getParentId都是0的,都是省份 wrapper.eq(PlateRegion::getParentId,0); wrapper.orderByDesc(PlateRegion::getId); baseMapper.selectPage(new MpPage<>(query),wrapper).updateQuery(); } @Override @Transactional(readOnly = true) public void listCities(CityQuery query) { MpQueryWrapper<PlateRegion> wrapper = new MpQueryWrapper<>(); wrapper.like(query.getKeyword(), PlateRegion::getName, PlateRegion::getPlate, PlateRegion::getPinyin); Integer provinceId = query.getParentId(); if (provinceId != null && provinceId >0 ){ //查询指定省份的城市 wrapper.eq(PlateRegion::getParentId,provinceId); }else { //查询所有省份的城市,getParentId != 0 wrapper.ne(PlateRegion::getParentId,0); } wrapper.orderByDesc(PlateRegion::getId); baseMapper.selectPage(new MpPage<>(query),wrapper).updateQuery(); } @Override @Transactional(readOnly = true) public List<PlateRegion> listProvinces() { MpQueryWrapper<PlateRegion> wrapper = new MpQueryWrapper<>(); wrapper.eq(PlateRegion::getParentId,0); wrapper.orderByDesc(PlateRegion::getPinyin); return baseMapper.selectList(wrapper); } }
-
Mybaits-plus 默认方法增强(重点理解!!!)
-
编辑一个省份信息时,保存的只有省份名称、车牌,并没有保存省份的拼音,那么数据库中pinyin字段肯定是空值,那么也就无法实现拼音的搜索
- 如何实现汉字自动转化成拼音,而且还能自动保存到数据库中呢?
-
当前保存使用的是BaseController的save方法
@PostMapping("/save") public R save(T entity){ if (getService().saveOrUpdate(entity)){ return Rs.ok("保存成功"); }else { throw new CommonException("保存失败"); } }
- 该方法是使用service的saveOrUpdate方法,而PlateRegionServiceImpl继承自ServiceImpl
- ServiceImpl为mybitis自己封装的类,里面有常用的sql方法,其中包含saveOrUpdate
-
ServiceImpl的saveOrUpdate分析
public boolean saveOrUpdate(T entity) { if (null == entity) { return false; } else { ... return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity); } }
- 可以看到本质是调用了this.updateById、this.save,这两个方法
- 而且PlateRegionServiceImpl类是继承了ServiceImpl这个类,因此可以想到在子类中重写这2个方法,做个方法的增强
-
改造PlateRegionServiceImpl类
//重写父类方法,手动实现拼音的存储,到数据库 @Override public boolean save(PlateRegion entity) { //手动处理将汉字转为拼音,然后赋值给PlateRegion的pinyin属性 processPinyin(entity); return super.save(entity); } @Override public boolean updateById(PlateRegion entity) { //手动处理将汉字转为拼音,然后赋值给PlateRegion的pinyin属性 processPinyin(entity); return super.updateById(entity); }
- 如何实现汉字转拼音呢?
-
pom.xml添加依赖tinypinyin
<!-- 将汉字转为拼音--> <dependency> <groupId>com.github.promeg</groupId> <artifactId>tinypinyin</artifactId> <version>${tinypinyin.version}</version> </dependency>
-
PlateRegionServiceImpl封装方法
private void processPinyin(PlateRegion region) { String name = region.getName(); if (name == null) return; //将汉字转为拼音 //使用第三方库,tinypinyin //guang_dong region.setPinyin(Pinyin.toPinyin(name,"_")); }
-
数据的一致性
- 一张表的数据跟多张表有关联,如果删除这张表的某条数据,那么它所关联的其他表的数据也要删除,这就是数据的一致性,比如:省份表,每个省份下面有n个城市,如果删除了某个省份,那么这个省份下的所有城市也应该删除
- 解决方法:
- 方法一: 通过数据库的外键解决,本质是数据库底层直接实现,但是这种方法比较耗性能,目前市场不再使用
- 方法二: 通过应用层面来解决
- 通过应用层面实现数据一致性的思路
- 自定义注解,注解每个有关联po对象(每个po对应一张表)的属性,然后扫描所有的po对象
- 通过AOP拦截service层所有的impl实现类的remove方法,然后在remove方法中进行分析
新增类、字符串处理工具类
-
类的处理工具Classes
//com.zh.jk.common.util public class Classes { /** * 返回第一个不是Object.class的类 */ public static Class<?> notObject(Class<?>... sources) { if (sources == null) return null; for (Class<?> source : sources) { if (!source.equals(Object.class)) return source; } return null; } /** * 获取cls类中的fieldName属性 */ public static Field getField(Class<?> cls, String fieldName) throws Exception { return enumerateFields(cls, (field, curCls) -> { if (field.getName().equals(fieldName)) return Stop.create(field); return null; }); } /** * 遍历cls的所有属性 */ public static <T> T enumerateFields(Class<?> cls, FieldConsumer<T> fieldConsumer) throws Exception { if (fieldConsumer == null || cls == null) return null; Class<?> curCls = cls; while (!curCls.equals(Object.class)) { for (Field field : curCls.getDeclaredFields()) { Stop<T> stop = fieldConsumer.accept(field, curCls); if (stop != null) return stop.getData(); } curCls = curCls.getSuperclass(); } return null; } public interface FieldConsumer<T> { Stop<T> accept(Field field, Class<?> ownerCls) throws Exception; } }
-
字符串的处理工具Strings
//com.zh.jk.common.util public class Strings { private static final int DELTA = 'a' - 'A'; public static boolean isEmpty(String source) { return source == null || source.equals(""); } /** * 首字母变小写 * @return TestCase -> testCase */ public static String firstLetterLowercase(String source) { if (isEmpty(source)) return source; StringBuilder res = processFirstLetterLowercase(source); int len = source.length(); for (int i = 1; i < len; i++) { res.append(source.charAt(i)); } return res.toString(); } private static StringBuilder processFirstLetterLowercase(String source) { StringBuilder res = new StringBuilder(); // 拼接首字符 char firstChar = source.charAt(0); if (isBigLetter(firstChar)) { res.append((char) (firstChar + DELTA)); } else { res.append(firstChar); } return res; } /** * 驼峰 -> 下划线 * @return TestCase -> test_case */ public static String camel2underline(String source) { if (isEmpty(source)) return source; StringBuilder res = processFirstLetterLowercase(source); // 其他字符 int len = source.length(); for (int i = 1; i < len; i++) { char c = source.charAt(i); if (isBigLetter(c)) { res.append("_"); res.append((char) (c + DELTA)); } else { res.append(c); } } return res.toString(); } /** * 下划线 -> 小驼峰 * @return test_case -> testCase */ public static String underline2smallCamel(String source) { return underline2camel(source, false); } /** * 下划线 -> 大驼峰 * @return test_case -> TestCase */ public static String underline2bigCamel(String source) { return underline2camel(source, true); } private static String underline2camel(String source, boolean big) { if (isEmpty(source)) return source; StringBuilder res = new StringBuilder(); // 其他字符 int len = source.length(); // 上一个字符是下划线 boolean prevUnderline = false; for (int i = 0; i < len; i++) { char c = source.charAt(i); if (c == '_') { prevUnderline = true; continue; } if (res.length() == 0) { // 首字符 if (big && isSmallLetter(c)) { // 大驼峰 res.append((char) (c - DELTA)); } else if (!big && isBigLetter(c)) { // 小驼峰 res.append((char) (c + DELTA)); } else { res.append(c); } } else if (prevUnderline && isSmallLetter(c)) { res.append((char) (c - DELTA)); } else { res.append(c); } prevUnderline = false; } return res.toString(); } public static boolean isBigLetter(char source) { return source >= 'A' && source <= 'Z'; } public static boolean isSmallLetter(char source) { return source >= 'a' && source <= 'z'; } /** * 返回第一个不为empty的字符串 */ public static String notEmpty(String... sources) { if (sources == null) return null; for (String source : sources) { if (!isEmpty(source)) return source; } return null; } }
-
enhance中新增一个增强类Stop
//com.zh.jk.common.enhance @Data public class Stop<T> { private T data; public static <T> Stop<T> create() { return new Stop<>(); } public static <T> Stop<T> create(T data) { Stop<T> stop = new Stop<>(); stop.setData(data); return stop; } }
自定义注解
-
新建一个枚举ForeignCascade
//com.zh.jk.common.foreign.anno //设置一个枚举,用于设置子表的某条数据删除,主表对应的那条数据是否也删除,DEFAULT不删除,DELETE直接删除 public enum ForeignCascade { //默认不可以删、可直接删除 DEFAULT, DELETE }
-
修饰外键字段注解:ForeignField,即一个属性被该注解修饰,说明该字段被其他表引用
//com.zh.jk.common.foreign.anno //生成文档使用,标明是否生成javadoc文档,不重要 @Documented /** * 注解的保留策略,注解必须有保留策略:SOURCE/ClASS/RUNTIME * SOURCE 文件是java源码阶段---磁盘中 * ClASS 源码文件被编译成class字节码时----磁盘中 * RUNTIME 应用程序启动被加载到运行内存时----运行内存中 * 因为别的代码要读取某个注解肯定是从运行内存中读取,因此是RUNTIME */ @Retention(RetentionPolicy.RUNTIME) // 当前注解被应用到哪个地方:类、属性、方法,还是其他 @Target(ElementType.FIELD) @Repeatable(ForeignField.ForeignFields.class) public @interface ForeignField { /** * 当前这个字段的类型是什么 */ Class<?> value() default Object.class; /** * 当前这个字段被引用的主表类型是什么 */ Class<?> mainTable() default Object.class; /** * 被引用的主表类对应的属性名是什么,默认值是id */ String mainField() default "id"; /** * 当前这个属性在数据库中的字段名是什么 */ String column() default ""; /** * 级联类型,是否是关联性删除,默认不删除 */ ForeignCascade cascade() default ForeignCascade.DEFAULT; /** * 定义注解类型,目的是为了在一个类、属性、方法 上多次使用ForeignField注解 * 比如:一个类可以多多个字段使用ForeignField注解 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface ForeignFields { /** * 被引用的主表类 */ ForeignField[] value() default {}; } }
-
修饰包含外键属性的类注解:ForeignTable,即一个类被该注解修饰,则该类中有字段包含外键
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ForeignTable { /** * 数据库默认表名 */ String value() default ""; /** * 对应数据库表名 */ String name() default ""; }
自定义增强类
-
外键属性对应的类:ForeignFieldInfo
//com.zh.jk.common.foreign.info /** * 该类用来实例化一个“属性”的所有信息:字段属性、列名、哪张表、 */ @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) { field.setAccessible(true); this.field = field; } public void addSubField(ForeignFieldInfo subField) { if (subFields == null) { subFields = new ArrayList<>(); } else if (subFields.contains(subField)) { return; } subFields.add(subField); } public void addMainField(ForeignFieldInfo mainField) { if (mainFields == null) { mainFields = new ArrayList<>(); } else if (mainFields.contains(mainField)) { return; } mainFields.add(mainField); } }
-
拥有外键属性对应表对应的类:ForeignTableInfo
//com.zh.jk.common.foreign.info /** * 该类用来实例化一个“表”的所有信息,表对应的类、表名、外键是什么 */ @Getter @Setter public class ForeignTableInfo { // 表对应的类:ForeignTableInfo private static final Map<Class<?>, ForeignTableInfo> cache = new HashMap<>(); //当前表对应的类 private Class<?> cls; //当前表对应的表名 private String table; //当前表对应的外键,当前表是主表,哪个字段被其他表引用。 private ForeignFieldInfo mainField; /** * key是field的属性名 value是ForeignFieldInfo * 当前表是子表,存放着表中所有的外键字段 */ private Map<String, ForeignFieldInfo> subFields = new HashMap<>(); /** * 根据一个表对应的类,获取到这个类的ForeignTableInfo对象 */ public static ForeignTableInfo get(Class<?> tableCls) { return get(tableCls, false); } /** * 从缓存中取出table * @param newIfAbsent 如果找不到就新建一个 */ public static ForeignTableInfo get(Class<?> tableCls, boolean newIfAbsent) { if (!newIfAbsent) return cache.get(tableCls); return cache.computeIfAbsent(tableCls, k -> { ForeignTableInfo table = new ForeignTableInfo(); // 类 table.setCls(tableCls); // 表名 ForeignTable tableAnno = tableCls.getAnnotation(ForeignTable.class); String tableName; if (tableAnno != null) { tableName = Strings.notEmpty(tableAnno.name(), tableAnno.value()); } else { tableName = Strings.camel2underline(tableCls.getSimpleName()); } table.setTable(tableName); return table; }); } /** * 根据一个 外键属性 Field 封装成一个ForeignFieldInfo对象 * 当前表是主表 */ public ForeignFieldInfo getMainField(Field field) { if (mainField == null) { mainField = new ForeignFieldInfo(); mainField.setTable(this); mainField.setField(field); mainField.setColumn(getFieldColumn(field)); //判断当前字段是否被注解 ForeignField ff = field.getAnnotation(ForeignField.class); if (ff != null) { //被注解,要设置它是否级联删除 mainField.setCascade(ff.cascade()); } else { //没有备注接,不删除 mainField.setCascade(ForeignCascade.DEFAULT); } } return mainField; } /** * 根据一个属性Field封装成一个ForeignFieldInfo对象 * 当前表是子表 */ public ForeignFieldInfo getSubField(Field field) { String fieldName = field.getName(); return subFields.computeIfAbsent(fieldName, k -> { ForeignFieldInfo subField = new ForeignFieldInfo(); subField.setTable(this); subField.setField(field); subField.setColumn(getFieldColumn(field)); return subField; }); } /** * 根据 类的属性Field获取到 表中的列名称 */ private String getFieldColumn(Field field) { ForeignField ff = field.getAnnotation(ForeignField.class); //1. 如果有特别注解,那么直接从注解中获取 if (ff != null) { String column = ff.column(); if (!Strings.isEmpty(column)) return column; } //2. 如果没有注解,那么这个数据库中的列名就是驼峰转下划线 return Strings.camel2underline(field.getName()); } }
po类添加注解
-
DictItem
//子表 @Data //当前这张表是什么,由于类与表的转化默认的就是驼峰转下划线,所以这句可以不写 //@ForeignTable(name = "dict_item") public class DictItem { /** * 主键 */ private Integer id; /** * 名称 */ private String name; /** * 值 */ private String value; /** * 排列顺序,默认0。值越大,就排在越前面 */ private Integer sn; /** * 是否禁用。0代表不禁用(启用),1代表禁用 */ private Short disabled; /** * 所属的类型 * 当前表中的字段是type_id,是引用dict_type表中的id主键 * //一个id引用多张表,也支持 @ForeignField(DictType.class) @ForeignField(Other.class) 等价于 @ForeignField({ @ForeignField(DictType.class) @ForeignField(Other.class) }) */ //当前这张表引用的是哪张表,即主表mainTable是谁,引用主表的那个字段属性mainField,column当前这个字段在数据库中的字段是什么 // @ForeignField(mainTable = DictType.class,mainField = "id",column = "type_id") //由于mainTable、mainField与默认值一样,因此mainTable、mainField可以不写 @ForeignField(DictType.class) private Integer typeId; }
-
DictType
//主表 @Data //当前这张表是什么 //@ForeignTable(name = "dict_type") public class DictType { /** * 主键 */ //表明这个属性被其他表引用着,在数据库中的字段是id,而且如果其他表数据删了,这个表数据可以删除DELETE,DEFAULT代表必能删除 //属性默认转表字段是原值或者下划线转驼峰、cascade使用DEFAULT,因此下面这句可以不写 //@ForeignField(column = "id",cascade = ForeignCascade.DEFAULT) private Integer id; /** * 名称 */ private String name; /** * 值 */ private String value; /** * 简介 */ private String intro; }
-
PlateRegion
@Data public class PlateRegion { /** * 主键 */ private Integer id; /** * 名称 */ private String name; /** * 车牌 */ private String plate; /** * 拼音 */ private String pinyin; //城市引用着省份的id @ForeignField(PlateRegion.class) private Integer parentId; }
使用AOP切面编程
-
pom.xml中引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
foreign包下新建切入类
//com.zh.jk.common.foreign @Aspect @Component public class ForeignAspect implements InitializingBean { private static final String FOREIGN_SCAN = "classpath*:com/zh/jk/pojo/po/**/*.class"; @Autowired private ApplicationContext ctx; @Autowired private ResourceLoader resourceLoader; // 拦截service层的所有删除操作,删除省份,是否删除省份对应的城市 @Around("execution(* com.zh.jk.service..*.remove*(..))") public Object handleRemove(ProceedingJoinPoint point) throws Throwable { Object target = point.getTarget(); if (!(target instanceof IService)) return point.proceed(); // 获取PO类 Class<?> poCls = ((IService<?>) target).getEntityClass(); // 根据po类拿到表对象 ForeignTableInfo table = ForeignTableInfo.get(poCls); if (table == null) return point.proceed(); // 主键,拿到这个表的引用Field ForeignFieldInfo mainField = table.getMainField(); //如果为空,说明当前表没有关联的外键,或者当前表是子表,至二级返回 if (mainField == null) return point.proceed(); // 获取外键约束,根据这个主表的字段拿到引用该字段的所有字段(每个字段对应一张表) List<ForeignFieldInfo> subFields = mainField.getSubFields(); if (CollectionUtils.isEmpty(subFields)) return point.proceed(); // 获取参数值 Object arg = point.getArgs()[0]; List<Object> ids; if (arg instanceof List) { //1. 批量删除省份 ids = (List<Object>) arg; } else { ids = new ArrayList<>(); //2. 单个删除省份 ids.add(arg); } //遍历所有的子表 for (ForeignFieldInfo subField : subFields) { //根据子表字段获取到当前表的表类 ForeignTableInfo subTable = subField.getTable(); //获取到当前表对应的mapper BaseMapper<Class<?>> mapper = getMapper(subTable.getCls()); //查询出这n个省份的ids对应的所有市数据 QueryWrapper<Class<?>> wrapper = new QueryWrapper<>(); wrapper.in(subField.getColumn(), ids); if (mainField.getCascade() == ForeignCascade.DEFAULT) { // 默认 Integer count = mapper.selectCount(wrapper); if (count == 0) continue; Rs.raise(String.format("有%d条【%s】数据相关联,无法删除!", count, subTable.getTable())); } else { // 删除关联数据 mapper.delete(wrapper); } } return point.proceed(); } // 拦截service层的所有添加操作,添加城市,如果插入的城市没有对应的省份,报错提示 @Around("execution(* com.zh.jk.service..*.save*(..)) || execution(* com.zh.jk.service..*.update*(..)) ") public Object handleSaveOrUpdate(ProceedingJoinPoint point) throws Throwable { Object target = point.getTarget(); if (!(target instanceof IService)) return point.proceed(); // 获取po对象 Class<?> poCls = ((IService<?>) target).getEntityClass(); // 表格,获取到子表类 ForeignTableInfo table = ForeignTableInfo.get(poCls); if (table == null) return point.proceed(); // 获取外键约束:根据子表类获取到该子表所有引用的外键字段,可能有n个外键,引用n张主表 Collection<ForeignFieldInfo> subFields = table.getSubFields().values(); if (CollectionUtils.isEmpty(subFields)) return point.proceed(); // 参数:判断参是否是当前的po类 Object model = point.getArgs()[0]; if (model.getClass() != poCls) { return point.proceed(); } // 遍历外键约束 for (ForeignFieldInfo subField : subFields) { //根据当前子表的所有外键字段拿到所有主表的表字段 List<ForeignFieldInfo> mainFields = subField.getMainFields(); if (CollectionUtils.isEmpty(mainFields)) continue; // 引用的主键超过1个,无法智能处理,需要手动处理 if (mainFields.size() > 1) continue; Object subValue = subField.getField().get(model); // 跳过空值(代表此字段不进行更新) if (subValue == null) continue; // 唯一的一个主键 ForeignFieldInfo mainField = mainFields.get(0); BaseMapper<Class<?>> mapper = getMapper(mainField.getTable().getCls()); QueryWrapper<Class<?>> wrapper = new QueryWrapper<>(); wrapper.eq(mainField.getColumn(), subValue); if (mapper.selectCount(wrapper) == 0) { Rs.raise(String.format("%s=%s不存在", subField.getColumn(), subValue)); } } return point.proceed(); } /** * 在spring的bean的生命周期中,实例化->生成对象->属性填充后会进行afterPropertiesSet方法 * 1. 通过加载器获取到FOREIGN_SCAN 下的所有资源 * 2. 读取出所有资源对应的类名 */ @Override public void afterPropertiesSet() throws Exception { ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); Resource[] rs = resolver.getResources(FOREIGN_SCAN); if (rs.length == 0) { Rs.raise("FOREIGN_SCAN配置错误,找不到任何类信息"); } MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourceLoader); for (Resource r : rs) { parseCls(readerFactory.getMetadataReader(r).getClassMetadata().getClassName()); } } private BaseMapper<Class<?>> getMapper(Class<?> poCls) { return (BaseMapper<Class<?>>) ctx.getBean(Strings.firstLetterLowercase(poCls.getSimpleName()) + "Mapper"); } /** * 遍历出一个po类对应的所有带ForeignField注解的属性 * 1. 并根据po类创建ForeignTableInfo实例并保存到ForeignTableInfo的cache中去 */ private void parseCls(String clsName) throws Exception { //获取po类 Class<?> subCls = Class.forName(clsName); //根据po类创建对应的表类ForeignTableInfo,本质会存储到ForeignTableInfo的cache成员中 ForeignTableInfo subTable = ForeignTableInfo.get(subCls, true); // 遍历这个po类所有的属性 Classes.enumerateFields(subCls, (subField, curCls) -> { //获取到某个属性对应的ForeignField注解 ForeignField ff = subField.getAnnotation(ForeignField.class); parseForeignField(subTable, subField, ff); //获取到某个属性对应的ForeignFields注解 ForeignFields ffs = subField.getAnnotation(ForeignFields.class); if (ffs == null) return null; for (ForeignField subFf : ffs.value()) { parseForeignField(subTable, subField, subFf); } return null; }); } /** * * @param subTable 表类ForeignTableInfo * @param subField 带标注的字段 * @param ff 标注内容 * @throws Exception */ private void parseForeignField(ForeignTableInfo subTable, Field subField, ForeignField ff) throws Exception { // 跳过没有ForeignField注解的属性 if (ff == null) return; // 主表的类 Class<?> mainCls = Classes.notObject(ff.mainTable(), ff.value()); // 说明ForeignField注解的是主键属性(因为缺乏mainCls),就是当前这个字段被别人引用,而不是引用别人 if (mainCls == null || mainCls.equals(Object.class)) return; // 主表中的主键属性 Field mainField = Classes.getField(mainCls, ff.mainField()); // 跳过错误(找不到)的主键属性 if (mainField == null) return; //根据主表类获取到主表的表类ForeignTableInfo ForeignTableInfo mainTable = ForeignTableInfo.get(mainCls, true); //根据子表类获取到获取到子表字段类 ForeignFieldInfo subFieldInfo = subTable.getSubField(subField); //根据主表类获取到获取到主表字段类 ForeignFieldInfo mainFieldInfo = mainTable.getMainField(mainField); // 存储到缓存中 // 将主表字段类存储到子表字段类中 subFieldInfo.addMainField(mainFieldInfo); //将子表字段类存储到主表字段类中 mainFieldInfo.addSubField(subFieldInfo); } }
-
运行程序,删除【数据字典类型】中的某条数据,就无法删除,因为这条数据id被数据字典条目中的某些数据引用着了