数据一致性

news2024/12/25 14:23:31

目录

  • 一、AOP 动态代理切入方法
    • (1) Aspect Oriented Programming
    • (2) 切入点表达式
  • 二、SpringBoot 项目扫描类
    • (1) ResourceLoader 扫描类
    • (2) Map 的 computeIfAbsent 方法
    • (3) 反射几个常用 api
      • ① 创建一个测试注解
      • ② 创建测试 PO 类
      • ③ 反射 api 获取指定类的指定注解信息
    • (4) 返回第一个不为空的字符串
    • (5) 判断一个【字符】是大写字母还是小写字母
    • (6) 让英文单词的首字母变小写
    • (7) 驼峰转下划线形式
    • (8) GForeignTableInfo(外键表信息)
    • (9) 遍历 Class 对象的全部属性

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

一、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 对象的全部属性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Golang哈希算法监控配置文件变化

SHA(secure hashing algorithm)表示安全哈希算法.SHA是MD5的修正版本&#xff0c;用于数据摘要和认证。哈希和加密类似&#xff0c;唯一区别是哈希是单项的&#xff0c;即哈希后的数据无法解密。SHA有不同的算法&#xff0c;主要包括SHA-1, SHA-2, SHA-256, SHA-512, SHA-224, …

1.2.6存储结构-磁盘管理:单缓冲区与双缓冲区读取、流水线周期、计算流水线执行时间

1.2.6存储结构-磁盘管理&#xff1a;单缓冲区与双缓冲区读取、流水线周期、计算流水线执行时间流水线周期计算流水线执行时间微秒&#xff0c;时间单位&#xff0c;符号μs&#xff08;英语&#xff1a;microsecond &#xff09;&#xff0c;1微秒等于百万分之一秒&#xff08;…

SpringSecurity的使用与步骤

1、SpringSecurity流程图2、导入坐标<!-- spring-boot-starter-security --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- jjwt…

生物素-磺基-活性酯,Sulfo-NHS Biotin科研用试剂简介;CAS:119616-38-5

生物素-磺基-活性酯,Sulfo-NHS Biotin 结构式&#xff1a; ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 英文名称&#xff1a;Sulfo-NHS-Biotin Sulfosuccinimidyl biotin 中文名称&#xff1a;磺酸基-Biotin-N-琥珀酰亚胺基酯 CAS&…

自己总结优化代码写法

jdk1.7新特性详解 开发期间略知jdk1.7的一些特性&#xff0c;没有真正的一个一个得展开研究&#xff0c;而是需要说明再去查&#xff0c;导致最整个新特性不是特别的清楚&#xff0c;这种情况以后得需要改变了&#xff0c;否则就会变成代码的奴隶。现在正好有时间可以细细的研…

SpringCloud第二讲 Ribbon负载均衡源码分析

前言介绍&#xff1a; 这一讲我们将依据Eureka的负载均衡规则&#xff0c;Eureka的具体服务搭建以及服务注册和服务发现可以参考基于Eureka实现服务注册和服务发现_热爱Java的编程小白的博客-CSDN博客 Eureka的服务搭建之后便可以在这上面进行服务注册&#xff0c;如果存在两个…

产品权限分析与设计

目前我们使用的访问控制授权方案&#xff0c;主要有以下4种&#xff1a;DAC自主访问控制ACL 访问控制列表MAC强制访问控制RBAC 基于角色的访问控制笔者将拆解和分析这4种权限管理方案的逻辑&#xff0c;并告诉你&#xff0c;这4种权限分别可以运用在什么样的场景中&#xff0c;…

不能注册?让小白也能使用【ChatGPT】

ChatGPT介绍最近ChatGPT可谓是非常火爆&#xff0c;使得互联网各界人士都备受关注&#xff0c;如果你还不了解ChatGPT,那你真的有点落后了哈。 简单介绍一下ChatGPTChatGPT是由美国人工智能OpenAI研究开发的一种全新聊天机器人模型&#xff0c;它能够通过学习和理解人类的语言跟…

研一寒假C++复习笔记--左值和右值的理解和使用

目录 1--左值和右值的定义 2--简单理解左值和右值的代码 3--非const引用只能接受左值 1--左值和右值的定义 左值&#xff1a;L-Value&#xff0c;L理解为 Location&#xff0c;表示可寻&#xff1b; 右值&#xff1a;R-Value&#xff0c;R理解为 Read&#xff0c;表示可读&a…

Windows 安装appium环境

1 windows Appium环境 1.1 安装Node.js Node.js的安装相对简单,下载安装包安装&#xff08;安装包node-v19.6.0-x64.msi&#xff09;, nodejs 安装 然后一路狂点下一步就可以了 安装完成后,在终端中输入node -v,显示版本号则表示安装成功 node-v16.13.1 1.2 JDK安装及环境变…

路由器刷固件

前言 我希望可以远程访问我的电脑。但&#xff0c;我不希望电脑总是处于运行状态&#xff0c;因为那样比较费电。所以需要一个方案&#xff0c;能将睡眠/关机中的电脑唤醒。 方案一&#xff1a;选用智能插座&#xff0c;远程给电脑上电。电脑设置上电自启。但&#xff0c;这存…

试题 算法训练 N皇后问题(明确清晰)

试题 算法训练 N皇后问题 提交此题 评测记录 资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;100ms Java时间限制&#xff1a;300ms Python时间限制&#xff1a;500ms 问题描述   在N*N的方格棋盘放置了N个皇后&#xff0c;使得它们不相互攻击&#xff08;即…

色彩-基础理论

颜色三大指标 色相 色相是颜色的一个属性&#xff0c;只有黑白灰没有色相这个属性(那银灰色是什么&#xff1f;) 颜色的相貌&#xff0c;指的也是给颜色一个名字 例如&#xff1a;暗红、酒红、土黄、墨绿 饱和度 颜色的鲜艳程度 纯度 饱和度主要取决于含色成分和消色成分&a…

IDE2022源码编译tomcat

因为学习需要&#xff0c;我需要源码编译运行tomcat对其源码进行一个简单的追踪分析。由于先前并未接触过java相关的知识&#xff0c;安装阻力巨大。最后请教我的开发朋友才解决了最后的问题。将其整理出来&#xff0c;让大家能够快速完成相关的部署。本文仅解决tomcat-8.5.46版…

tensorflow.js 视频图片多目标检测

前言&#xff1a;Tensorflow.js 官方提供了很多常用模型库&#xff0c;涵盖了平时开发中大部分场景的模型。例如&#xff0c;前面提到的图片识别&#xff0c;除此之外还有人体姿态识别&#xff0c;目标物体识别&#xff0c;语音文字等识别。其中一些可能是 Python 转换而来&…

前后端RSA互相加解密、加签验签、密钥对生成(Java)

目录一、序言二、关于PKCS#1和PKCS#8格式密钥1、简介2、区别二、关于JSEncrypt三、关于jsrsasign四、前端RSA加解密、加验签示例1、相关依赖2、cryptoUtils工具类封装3、测试用例五、Java后端RSA加解密、加验签1、CryptoUtils工具类封装2、测试用例六、前后端加解密、加验签交互…

导数与微分总复习——“高等数学”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰来复习一下之前学过的知识点&#xff0c;也就是导数与微分的总复习&#xff0c;依旧是高等数学的内容&#xff0c;主要是明天就要考高等数学了&#xff0c;哈哈哈&#xff0c;下面&#xff0c;让我们一起进入高等数学…

取电芯片全协议都可兼容

乐得瑞PD协议芯片/PD取电芯片/PD受电端协议芯片 支持5/9/12/15/20v定制 1、概述 LDR6328S 是乐得瑞科技有限公司开发的一款兼容 USB PD、QC 和 AFC 协议的 Sink 控制器。 LDR6328S 从支持 USB PD、QC 和 AFC 协议的适配器取电&#xff0c;然后供电给设备。比如可以配置适配器输…

二十九、异常处理

目录 ①前言: ②常见的运行时异常 ③常见的编译时异常 ④异常的处理机制 ⑤自定义异常 ①前言: 1.什么是异常&#xff1f; 异常是程序在“编译”或者“执行”的过程中可能出现的问题&#xff0c;注意&#xff1a;语法错误不算在异常体系中。 比如: 数据索引越界异常&…

C语言的程序环境和预处理详解

目录 一、程序的翻译环境和执行环境 二、编译和链接详解 2、1 翻译环境 2、2 编译过程详解 2、3 执行环境 三、预处理详解 3、1 预定义符号 3、2 #define 3、2、1 #define定义的符号 3、2、2 #define 定义宏 3、2、3 #define 替换规则 3、3 宏和函数的对比 3、4 条件编译 3、5…