spring boot 整合mybatis、druid以AOP+自定义注解方式实现多数据源,读写分离

        众所周知,当应用的访问量变大的时候,数据库的压力也会随之增大,这个时候除了可以使用缓存为数据库分担压力外,还可以使用备库做读写分离来分担主库压力。

        最近项目可能要实现读写分离,看了不少资料,先实践实践,这里使用springboot整合mybatis、druid以AOP+自定义注解方式实现多数据源,读写分离。demo项目大致实现如下:

        首先,新建一个maven项目:DynamicDataSource,在pom文件中引入springboot、mybatis、druid、AOP、connector相关依赖,具体如下:

	
		org.springframework.boot
		spring-boot-starter-web
	
	
		org.mybatis.spring.boot
		mybatis-spring-boot-starter
		2.0.0
	
	
		org.mybatis.generator
		mybatis-generator-core
		1.3.5
	
	
		com.alibaba
		druid
		1.1.0
	
	
		mysql
		mysql-connector-java
	
	
		org.springframework.boot
		spring-boot-starter-aop
	

        接着新建application.yml配置文件,并配置两个数据源,如下:

server:
  port: 8080
  servlet:
    context-path: /demo

spring:
  datasource:
    # master config
    master:
      url: jdbc:mysql://192.168.1.102:3306/test?characterEncoding=utf-8
      username: root
      password:
      driver-class-name: com.mysql.jdbc.Driver
    # slave config
    slave:
      url: jdbc:mysql://192.168.1.103:3306/test?characterEncoding=utf-8
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver

        同时新建数据源配置类:DataSourceConfig,具体如下:

/**
 * 数据源相关配置
 */
@Configuration
@MapperScan(basePackages = "com.spring.boot.demo.DynamicDataSource.mapper")
public class DataSourceConfig {

	@Bean(name = "dataSourceMaster")
	@Primary // 当有多个实现类型时,注解了@Primary则优先使用
	public DataSource dataSourceMaster(MasterConfig masterConfig) throws Exception {
		DruidDataSource druidDataSourceMaster = new DruidDataSource();
		druidDataSourceMaster.setUrl(masterConfig.getUrl());
		druidDataSourceMaster.setUsername(masterConfig.getUsername());
		druidDataSourceMaster.setPassword(masterConfig.getPassword());
		druidDataSourceMaster.setDriverClassName(masterConfig.getDriverClassName());
		return druidDataSourceMaster;
	}

	@Bean(name = "dataSourceSlave")
	public DataSource dataSourceSlave(SlaveConfig slaveConfig) throws Exception {
		DruidDataSource druidDataSourceSalve = new DruidDataSource();
		druidDataSourceSalve.setUrl(slaveConfig.getUrl());
		druidDataSourceSalve.setUsername(slaveConfig.getUsername());
		druidDataSourceSalve.setPassword(slaveConfig.getPassword());
		druidDataSourceSalve.setDriverClassName(slaveConfig.getDriverClassName());
		return druidDataSourceSalve;
	}

	/**
	 * 动态数据源,配置需要使用到的多个数据源
	 * @param master
	 * @param slave
	 * @return
	 */
	@Bean
	public DynamicDataSource dynamicDataSource(@Qualifier("dataSourceMaster") DataSource master, @Qualifier("dataSourceSlave") DataSource slave) {
		Map targetDataSources = new HashMap<>();
		targetDataSources.put(DataSourceEnum.DATA_SOURCE_MASTER, master); // 添加主库源
		targetDataSources.put(DataSourceEnum.DATA_SOURCE_SLAVE, slave); // 添加从库源
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		dynamicDataSource.setDefaultTargetDataSource(master); // 默认使用主库源
		dynamicDataSource.setTargetDataSources(targetDataSources);
		return dynamicDataSource;
	}

	@Bean
	public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
		return new DataSourceTransactionManager(dynamicDataSource);
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
		sessionFactory.setDataSource(dynamicDataSource);
		sessionFactory.setMapperLocations(((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
				.getResources("classpath*:/mapping/*Mapper.xml"));
		return sessionFactory.getObject();
	}
}

        MasterConfig、SlaveConfig是两个配置文件类,这里略过,DataSourceEnum是数据源枚举类,如下:

/**
 * 数据源枚举
 */
public enum DataSourceEnum {
	/**
	 * 主库源
	 */
	DATA_SOURCE_MASTER,
	/**
	 * 从库源
	 */
	DATA_SOURCE_SLAVE;
}

        DynamicDataSourceContextHolder是一个线程的上下文类,为每个线程保存要使用的数据源类型,如下:

public class DynamicDataSourceContextHolder {

	// 定义一个ThreadLocal变量,保存数据源类型(保证线程安全,多个线程之间互不影响)
	private static final ThreadLocal DATA_SOURCE_CONTEXT_HOLDER = new ThreadLocal<>();

	public static final DataSourceEnum DEFAULT_DATA_SOURCE = DataSourceEnum.DATA_SOURCE_MASTER;
	
	static {
		setDefaultDataSource(); // 默认指定主库
	}
	
	public static void setDefaultDataSource() {
		DATA_SOURCE_CONTEXT_HOLDER.set(DEFAULT_DATA_SOURCE);
	}

	public static void setDataSource(DataSourceEnum dataSourceEnum) {
		DATA_SOURCE_CONTEXT_HOLDER.set(dataSourceEnum);
	}

	public static DataSourceEnum getDataSource() {
		return DATA_SOURCE_CONTEXT_HOLDER.get();
	}
}

        DynamicDataSource是自定义的动态数据源类,继承Spring的AbstractRoutingDataSource类(DataSource的路由类),重写determineCurrentLookupKey()方法,Spring通过此方法的返回值从targetDataSources中获取数据源(即动态切换数据源),如下:

/**
 * 自定义动态数据源类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

	private final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

	@Override
	protected Object determineCurrentLookupKey() {
		DataSourceEnum dataSourceEnum = DynamicDataSourceContextHolder.getDataSource();
		logger.info("当前使用数据源为:{}", dataSourceEnum);
		return dataSourceEnum;
	}
}

        然后新建数据源注解DataSourceAnnotation,并增加属性value,通过value指定要使用的数据源,默认值是主库,如下:

/**
 * 自定义数据源注解
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceAnnotation {
	DataSourceEnum value() default DataSourceEnum.DATA_SOURCE_MASTER;
}

        新建数据源注解的切面实现类:DynamicDataSourceAspect,通过切面获取注解的数据源类型,并保存,具体实现如下:

/**
 * 数据源注解切面实现
 */
@Aspect
@Component
public class DynamicDataSourceAspect {

	@Before("@annotation(dataSourceAnnotation)")
	public void before(JoinPoint point, DataSourceAnnotation dataSourceAnnotation) {
		Class clazz = point.getTarget().getClass();
		MethodSignature signature = (MethodSignature) point.getSignature();
		try {
			Method method = clazz.getMethod(signature.getName(), signature.getParameterTypes());
			if (method.isAnnotationPresent(DataSourceAnnotation.class)) {
				// 根据注解设置数据源
				DataSourceAnnotation annotation = method.getAnnotation(DataSourceAnnotation.class);
				DynamicDataSourceContextHolder.setDataSource(annotation.value());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@After("@annotation(dataSourceAnnotation)")
	public void after(JoinPoint point, DataSourceAnnotation dataSourceAnnotation) {
		DynamicDataSourceContextHolder.setDefaultDataSource();
	}
}

        到这里可以看到AOP+注解实现多数据源主要实现思路如下:

        1、通过AOP获取注解的数据源类型并保存到ThreadLocal中(主要实现类:DynamicDataSourceAspect)

        2、Spring就可以通过ThreadLocal获取当前线程的数据源类型进行切换(主要实现类:DynamicDataSource)

        最后编写一个测试的service,使用上面的数据源注解,如下:

@Service
public class TestServiceImpl implements ITestService {

	@Autowired
	private TestMapper testMapper;
	
	@Override
	@DataSourceAnnotation(DataSourceEnum.DATA_SOURCE_MASTER) // 指定主库
	public int insert(Test test) {
		// TODO Auto-generated method stub
		return testMapper.insert(test);
	}

	@Override
	@DataSourceAnnotation(DataSourceEnum.DATA_SOURCE_SLAVE) // 指定从库
	public Test select(Long id) {
		// TODO Auto-generated method stub
		return testMapper.selectByPrimaryKey(id);
	}

	@Override
	@DataSourceAnnotation // 不指定,默认主库
	public int updateByPrimaryKey(Test test) {
		// TODO Auto-generated method stub
		return testMapper.updateByPrimaryKey(test);
	}

	@Override // 无注解,默认主库
	public int delete(Long id) {
		// TODO Auto-generated method stub
		return testMapper.delete(id);
	}

}

        同时修改Application启动类,注入service,测试测试效果,如下:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) // 去掉Spring的数据源自动配置
public class DynamicDateSourceApplication {
	
	public static void main(String[] args) {
		SpringApplication.run(DynamicDateSourceApplication.class, args);
	}
	
	@Autowired
	private ITestService iTestService;
	
	@PostConstruct
	public void initTest() {
		// init data
		Test insert = new Test();
		insert.setName("insert");
		// insert
		iTestService.insert(insert);
		Long id = insert.getId();
		System.out.println(id);
		// select
		Test select = iTestService.select(id);
		System.out.println(select.toString());
		// update
		select.setName("update");
		int update = iTestService.updateByPrimaryKey(select);
		System.out.println(update);
		// delete
		int delete = iTestService.delete(id);
		System.out.println(delete);
	}
	
}

        运行效果如下:

spring boot 整合mybatis、druid以AOP+自定义注解方式实现多数据源,读写分离_第1张图片

        demo:https://github.com/191720653/DynamicDataSource.git

你可能感兴趣的:(springboot)