前言

  通过注解和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<UserBean> getAllUser();
}

dao接口

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wht.springdynamicdatasource.dao.UserDao">
<!-- 创建用户(Create) -->
<insert id="insertUser" parameterType="com.wht.springdynamicdatasource.entity.UserBean">
insert into users(name,password) values(#{name},#{password})
</insert> <!-- 查询全部用户 -->
<select id="getAllUser" resultType="com.wht.springdynamicdatasource.entity.UserBean">
select * from users
</select>
</mapper>

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<String> 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的配置文件

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局参数 -->
<settings>
<!-- 使全局的映射器启用或禁用缓存。 -->
<setting name="cacheEnabled" value="false"/>
<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
<setting name="aggressiveLazyLoading" value="true"/>
<!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
<setting name="useColumnLabel" value="true"/>
<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 使用驼峰命名法转换字段。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
</configuration>

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

SpringAOP--动态数据源的更多相关文章

  1. SSM配置动态数据源

    多数据源配置主要涉及自定义类(DataSource注解类.DataSourceAspect切面类,动态数据源接口实现类.以及数据源字符串线程保存类),pom.xml文件.applicationCont ...

  2. Spring 的动态数据源实现

    1. 配置多个数据源 这里以两个c3p0数据库连接池的数据源作为实例.在Spring框架下使用c3p0的数据库需要加入c3p0-0.9.1.2.jar(现在最新的)这个支持包.这里以数据同步项目为例: ...

  3. SSH配置动态数据源

    用到一个项目,需要整合2个不同的数据库! 现将代码贴下,以备后用: 1.创建静态映射类,该类映射动态数据源 public class DataSourceMap { public static fin ...

  4. spring 动态数据源

    1.动态数据源:  在一个项目中,有时候需要用到多个数据库,比如读写分离,数据库的分布式存储等等,这时我们要在项目中配置多个数据库. 2.原理:   (1).spring 单数据源获取数据连接过程: ...

  5. dubbo服务+Spring事务+AOP动态数据源切换 出错

    1:问题描述,以及分析 项目用了spring数据源动态切换,服务用的是dubbo.在运行一段时间后程序异常,更新操作没有切换到主库上. 这个问题在先调用读操作后再调用写操作会出现. 经日志分析原因: ...

  6. Spring动态数据源的配置

    Spring动态数据源 我们很多项目中业务都需要涉及到多个数据源,就是对不同的方法或者不同的包使用不同的数据源.最简单的做法就是直接在Java代码里面lookup需要的数据源,但是这种做法耦合性太高, ...

  7. Java注解--实现动态数据源切换

    当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换. 实现原理 在Spring 2.0.1中引入了Abstract ...

  8. 如何通过Spring Boot配置动态数据源访问多个数据库

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...

  9. AbstractRoutingDataSource实现动态数据源切换 专题

    需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...

  10. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

随机推荐

  1. Netty源码解析一——线程池模型之线程池NioEventLoopGroup

    本文基础是需要有Netty的使用经验,如果没有编码经验,可以参考官网给的例子:https://netty.io/wiki/user-guide-for-4.x.html.另外本文也是针对的是Netty ...

  2. tomcat 上传文件权限不足

    参考:https://www.cnblogs.com/houchaoying/p/8652040.html tomcat-bin-catalina.sh UMASK="0027" ...

  3. 使用Java的GUI技术实现 “ 贪吃蛇 ” 游戏

    详细教程: 使用Java的GUI技术实现 " 贪吃蛇 " 游戏_IT打工酱的博客-CSDN博客

  4. k8s节点简介、移除节点、新增节点

    简介 Node是Pod真正运行的主机,可以是物理机也可以是虚拟机. Node本质上不是Kubernetes来创建的, Kubernetes只是管理Node上的资源. 为了管理Pod,每个Node节点上 ...

  5. springMVC整合mybatis,spring

    使用spring-mvc创建一个项目的过程 spring的配置十分复杂,很难记忆. 这篇博客用于记录springmvc整合创建过程,虽然步骤有点多,但是每一步都很容易理解,便于以后忘记后参考和记忆. ...

  6. IDE 、SDK 、API区别、库、框架、组件、CLI

    IDE:集成开发环境:包括代码编辑器.代码检测.代码调试器.译器/解释器.以及其他工具 SDK:SDK是IDE的基础引擎 ,比IDE更基本,因为它通常没有图形工具.工程师为辅助开发某类软件的相关文档. ...

  7. Mysql基础学习第二天

    Mysql基础学习第二天 函数 函数:是指一段可以直接被另一段程序调用的程序或代码. 字符串函数 数值函数 日期函数 流程函数 字符串函数 MySQL内置很多字符串函数,常用的几个如下: 函数 功能 ...

  8. VT 入门篇——最小 VT 实现(上)

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. Docker - 安装&测试

    一.什么是Docker Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中 ...

  10. quartz框架(七)-JobStore

    JobStore 在之前的博文中,博主已经写了关于Job的相关内容.本篇博文,博主将介绍JobStore相关的内容. JobStore是存放Job和Trigger的地方.当我们调用Scheduler对 ...