数据一致性

简介: 数据一致性


🍀 学习不使用数据库外键的情况下保证有关联的表之间的数据的一致性

一、AOP 动态代理切入方法

(1) Aspect Oriented Programming

🎄 AOP(Aspect Oriented Programming)面向切面编程

🎄 Spring 使用 AOP 技术封装了动态代理功能

🎄 它依赖 AspectJ 库

<dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjrt</artifactId>
     <version>1.9.6</version>
 </dependency>
 <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.9.6</version>
 </dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

(2) 切入点表达式

🍀 ① 任意公共方法:

execution(public * *(..))

🍀 ② 方法名以 set 开头的全部方法:

execution(* set*(..))

🍀 ③ UserService 接口定义的全部方法:

execution(* com.guoqing.service.UserService.*(..))

🍀 ④ service 包下定义的全部方法(不包括子包):

execution(* com.guoqing.service.*.*(..))

🍀 ⑤ service 包下定义的全部方法(包括子包):

execution(* com.guoqing.service..*.*(..))

🍀 ⑥ 包含两个 String 类型参数的全部方法:

execution(* *(String, String))

🍀 ⑦ 只有一个 Serializable 参数的全部方法:

args(java.io.Serializable)

🍀 ⑧ service 包中的全部方法:

within(com.guoqing.service.*)

🍀 ⑨ service 包中的全部方法(包括子包):

within(com.guoqing.service..*)


@Slf4j
@Aspect
@Component
public class GForeignAspect {
    @Around("execution(* com.guoqing.service..*.remove*(..))")
    public Object handleRemove(ProceedingJoinPoint point) throws Throwable {
        Object target = point.getTarget();
        if (!(target instanceof IService)) return point.proceed();
        IService<?> service = (IService<?>) target;
        // MP 泛型的类型(genericType)是 PO, 是要被删除的【表】
        Class<?> genericType = service.getEntityClass();
        // eg: class com.guoqing.po.WechatUser
        // 知道要被删除的类型(PO)后, 就可以知道该 PO 被哪些表引用着
        log.info("【庆】{}", genericType.toString());
        return point.proceed();
    }
}

二、SpringBoot 项目扫描类

🍀 ① 实现(implements)ApplicationContextAware 接口,并实现它的 void setApplicationContext(ApplicationContext) 方法

🎄 这是 Spring Bean 的生命周期方法

🎄 通过该方法可以获取 Spring 的 IoC 容器,进而获取到某个类的实例(通过它的 getBean() 方法)

@Component
public class TestLifecycle implements ApplicationContextAware {
 
    private ApplicationContext iocContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.iocContext = applicationContext;
        UserController userController = (UserController) iocContext.getBean("Users");
    }
}

🍀 ② 实现(implements) InitializingBean 接口,并实现它的 void afterPropertiesSet() 方法

🎄 这是 Spring Bean 的生命周期方法

🎄 当实现该接口的类的属性被设置(注入)完毕后,afterPropertiesSet() 方法被调用

@Component
public class GForeignAspect implements ApplicationContextAware, InitializingBean {
    private ApplicationContext iocContext;
    
    // 属性被设置(注入)完毕时,InitializingBean 接口的 afterPropertiesSet 被调用
    @Autowired 
    private ResourceLoader resourceLoader; 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.iocContext = applicationContext; 
    }
    @Override
    public void afterPropertiesSet() throws Exception {
    }
}

(1) ResourceLoader 扫描类

🍀 Spring 框架的 org.springframework.core.io.ResourceLoader

@Slf4j
@Aspect
@Component
public class GForeignAspect implements InitializingBean {
    // 要被扫描的类的 class 文件
    private static final String SCAN_CLASS = "classpath*:com/guoqing/po/**/*.class";
    @Autowired
    private ResourceLoader resourceLoader;
    /**
     * 扫描全部的 PO 类
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 通过资源加载器拿到资源匹配解析器
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        // 通过资源加载器拿到元数据读取工厂类
        CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
        // 通过资源匹配解析器读取到类的 class 资源
        Resource[] resources = resolver.getResources(SCAN_CLASS);
        if (resources.length == 0) {
            log.info("没有可供扫描的 PO");
            throw new Exception("没有可供扫描的 PO");
        }
        // 遍历 class 资源, 通过元数据解析器读取 class 资源的信息(如 类名)
        for (Resource resource : resources) {
            // 通过元数据读取工厂获取到元数据读取器
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            // 通过元数据读取器拿到 class 元数据读取器
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            // 通过元数据读取器拿到 class 的类名
            String className = classMetadata.getClassName();
            /*
                output:
                  className = com.guoqing.po.People
                  className = com.guoqing.po.WechatUser
             */
            System.out.println("className = " + className);
        }
    }
}

🎄 上面的一段代码都是比较固定的,直接复制使用即可

🎄 通过元数据读取器还可调用下面的方法获取其他 class 信息

🎄 上面的代码中,获取到了 class(或类)的完整路径。可通过 Java 的反射机制创建相应类的实例

(2) Map 的 computeIfAbsent 方法

  • compute: 计算
  • absent: 不存在

先看懂下面的代码,然后用 computeIfAbsent 改写下面的代码:

public class TestTest {
    @Test
    public void testComputeIfAbsent() {
        HashMap<String, Set<String>> hashMap = new HashMap<>();
        Set<String> set = new HashSet<>();
        set.add("杨天荣");
        hashMap.put("boy", set);
        // 判断 Map 中是否存在 key 是【boy】的键值对?
        if (hashMap.containsKey("boy")) {
            // 如果有, 获取该 key(boy)对应的 value(Set 类型)
            // 并往其中添加值【庆医】
            hashMap.get("boy").add("庆医");
        } else {
            // 如果没有, 新创建一个 Set, 把值【庆医】添加其中
            // 然后把新创建的这个 Set 添加到 Map 中
            Set<String> tmpSet = new HashSet<>();
            tmpSet.add("庆医");
            hashMap.put("boy", tmpSet);
        }
        // output: {boy=[杨天荣, 庆医]}
        System.out.println(hashMap.toString());
    }
}

✏️ 上面代码的逻辑很容易,但是写起来比较麻烦

✏️ Map 提供了 computeIfAbsent 方法,用以实现类型上面的逻辑功能,且写法很优美 🍀


public class TestTest {
    @Test
    public void testComputeIfAbsent() {
        HashMap<String, Set<String>> hashMap = new HashMap<>();
        HashSet<String> set = new HashSet<>();
        set.add("杨天荣");
        hashMap.put("boy", set);
        // hashMap.computeIfAbsent("boy", this::getSetInstance).add("庆医");
        hashMap.computeIfAbsent("boy", key -> getSetInstance(key)).add("庆医");
        // output: {boy=[杨天荣, 庆医]}
        System.out.println(hashMap.toString());
    }
    private Set<String> getSetInstance(String key) {
        return new HashSet<>();
    }
}

🔋 computeIfAbsent: 如果存在映射的值,则返回该值,否则返回计算值

(3) 反射几个常用 api

① 创建一个测试注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String function() default "该类作用显而易见, 不予解释";
}

✏️ 当和反射结合的时候,注解的作用非常强大

✏️ 注解没有和反射结合的时候,注解毫无作用

✏️ 上面的代码是一个简单的注解案例,如需了解更多 💯 关于注解: https://zgqwillbeverylucky.blog.csdn.net/article/details/127098256

② 创建测试 PO 类

@Data
public class BasePo {
    private Integer id;
    private String name;
    private String createTime;
}
@EqualsAndHashCode(callSuper = true)
@MyTestAnnotation(function = "人类, 用于构建人")
@Data
public class People extends BasePo {
    private String phone;
    private String job;
}

③ 反射 api 获取指定类的指定注解信息

📝 已知 People 类的完整类名是:com.guoqing.po.People

📝 已知 People 类有 @MyTestAnnotation 注解

📝 获取出 People 类的 @MyTestAnnotation 注解信息

public class TestReflection {
    private static final String CLASS_FULL_NAME = "com.guoqing.po.People";
    @Test
    public void test() throws ClassNotFoundException {
        // 使用 Class 类的 forName 静态方法, 传入完整类名, 创建该类的【类对象】
        Class<?> clsObj = Class.forName(CLASS_FULL_NAME);
        // 通过【类对象】的 getAnnotation 方法获取注解信息
        MyTestAnnotation annotation = clsObj.getAnnotation(MyTestAnnotation.class);
        // annotation = @com.guoqing.common.aop.foreign.annotation.MyTestAnnotation(function=人类, 用于构建人)
        System.out.println("annotation = " + annotation);
        // annotation.function = 人类, 用于构建人
        System.out.println("annotation.function = " + annotation.function());
    }
}

(4) 返回第一个不为空的字符串

public class GCommonUtil {
    /**
     * 判断一个字符串是否为 null 或空串
     */
    public static boolean isEmptyStr(String string) {
        return string == null || "".equals(string);
    }
    /**
     * 传入多个字符串 (可以是可变参数, 或数组), 返回第一个不为空的字符串
     */
    public static String firstNotEmptyStr(String... strings) {
        if (strings == null || strings.length < 1) return null;
        if (strings.length == 1) return strings[0];
        for (String string : strings) {
            if (!isEmptyStr(string)) {
                return string;
            }
        }
        return null;
    }
}

(5) 判断一个【字符】是大写字母还是小写字母

public class GCommonUtil {
    /**
     * 当一个字符是【大】写英文字母的时候返回 true
     */
    public static boolean isBigLetter(char source) {
        return source >= 'A' && source <= 'Z';
    }
    /**
     * 当一个字符是【小】写英文字母的时候返回 true
     */
    public static boolean isSmallLetter(char source) {
        return source >= 'a' && source <= 'z';
    }
 
}

(6) 让英文单词的首字母变小写

大写英文字符(如 A、E、F、B)加上数字 32 后变为小写英文字符

public class GCommonUtil {
    // 大写英文字符加上数字 32 后变为小写英文字符
    private static final int DELTA = 'a' - 'A'; // 32
     
    /**
     * 传入一个字符串, 若它的首字母是小写, 通过它的首字母创建 StringBuilder 对象返回
     * 若它的首字母是大写, 把首字母转换为小写后通过首字母创建 StringBuilder 对象返回
     */
    public static StringBuilder firstLetterLowerStringBuilder(String source) {
        StringBuilder sb = new StringBuilder();
        if (isEmptyStr(source)) return sb;
        // 取出字符串的首字母
        char firstLetter = source.charAt(0);
        if (isBigLetter(firstLetter)) { // 如果首字母是大写的
            sb.append((char) (firstLetter + DELTA));
        } else {
            sb.append(firstLetter);
        }
        return sb;
    } 
    /**
     * 返回英文字符串的首字母小写形式
     * 【 BOY-> bOY】
     */
    public static String firstLetterLowercase(String source) {
        if (isEmptyStr(source)) return source;
        StringBuilder sb = firstLetterLowerStringBuilder(source);
        int length = source.length();
        for (int i = 1; i < length; i++) {
            sb.append(source.charAt(i));
        }
        return sb.toString();
    } 
    
}

(7) 驼峰转下划线形式

/**
     * 驼峰转下划线形式
     * LoveYou -> love_you
     */
    public static String camel2underline(String source) {
        if (isEmptyStr(source)) return null;
        StringBuilder sb = firstLetterLowerStringBuilder(source);
        int len = source.length();
        for (int i = 1; i < len; i++) {
            char curChar = source.charAt(i);
            if (isBigLetter(curChar)) {
                sb.append("_");
                sb.append((char) (curChar + DELTA));
            } else {
                sb.append(curChar);
            }
        }
        return sb.toString();
    }

(8) GForeignTableInfo(外键表信息)

✏️ 该类用以描述的信息

✏️ 该类的内容很多,该节先介绍一部分(后面慢慢补充)

🍀① cacheClassTableMap 该属性缓存某个表的 class 对象和外键表(GForeignTableInfo)信息。它是 static 属性(常量 static final),所以它的 JVM 中只占用独一无二的一份内存

🍀 ② clsObj 该属性记录类对象(class)信息。它作为 key,和对应的 GForeignTableInfo 相映射

🍀③ tableName 该属性记录了该外键表信息对应的表的名字

@Getter
@Setter
public class GForeignTableInfo {
    // 缓存【类】和【表】的映射关系
    private static final Map<Class<?>, GForeignTableInfo> CACHE_CLASS_TABLE_MAP = new HashMap<>();
    private Class<?> clsObj;
    private String tableName;
    public static GForeignTableInfo getInfo(Class<?> clsObj, boolean newIfAbsent) {
        if (!newIfAbsent) return CACHE_CLASS_TABLE_MAP.get(clsObj);
        return CACHE_CLASS_TABLE_MAP.computeIfAbsent(clsObj, k -> {
            GForeignTableInfo foreignTableInfo = new GForeignTableInfo();
            foreignTableInfo.setClsObj(clsObj);
            // 设置表名
            String tableName;
            // 获取 clsObj 的 @GForeignTable 注解信息
            GForeignTable annotation = clsObj.getAnnotation(GForeignTable.class);
            if (annotation != null) {
                // 获取 @GForeignTable 注解中 name 或 value 注解信息
                // name 或 value 注解信息都可以表示表名
                tableName = GCommonUtil.firstNotEmptyStr(annotation.name(), annotation.value());
            } else {
                String clsSimpleName = clsObj.getSimpleName();
                tableName = GCommonUtil.firstLetterLowercase(clsSimpleName);
            }
            foreignTableInfo.setTableName(tableName);
            return foreignTableInfo;
        });
    }
}

(9) 遍历 Class 对象的全部属性

相关文章
|
6月前
|
分布式数据库 数据库
数据同步并发控制与数据一致性
数据同步并发控制与数据一致性
87 3
|
6月前
|
人工智能 数据管理 大数据
探索分布式系统中的数据一致性与并发控制
【2月更文挑战第8天】在当今互联网时代,分布式系统的概念越来越广泛应用于各种应用场景中。本文将深入探讨分布式系统中数据一致性与并发控制的重要性和挑战,介绍常见的解决方案以及未来发展趋势。
|
1月前
|
算法 Java 关系型数据库
漫谈分布式数据复制和一致性!
漫谈分布式数据复制和一致性!
|
6月前
|
关系型数据库 数据库 数据库管理
数据一致性
数据一致性
120 6
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
31534 70
如何保证分布式文件系统的数据一致性
|
算法 网络协议 NoSQL
「数据一致性」理解分布式系统中的一致性
「数据一致性」理解分布式系统中的一致性
为什么分布式系统中无法同时保证一致性和可用性?
为什么分布式系统中无法同时保证一致性和可用性?
244 0
|
算法 NoSQL 分布式数据库
如何在分布式系统中实现一致性?
如何在分布式系统中实现一致性?
348 0
|
新零售 消息中间件 存储
保证分布式系统数据一致性的6种方案
在电商等业务中,系统一般由多个独立的服务组成,如何解决分布式调用时候数据的一致性?
4283 0
分布式一致性
本文从分布式一致性问题出发,介绍了各种一致性算法,希望通过该文能让大家对分布式系统有一定的认识。