【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换读写分离】—— 案例实战

💧 S p r i n g A O P + 主从数据源切换 + 读写分离 + 自定义注解案例实战! \color{#FF1493}{Spring AOP + 主从数据源切换 + 读写分离 + 自定义注解 案例实战!} SpringAOP+主从数据源切换+读写分离+自定义注解案例实战!💧
🌷 仰望天空,妳我亦是行人.✨
🦄 个人主页——微风撞见云的博客🎐
🐳 《数据结构与算法》专栏的文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺
💧 《Java学习笔记》专栏的文章是本人在Java学习中总结的一些知识点~ 💐
🥣 《每天一点小知识》专栏的文章可以丰富你的知识库,滴水成河~ 🌊
🎐 《Redis》专栏的文章是在学习Redis时,整理的笔记与记录的思考~ 🥏
🥕 《RabbitMQ》专栏的文章是在学习尚硅谷课程时整理的笔记,方便复习巩固~ 🍑
🪁 希望本文能够给读者带来一定的帮助~🌸文章粗浅,敬请批评指正!🐥
文章目录
- 🐳Spring AOP + 自定义注解 + 数据源 实现主从库切换&读写分离 项目实战
- 准备工作
- 项目搭建以及相关依赖
- 书写yml文件
- 数据库准备
- config目录各文件介绍
- 定义Spring AOP的切面类 DataSourceAop
- 配置数据源和动态数据源切换
- 创建自定义注解
- 定义数据库读写分离的工具类DBContextHolder
- 定义枚举类DBTypeEnum
- 配置Mybatis指定数据源:SqlSessionFactory和事务管理器
- 自定义数据源路由类MyRoutingDataSource
- config配置类总结
- 其他文件说明
- UserController
- UserEntity
- UserMapper
- UserService
- 主启动类DemoApplication
- 功能演示
- 总结
- 🐳结语
🐳Spring AOP + 自定义注解 + 数据源 实现主从库切换&读写分离 项目实战
在现代的应用程序开发中,数据库读写分离是提高应用性能和可伸缩性的重要策略之一。Spring AOP 和自定义注解为我们提供了实现读写分离的有效工具,而德鲁伊(Druid)数据源则为我们提供了高性能的连接池,我们用它来实现动态数据源。本篇博客将带领你一步一步实现 Spring AOP 结合自定义注解和动态数据源实现主从数据库切换以及读写分离。
准备工作
项目搭建以及相关依赖
💧首先,我们需要确保已经创建好了一个 Spring Boot (2.x.x) 项目,并添加了相关依赖。
<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><!--SpringBoot集成Aop起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!--SpringBoot集成WEB起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis集成SpringBoot起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
💧项目结构如图所示:

书写yml文件
💧我们在application.yml中配置一下主从数据源
server:port: 8080spring:datasource:#主数据源master:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://host/yoordp?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8username: password: #从数据源slave:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://host/yoordp?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8username: password:
数据库准备
💧准备两个数据库,分别作为主从数据库(当然,这里不用强制实现它们俩直接的主从关系 ),然后分别建 user2 表,然后准备一个可以作为区分的数据。如果有不清楚如果实现mysql主从复制的同学可以看看我的这篇文章:docker实现mysql 主从复制
CREATE TABLE `user2` (`user_id` int NOT NULL,`account` varchar(255) DEFAULT NULL,`nickname` varchar(255) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`headimage_url` varchar(255) DEFAULT NULL,`introduce` varchar(255) DEFAULT NULL,PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;


config目录各文件介绍
定义Spring AOP的切面类 DataSourceAop
💧DataSourceAop 是一个Spring AOP切面类,用于拦截方法调用,并根据方法的特定条件来选择数据源类型。它通过@Pointcut定义了两个切点表达式,分别用于读操作和写操作的方法。在前置通知方法中,根据目标方法上是否存在 @Master 注解,来决定使用主库还是从库。这样,通过AOP的切面功能,实现了数据库的读写分离。
package com.lxr.demo.config;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 默认情况下,所有的查询都走从库,插入/修改/删除走主库。我们通过方法名来区分操作类型(CRUD)* <p>* 切面不能建立在DAO层,事务是在service开启的,到dao层再切换数据源,那事务就废了*/
@Aspect
@Component
public class DataSourceAop {/*** 第一个”*“符号 表示返回值的类型任意;* com.sample.service.impl AOP所切的服务的包名,即,我们的业务部分* 包名后面的”..“ 表示当前包及子包* 第二个”*“ 表示类名,*即所有类。此处可以自定义,下文有举例* .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型* <p>* 这是一个切点表达式,它定义了一个切点,该切点在执行以下条件时成立:* !@annotation(com.lxr.demo.config.Master): 这表示切点会排除那些带有@com.lxr.demo.config.Master注解的方法。* execution(* com.lxr.demo.service.*.select*(..)):* 表示切点会包含所有com.lxr.demo.service包下以select开头的方法,并且方法参数可以是任意个数、任意类型。* execution(* com.lxr.demo.service..*.find*(..)):* 表示切点会包含所有com.lxr.demo.service包及其子包下以find开头的方法,并且方法参数可以是任意个数、任意类型。*/@Pointcut("!@annotation(com.lxr.demo.config.Master) " +"&& (execution(* com.lxr.demo.service.*.select*(..)) || execution(* com.lxr.demo.service..*.find*(..)) ) ")public void readPointcut() {}@Pointcut("@annotation(com.lxr.demo.config.Master) " +"|| execution(* com.lxr.demo.service..*.save*(..)) " +"|| execution(* com.lxr.demo.service..*.add*(..)) " +"|| execution(* com.lxr.demo.service..*.insert*(..)) " +"|| execution(* com.lxr.demo.service..*.update*(..)) " +"|| execution(* com.lxr.demo.service..*.edit*(..)) " +"|| execution(* com.lxr.demo..*.delete*(..)) " +"|| execution(* com.lxr.demo..*.remove*(..))")public void writePointcut() {}@Before("readPointcut()")public void read(JoinPoint jp) {
/*** JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.* 常用api:** 方法名 功能* Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息* Object[] getArgs(); 获取传入目标方法的参数对象* Object getTarget(); 获取被代理的对象* Object getThis(); 获取代理对象*///获取当前的方法信息MethodSignature methodSignature = (MethodSignature) jp.getSignature();//方法头指定修饰符(例如static)、返回值类型、方法名、和形式参数。Method method = methodSignature.getMethod();//判断方法上是否存在注解@Masterboolean present = method.isAnnotationPresent(Master.class);//判断注解是否存在该元素上,如果有则返回true,否则falseif (!present) {//如果不存在,默认走从库读System.out.println("no");DBContextHolder.slave();} else {//如果存在,走主库读System.out.println("yes");DBContextHolder.master();}}@Before("writePointcut()")public void write() {System.out.println("write");DBContextHolder.master();}/*** 另一种写法:if...else... 判断哪些需要读从数据库,其余的走主数据库*/
// @Before("execution(* com.cjs.example.service.impl.*.*(..))")
// public void before(JoinPoint jp) {
// String methodName = jp.getSignature().getName();
//
// if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
// DBContextHolder.slave();
// }else {
// DBContextHolder.master();
// }
// }}
配置数据源和动态数据源切换
💧我们首先创建一个配置类 DataSourceConfig 来配置德鲁伊数据源和动态数据源切换。这个配置类中使用了@Configuration和@Bean注解,定义了两个数据源(主库和从库)和一个动态数据源。动态数据源会根据业务需求自动选择主库还是从库,从而实现了读写分离的功能。这在多数据库场景下非常有用,可以提高数据库的读取性能。
package com.lxr.demo.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 增加了 DataSourceConfig 这个配置文件之后,需要添加druid连接池,单数据源自动装载时不会出这样的问题** @Configuration 注解,表明这就是一个配置类,指示一个类声明一个或者多个@Bean 声明的方法并且由Spring容器统一管理,以便在运行时为这些bean生成bean的定义和服务请求的类。*/
@Configuration
public class DataSourceConfig {/*** 注入主库数据源*/@Bean@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource masterDataSource() {return new DruidDataSource();}/*** 注入从库数据源*/@Bean@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDataSource() {return new DruidDataSource();}/*** 配置选择数据源** @param masterDataSource* @param slaveDataSource* @return DataSource*/@Beanpublic DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) {Map<Object, Object> targetDataSource = new HashMap<>();targetDataSource.put(DBTypeEnum.MASTER, masterDataSource);targetDataSource.put(DBTypeEnum.SLAVE, slaveDataSource);MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();//找不到用默认数据源myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);//可选择目标数据源myRoutingDataSource.setTargetDataSources(targetDataSource);return myRoutingDataSource;}
}
创建自定义注解
💧接下来,我们创建一个自定义注解 Master 来标记我们需要进行主从分离的方法。
package com.lxr.demo.config;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 有时候主从延迟,需要强制读主库的注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Master {
}
定义数据库读写分离的工具类DBContextHolder
💧这里的 DBContextHolder 是一个线程上下文工具类,通过 ThreadLocal 来实现不同线程使用不同数据源的功能。在实现数据库读写分离的场景下,它可以根据业务需求自动选择主库或从库,确保在多线程环境下的数据源正确切换。这种实现方式非常适用于多线程环境下需要使用读写分离的项目。
package com.lxr.demo.config;/*** ThreadLocal 定义数据源切换,通过ThreadLocal将数据源绑定到每个线程上下文中,* ThreadLocal 用来保存每个线程的是使用读库还是写库。操作结束后清除该数据,避免内存泄漏。*/
public class DBContextHolder {/*** ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,对数据存储后,只有在当前线程中才可以获取到存储的数据,对于其他线程来说是无法获取到数据。* 大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的,通过get和set方法就可以得到当前线程对应的值。*/private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();public static void set(DBTypeEnum dbTypeEnum) {contextHolder.set(dbTypeEnum);}public static DBTypeEnum get() {return contextHolder.get();}public static void master() {set(DBTypeEnum.MASTER);System.out.println("--------以下操作为master(操作)--------");}public static void slave() {set(DBTypeEnum.SLAVE);System.out.println("--------以下操作为slave(读操作)--------");}public static void clear() {contextHolder.remove();}
}
定义枚举类DBTypeEnum
💧这里的 DBTypeEnum 是一个枚举类,用于表示数据库的主库和从库,在数据库读写分离的实现中,可能会用作标识数据源类型的常量,以便在动态数据源切换时选择不同的数据源。这种枚举常量的使用方式有助于代码的可读性和维护性。
package com.lxr.demo.config;public enum DBTypeEnum {MASTER, SLAVE;
}
配置Mybatis指定数据源:SqlSessionFactory和事务管理器
💧这里的MyBatisConfig 是一个Spring配置类,用于配置MyBatis的SqlSessionFactory和事务管理器。通过这个配置类,MyBatis可以连接到动态数据源,并实现数据库的读写分离。同时,启用了事务管理功能,确保在进行数据库操作时能够进行事务控制。
package com.lxr.demo.config;import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;
import javax.sql.DataSource;/*** 配置Mybatis指定数据源:SqlSessionFactory和事务管理器*/
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {/*** 注入自己重写的数据源*/@Resource(name = "myRoutingDataSource")private DataSource myRoutingDataSource;/*** 配置SqlSessionFactory** @return SqlSessionFactory* @throws Exception*/@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(myRoutingDataSource);//ResourcePatternResolver(资源查找器)定义了getResources来查找资源//PathMatchingResourcePatternResolver提供了以classpath开头的通配符方式查询,否则会调用ResourceLoader的getResource方法来查找
// ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocation));return sqlSessionFactoryBean.getObject();}/*** 事务管理器,不写则事务不生效:事务需要知道当前使用的是哪个数据源才能进行事务处理*/@Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(myRoutingDataSource);}// /**
// * 当自定义数据源,用户必须覆盖SqlSessionTemplate,开启BATCH处理模式
// *
// * @param sqlSessionFactory
// * @return
// */
// @Bean
// public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
// return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
// }}
自定义数据源路由类MyRoutingDataSource
💧这里的MyRoutingDataSource 是一个自定义的数据源路由类,继承了 AbstractRoutingDataSource 类。它通过重写 determineCurrentLookupKey() 方法,动态决定使用哪个数据源,从而实现了数据库的读写分离。这种动态数据源切换的方式非常灵活,可以根据业务需求在运行时动态选择不同的数据源。
package com.lxr.demo.config;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/*** 重写 determineCurrentLookupKey 方法,获取当前线程上绑定的路由key。Spring 在开始进行数据库操作时会通过这个方法来决定使用哪个数据库源,因此我们在这里调用上面 DbContextHolder 类的getDbType()方法获取当前操作类别。** AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。** AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。** 基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {/*** determineCurrentLookupKey()方法决定使用哪个数据源、* 根据Key获取数据源的信息,上层抽象函数的钩子*/@Nullable@Overrideprotected Object determineCurrentLookupKey() {return DBContextHolder.get();}
}
config配置类总结
💧上面介绍了config中的各种配置类以及相关工具类,现在对它们进行简单梳理 ↓
DBTypeEnum:这是一个枚举类,定义了两个枚举常量 MASTER 和 SLAVE,分别表示数据库的主库和从库。DBContextHolder:这是一个工具类,使用了 ThreadLocal 来定义数据源切换。它可以将数据源与每个线程的上下文绑定在一起,用于在多线程环境下实现不同线程使用不同的数据源。DataSourceConfig:这是一个Spring配置类,用于配置数据源。它定义了两个 @Bean 方法,分别用于创建主库数据源和从库数据源。此外,还定义了一个 myRoutingDataSource 方法,用于创建一个动态数据源,根据不同的数据源类型选择相应的数据源。MyRoutingDataSource:这是一个自定义的数据源路由类,继承了 AbstractRoutingDataSource 类。它重写了 determineCurrentLookupKey() 方法,用于动态决定当前使用的数据源,根据 DBContextHolder 中存储的数据源类型(主库或从库),选择相应的数据源。MyBatisConfig:这是一个Spring配置类,用于配置MyBatis的 SqlSessionFactory 和事务管理器。它通过 @Resource 注解将 myRoutingDataSource 自动注入,将动态数据源应用到MyBatis框架中。DataSourceAop:这是一个切面类,用于在使用自定义注解时拦截方法调用。它在 before 方法中根据方法上的自定义注解,决定将当前线程的数据源设置为主库或从库,从而实现读写分离的功能。
💧这些类共同实现了一个数据库读写分离的功能。DBTypeEnum 定义了数据源类型,DBContextHolder 管理当前线程的数据源类型,DataSourceConfig 配置多个数据源和动态数据源切换,MyRoutingDataSource 实现数据源的动态路由,MyBatisConfig 将动态数据源应用到MyBatis框架中,DataSourceAop 切面根据方法上的注解选择数据源类型。这种组合使得我们可以在一个Spring Boot项目中实现数据库读写分离的功能。
其他文件说明
UserController
💧这个文件是一个Spring Boot的控制器类,名为 UserController。它处理来自前端的HTTP请求,调用 UserService 中的方法来处理业务逻辑,并返回相应的结果。
package com.lxr.demo.controller;import com.lxr.demo.entity.UserEntity;
import com.lxr.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Random;@RestController
public class UserController {@AutowiredUserService userService;@RequestMapping("/listUser")public List<UserEntity> listUser() {List<UserEntity> users = userService.findAll();return users;}@RequestMapping("/insertUser")public void insertUser() {UserEntity userEntity = new UserEntity();Random random = new Random();userEntity.setUser_id(random.nextInt());userEntity.setAccount("22222");userEntity.setNickname("lxrlxrlxr");userEntity.setPassword("123");userService.insertUser(userEntity);}}
UserEntity
💧一个普通的实体类,使用了lombok的@Data注解。
package com.lxr.demo.entity;import lombok.Data;@Data
public class UserEntity {private Integer user_id;private String account;private String nickname;private String password;private String headimage_url;private String introduce;}
UserMapper
💧一个简单的dao层。
package com.lxr.demo.mapper;import com.lxr.demo.entity.UserEntity;
import org.apache.ibatis.annotations.*;
import java.util.List;/*** Spring通过@Mapper注解实现动态代理,mybatis会自动创建Dao接口的实现类代理对象注入IOC容器进行管理,这样就不用编写Dao层的实现类*/
@Mapper
public interface UserMapper {@Select("SELECT * FROM user2")List<UserEntity> findAll();@Insert("insert into user2(user_id,account,nickname,password) values(#{user_id},#{account}, #{nickname}, #{password})")int insert(UserEntity user);// @Update("UPDATE user2 SET account=#{account},nickname=#{nickname} WHERE id =#{id}")
// void update(UserEntity user);
//
// @Delete("DELETE FROM user2 WHERE id =#{id}")
// void delete(Long id);
}
UserService
💧一个简单的Service层。
package com.lxr.demo.service;import com.lxr.demo.entity.UserEntity;
import com.lxr.demo.mapper.UserMapper;
import com.lxr.demo.config.Master;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserService {@AutowiredUserMapper userMapper;public List<UserEntity> findAll() {return userMapper.findAll();}@Masterpublic int insertUser(UserEntity user) {return userMapper.insert(user);}// void update(UserEntity user);
//
// void delete(Long id);}
主启动类DemoApplication
package com.lxr.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.lxr.demo.mapper")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class,args);}
}
功能演示
💧我们启动项目,打开浏览器或者postman等工具
💧分别访问:
从库的读操作 :http://localhost:8080/listUser


主库的写操作 :http://localhost:8080/insertUser


总结
💧通过本篇博客,我们学习了如何使用 Spring AOP 结合自定义注解和德鲁伊数据源来实现主从数据库切换和方法的读写分离。通过自定义注解 @Master 来区分主库还是从库,通过切点来区分读写方法,我们成功地将读写操作路由到不同的数据源,从而提高了应用程序的性能和可伸缩性。读写分离是一个重要的数据库优化策略,在实际的生产环境中非常有用。
💧希望本篇博客对您有所帮助,如果您有任何问题或建议,欢迎在评论区留言。谢谢阅读!

🐳结语
🐬初学一门技术时,总有些许的疑惑,别怕,它们是我们学习路上的点点繁星,帮助我们不断成长。
🐟积少成多,滴水成河。文章粗浅,希望对大家有帮助!
相关文章:
【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换读写分离】—— 案例实战
💧 S p r i n g A O P 主从数据源切换 读写分离 自定义注解案例实战! \color{#FF1493}{Spring AOP 主从数据源切换 读写分离 自定义注解 案例实战!} SpringAOP主从数据源切换读写分离自定义注解案例实战!💧 …...
【LeetCode每日一题合集】2023.7.24-2023.7.30
文章目录 771. 宝石与石头代码1——暴力代码2——位运算集合⭐(英文字母的long集合表示) 2208. 将数组和减半的最少操作次数(贪心 优先队列)2569. 更新数组后处理求和查询⭐⭐⭐⭐⭐(线段树)TODO2500. 删除…...
《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(14)-Fiddler断点(breakpoints)实战,篡改或伪造数据
1.简介 上一篇主要就讲解和分享Fiddler断点的理论和操作,今天宏哥就用具体例子,将上一篇中的理论知识实践一下。而且在实际测试过程中,有时候需要修改请求或响应数据,或者直接模拟服务器响应,此时可以使用fiddler进行…...
ELK + Fliebeat + Kafka日志系统
参考: ELKFilebeatKafka分布式日志管理平台搭建_51CTO博客_elk 搭建 ELK 日志分析系统概述及部署(上)-阿里云开发者社区 ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。…...
Scaling Instruction-Finetuned Language Models
Paper name Scaling Instruction-Finetuned Language Models Paper Reading Note Paper URL: https://arxiv.org/pdf/2210.11416.pdf TL;DR 2022 年谷歌出的文章,对指令微调的影响因素进行分析,提出了一些提升指令微调效果的方案。与该文章一起出品…...
rust 闭包函数
函数有自己的类型,可以像使用基础类型一样使用函数,包括将函数保存在变量中、保存在 vec 中、声明在结构体成员字段中。闭包函数也是函数,也有自己的类型定义。不过,函数实际上是指针类型,在 rust 所有权中属于借用的关…...
MySQL 实现分库和分表的备份 2023.7.29
1、分库备份 [rootlocalhost mysql-backup]# cat db_bak.sh #!/bin/bash k_userroot bak_password123456 bak_path/root/mysql-backup/ bak_cmd"-u$bak_user -p$bak_password" exc_db"Database|information_schema|mysql|performance_schema|sys" dbname…...
20230728----重返学习-跨域-模块化-webpack初步
day-122-one-hundred-and-twenty-two-20230728-跨域-模块化-webpack初步 跨域 跨域 为什么要跨域? 浏览器为了安全,不能让我们的html文件可以随意引用别的服务器中的文件,只允许我们的html或js文件中,请求我们自己服务器。这个…...
[SQL挖掘机] - 多表连接: union all
介绍: sql中的union all是用于合并两个或多个select语句的结果集的操作符。与union不同的是,union all不会自动去除重复的行,它会简单地将多个查询的结果集合并在一起,包括重复的行。 用法: union all的基本语法如下: select_…...
TypeError: run() got an unexpected keyword argument ‘hide_label‘ yolov5最新版本报错
报错展示 解决方法 把detect.py中的如上部分的 --hide-label改为 --hide-labels,成功解决....
什么是Java中的集成测试?
Java中的集成测试(Integration Test)是一种测试方法,用于测试多个模块或组件之间的交互和集成。在Java中,集成测试通常使用单元测试框架(如JUnit)编写和运行。 对于初学者来说,集成测试可能有些…...
打卡力扣题目二
#左耳听风 ARST 打卡活动重启# 目录 一、问题 二、 解题方法一 三、enumerate函数介绍 关于 ARTS 的释义 —— 每周完成一个 ARTS: ● Algorithm: 每周至少做一个 LeetCode 的算法题 ● Review: 阅读并点评至少一篇英文技术文章 ● Tips: 学习至少一个技术技巧 …...
【Qt】QML-02:QQuickView用法
1、先看demo QtCreator自动生成的工程是使用QQmlApplicationEngine来加载qml文件,下面的demo将使用QQuickView来加载qml文件 #include <QGuiApplication> #include <QtQuick/QQuickView>int main(int argc, char *argv[]) {QGuiApplication app(argc,…...
【IDEA】idea不自动生成target
文章目录 1. 不生成target2. 仅部分文件不生成target2.1. 一般原因就是资源没有设置2.2. 配置编译src/main/java文件夹下的资源文件2.3. 清理缓存(王炸) 3. 参考资料 本文描述idea不生成target的几种情况以及处理方法 1. 不生成target 像下图这样根本就…...
从官网认识 JDK,JRE,JVM 三者的关系
点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~ JVM 是一些大厂面试必问点,要想解决 OOM、性能调优方面的问题,掌握 JVM 知识必不可少,从今天开始,将为大家介绍 JVM 的常用知…...
python 将pdf文件转图片
有小伙伴问了怎么将 pdf文件转图片的问题,我百度了一波儿,搞了以下python代码给他封装成exe工具了。 中途打包踩了个坑,python进程池的问题,本地运行没啥问题,打包好的exe文件双击就会使电脑内存爆破卡死,…...
js原型以及原型链
目录 原型隐式原型显式原型constructornew操作符 重写原型对象原型链继承原型链继承借用构造函数继承组合构造继承 原型继承寄生继承组合寄生继承 原型继承关系 原型 在JavaScript中,每个对象都有一个内置属性[[prototype]],这个属性指向一个另一个对象…...
Java面向对象编程实战详解(图书管理系统示例)
文章目录 面向编程概念图书管理系统示例需求分析设计阶段编码实现创建目录结构Book类的编码BookList类的编码User类的编码AdminUser类的编码NormalUser类的编码启动类的编写具体的操作实现IOperation接口新增图书的实现借阅图书的实现删除图书的实现显示图书的实现查找图书的实…...
ubuntu设置主机ip
ubuntu 设置ip sudo dhclient -r enp67s0 # 是你的网卡,可以通过ifconfig 查,比如enp0 sudo ifconfig enp67s0 192.168.1.114 netmask 255.255.255.0 Ubuntu显示有线网已连接但无法上网,已经确认网口、交换机(路由器ÿ…...
CleanMyMac X4.14.1中文版如何清理 Mac系统?CleanMyMac 真的能断网激活吗?
CleanMyMac X4.14.1中文版如何清理 Mac系统?Mac系统在使用过程中都会产生大量系统垃圾,如不需要的系统语言安装包,视频网站缓存文件,mac软件卸载残留的注册表等。 随着时间推移,mac系统垃圾就会越来越多,电…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
