SpringAOP--动态数据源
阅读原文时间:2023年07月08日阅读:2

  通过注解和AOP,实现主从数据源的切换。

首先项目布局:

1:实体类,与数据库表的映射

@Data
@Builder
public class UserBean {

private Long id;

private String name;

private String password;  

}

2:dao类与对应的xml的mapper

@Repository
public interface UserDao {
//新增
int insertUser(UserBean userBean);
//查询
List getAllUser();
}

dao接口





insert into users(name,password) values(#{name},#{password})

<!-- 查询全部用户 -->  
<select id="getAllUser" resultType="com.wht.springdynamicdatasource.entity.UserBean">  
    select \* from users  
</select>  

mapper.xml

3:Service与实现类

public interface UserService {

int insertUser(UserBean user);

List<UserBean> getAllUser();  

}

service接口

@Service
@Transactional
// @DataSource("SLAVE_DATASOURCE")
public class UserServiceImpl implements UserService {

@Autowired  
private UserDao userDao;

@Override  
// @DataSource("MASTER\_DATASOURCE")  
public int insertUser(UserBean user) {  
    return userDao.insertUser(user);  
}

@Override  
@DataSource("SLAVE\_DATASOURCE")  
public List<UserBean> getAllUser() {  
    return userDao.getAllUser();  
}  

}

service实现类

4:动态数据源配置

(1)注解:value参数指定数据源类型

@Target(value = {ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "MASTER-DATASOURCE";
}

(2)切面:指明要切哪个方法,如何获取该方法上的注解和注解值,从而根据注解配置切换数据源实例;

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Pointcut(value = "execution(\* com.wht.springdynamicdatasource.service.\*.\*(..))")  
public void dataPointCut(){  
};

@Before("dataPointCut()")  
public void before(JoinPoint joinPoint){  
    Class<?> aClass = joinPoint.getTarget().getClass();  
    // 获取类级别注解  
    DataSource classAnnotation = aClass.getAnnotation(DataSource.class);  
    if (classAnnotation != null){  
        String dataSource = classAnnotation.value();  
        logger.info("this is datasource: "+ dataSource);  
        DynamicDataSource.putDataSourceKey(dataSource);  
    }else {  
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();  
        Method method = methodSignature.getMethod();  
        DataSource methodAnnotation = method.getAnnotation(DataSource.class);  
        if (methodAnnotation != null){  
            String dataSource = methodAnnotation.value();  
            logger.info("this is dataSource: "+ dataSource);  
            DynamicDataSource.putDataSourceKey(dataSource);  
        }  
    }  
}  
@After("dataPointCut()")  
public void after(JoinPoint joinPoint){  
    logger.info("执行完毕!");  
    DynamicDataSource.removeDataSourceKey();  
}  

}

(3)自定义数据源路由:通过ThreadLocal保存当前线程要使用的数据源实例名称,determineCurrentLookupKey决定使用哪个数据源实例进行数据库mysql操作

public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
//数据源key值
private static final ThreadLocal holder = new ThreadLocal<>();

//设置数据源  
public static void putDataSourceKey(String key){  
    log.info("切换数据源到{}", key);  
    holder.set(key);  
}  
//获取数据源  
public static String getDataSourceKey(){  
    return holder.get();  
}  
//移除  
public static void removeDataSourceKey(){  
    holder.remove();  
}  
// 该方法决定使用哪个数据源  
@Override  
protected Object determineCurrentLookupKey() {  
    Object o = getDataSourceKey();  
    log.info("当前数据源为:===", getDataSourceKey());  
    return getDataSourceKey();  
}  

}

通过枚举,方便进行多个数据源的名称等管理:

@Getter
public enum DataSourceName {

MASTER("MASTER\_DATASOURCE", 1),  
SLAVE("SLAVE\_DATASOURCE", 2);

private String name;  
private int index;

DataSourceName(String name, int index) {  
    this.name = name;  
    this.index = index;  
}  

}

(4)重要:多数据源的生成与配置,以及事务配置。多数据源最终目的是用自定义的数据源取代默认数据源配置,生成自定义的、线程安全的SqlSessionTemplate

/**
* @createtime 2019/9/3
* @description 根据配置文件生成多个DataSource实例,将其注入自定义DynamicDataSource中,
* 该类继承了AbstractRoutingDataSource,进行路由管理。由sqlSessionFactory扫描mybatis相关配置,
* 生成对应工厂实例。
*/
@Configuration
@EnableTransactionManagement
//指定dao的扫描位置和sqlSessionFactoryRef的引用实例。
@MapperScan(basePackages = {"com.wht.springdynamicdatasource.dao"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
//yml中配置的mapper扫描路径
@Value("${mybatis.mapperLocations}")
private String mapperLocations;

//yml中配置的mybatis配置文件位置  
@Value("${mybatis.configLocation}")  
private String configLocation;

/\*\*  
 \* step1:通过指定配置文件前缀,生成主、从两个DataSource实例  
 \* @return  
 \*/  
@Bean(name = "masterDataSource")  
@ConfigurationProperties(prefix = "spring.datasource.master")  
public DataSource masterDataSource(){  
    DruidDataSource masterDataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();  
    masterDataSource.setName("masterDataSource");  
    return masterDataSource;  
}  
//slave数据源实例  
@Bean(name = "slaveDataSource")  
@ConfigurationProperties(prefix = "spring.datasource.slave")  
public DataSource slaveDataSource(){  
    DruidDataSource slaveDataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();  
    slaveDataSource.setName("slaveDataSource");  
    return slaveDataSource;  
}

/\*\*  
 \* step: 通过DynamicDataSource继承Spring提供的数据库路由管理  
 \* 接口AbstractRoutingDataSource,将多个数据源实例注入其中。  
 \* @return  
 \*/  
@Bean(name = "dataSource")  
public DataSource dataSource(){  
    DynamicDataSource dynamicDataSource = new DynamicDataSource();  
    //默认数据源  
    dynamicDataSource.setDefaultTargetDataSource(masterDataSource());  
    //多数据源  
    Map<Object,Object> dataSourceMaps = new HashMap<>(2);  
    //构建数据源映射map  
    dataSourceMaps.put(DataSourceName.MASTER.getName(), masterDataSource());  
    dataSourceMaps.put(DataSourceName.SLAVE.getName(), slaveDataSource());  
    dynamicDataSource.setTargetDataSources(dataSourceMaps);  
    return dynamicDataSource;  
}

/\*\*  
 \* step3:通过上一步的自定义多数据源路由Bean,指定mybatis配置文件位置和mapper.xml的位置。  
 \* 通过SqlSessionFactoryBean,生成SqlSessionFactory;  
 \* @param dynamicDataSource  
 \* @return  
 \* @throws Exception  
 \*/  
@Bean(name = "sqlSessionFactory")  
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dynamicDataSource) throws Exception {  
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();  
    sqlSessionFactoryBean.setDataSource(dynamicDataSource);  
    //扫描mapper配置  
    sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));  
    //扫描mybatis配置文件  
    sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(configLocation));  
    return sqlSessionFactoryBean.getObject();  
}

/\*\*  
 \* step4:通过上一步的SqlSessionFactory,生成线程安全的SqlSessionTemplate,  
 \* 该Bean为mybatis管理数据库连接的最核心类;  
 \* @param sqlSessionFactory  
 \* @return  
 \* @throws Exception  
 \*/  
@Bean  
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {  
    /\* 使用上面配置的Factory,SqlSessionTemplate是线程安全的,是mybatis的管理数据库的最主要的一个类\*/  
    SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);  
    return template;  
}

/\*\*  
 \*  事务管理PlatformTransactionManager是Spring提供的事务管理的顶层抽象,  
 \*  通过手动配置,来代替默认的自动拾物配置,  
 \*  这样就可以在方法中直接使用注解@Transactional时,指定事务管理的数据源。  
 \* @param dataSource  
 \* @return  
 \*/  
@Bean  
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource){  
    return new DataSourceTransactionManager(dataSource);  
}  

}

5:启动类上,排除spring的自动数据源配置。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringDynamicDatasourceApplication {

public static void main(String\[\] args) {  
    SpringApplication.run(SpringDynamicDatasourceApplication.class, args);  
}

}

6:配置文件:

mybatis的配置文件


spring的启动配置文件

#logging日志配置
logging:
level:
root: info
org:
springframework:
web: DEBUG
##指向mapper的xml文件位置
mybatis:
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: mybatis/mapper/*.xml
# 加载全局的配置文件
configLocation: mybatis/mybatis-config.xml
spring:
datasource:
## master 数据源配置
master:
url: jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: ????
driverClassName: com.mysql.cj.jdbc.Driver
## cluster 数据源配置
slave:
url: jdbc:mysql://localhost:3306/mytest1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: ????
driverClassName: com.mysql.cj.jdbc.Driver