Springboot项目中自定义注解实现多数据源切换访问

2 篇文章 0 订阅

大家好,我是瓜哥。java程序中多数据访问实现的方式有好几种,可以使用开源的第三方插件,可以通过AOP的方式,可以自定义注解。今天我用自定义注解的方式亲自做了一遍。下面总结如下。

1、首先新建一个自定义注解ChoiceDataSource

   @Target({ElementType.METHOD,ElementType.TYPE}) 可以使注解在方法和接口或者类上都可以

 在springboot项目中定义不同的数据源

## datasource master #
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db1.url=jdbc:mysql://localhost:3306/test1?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.db1.username=root
spring.datasource.db1.password=123456
## datasource slave #
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db2.url=jdbc:mysql://localhost:3306/test2?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.db2.username=root
spring.datasource.db2.password=123456

 2、自定义数据源配置类DatasourceConfig 

/**
 * 定义数据库实体类并配置为多数据源的形式
 *
 * @author yangdechao
 * @version 1.0
 * @date 2020/07/14 16:36
 */
@Configuration
@MapperScan(basePackages = "cn.com.guage.dynamic.datasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DatasourceConfig {
	@Resource
	@Qualifier(Datasources.DB1)
	private DataSource db1;

	@Resource
	@Qualifier(Datasources.DB2)
	private DataSource db2;

	/**
	 * destroy-method="close"的作用是当数据库连接不使用的时候,就把该连接重新放到数据池中,方便下次使用调用.
	 */
	@Bean(destroyMethod = "close", name = Datasources.DB1)
	@ConfigurationProperties(prefix = "spring.datasource.db1")
	public DataSource dataSource() {
		return DataSourceBuilder.create().type(DruidDataSource.class).build();
	}

	@Bean(destroyMethod = "close", name = Datasources.DB2)
	@ConfigurationProperties(prefix = "spring.datasource.db2")
	public DataSource dataSourceSlave() {
		return DataSourceBuilder.create().type(DruidDataSource.class).build();
	}

	/**
	 * 多数据源配置
	 *
	 * @return DataSource
	 */
	@Bean(name = "dynamicDataSource")
	public DataSource dynamicDataSource() {
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		dynamicDataSource.setDefaultTargetDataSource(db1);
		Map<Object, Object> dsMap = Maps.newHashMap();
		dsMap.put(Datasources.DB1, db1);
		dsMap.put(Datasources.DB2, db2);
		dynamicDataSource.setTargetDataSources(dsMap);
		return dynamicDataSource;
	}

	@Bean(name = "sqlSessionFactory")
	public SqlSessionFactoryBean sqlSessionFactoryBean() {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dynamicDataSource());
		try {
			sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return sqlSessionFactoryBean;
	}
}

3、定义切面类DynamicDataSourceAspect 

 @Before("execution(* cn.com.guage.dynamic.datasource.service.impl..*.*(..))")  中会在cn.com.guage.dynamic.datasource.service.impl包相关类的方法执行之前进行拦截。

method.getDeclaringClass().getAnnotation(ChoiceDataSource.class).value()会获取执行方法所在类上注解ChoiceDataSource的value值。

根据类或者方法上面注解的值就可以判断访问哪个属于源。

/**
 * aop 拦截注解
 *
* @author yangdechao
 * @version 1.0
 * @date 2020/07/13 15:19
 */
@Aspect
@Component
public class DynamicDataSourceAspect {
	
    private final static Logger logger = LoggerFactory.getLogger(DatasourceContextHolder.class);

	/**
	 * 方法执行之前获取方法上面的注解
	 * @param joinPoint
	 */
    //@Before("@annotation(cn.com.guage.dynamic.datasource.annotation.ChoiceDataSource)")
    @Before("execution(* cn.com.guage.dynamic.datasource.service.impl..*.*(..))")
    public void beforeSwitchDS(JoinPoint joinPoint) {
    	logger.info("DynamicDataSourceAspect---------beforeSwitchDS----开始");
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method method = methodSignature.getMethod();
		Boolean isClassPresent = method.getDeclaringClass().isAnnotationPresent(ChoiceDataSource.class);
		String dataSource = DatasourceContextHolder.DEFAULT_DATASOURCE;
		//注解是否在类上
    	logger.info("注解是否在类上:"+isClassPresent);
		if(isClassPresent) {
			dataSource = method.getDeclaringClass().getAnnotation(ChoiceDataSource.class).value();
		}
		logger.info("注解是否在方法上:"+method.isAnnotationPresent(ChoiceDataSource.class));
		// 注释RoutingDataSource是否在方法上。如果在则返回true;不在则返回false
		if (method.isAnnotationPresent(ChoiceDataSource.class)) {
			ChoiceDataSource routingDataSource = method.getDeclaredAnnotation(ChoiceDataSource.class);
			dataSource = routingDataSource.value();
		}
		DatasourceContextHolder.setDB(dataSource);
		logger.info("DynamicDataSourceAspect---------beforeSwitchDS----结束");
    }

    /**
     * 方法使用完后,要清空DatasourceContextHolder
     */
    //@After("@annotation(cn.com.guage.dynamic.datasource.annotation.ChoiceDataSource)")
    @Before("execution(* cn.com.guage.dynamic.datasource.service.impl..*.*(..))")
    public void afterSwitchDS() {
    	logger.info("DynamicDataSourceAspect---------afterSwitchDS----开始");
        DatasourceContextHolder.clearDB();
    	logger.info("DynamicDataSourceAspect---------afterSwitchDS----结束");

    }
}

4、定义DatasourceContextHolder类

用ThreadLocal<String> contextHolder = new ThreadLocal<String>()来存储属于源选项。

/**
 * @author yangdechao
 * @version 1.0
 * @date 2020/07/14 16:36
 */
public class DatasourceContextHolder {
	
    protected final static Logger logger = LoggerFactory.getLogger(DatasourceContextHolder.class);

    public static final String DEFAULT_DATASOURCE = Datasources.DB1;

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static void setDB(String dbType) {
    	logger.info("切换到{}数据源", dbType);
        contextHolder.set(dbType);
    }

    /**
     * 获取数据源名
     */
    public static String getDB() {
        return contextHolder.get();
    }

    /**
     * 清除数据源名
     */
    public static void clearDB() {
        contextHolder.remove();
    }
}

5、在service层配置注解指定相应的数据源。

UserServiceImpl.java中使用DB1,BlogServiceImpl.java使用DB2

/**
 * 
 * @author yangdechao
 * @date 2021-06-24
 */
@Service
@ChoiceDataSource(value = Datasources.DB1)
public class UserServiceImpl implements IUserService 
{
	@Autowired
    private UserMapper userMapper;

    /**
     * 查询【请填写功能名称】
     * 
     * @param id 【请填写功能名称】ID
     * @return 【请填写功能名称】
     */
    @Override
    public User selectUserById(Integer id)
    {
        return userMapper.selectUserById(id);
    }

    /**
     * 查询【请填写功能名称】列表
     * 
     * @param user 【请填写功能名称】
     * @return 【请填写功能名称】
     */
    @Override
    @ChoiceDataSource(value = Datasources.DB2)
    public List<User> selectUserList(User user)
    {
        return userMapper.selectUserList(user);
    }

    /**
     * 新增【请填写功能名称】
     * 
     * @param user 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int insertUser(User user)
    {
        return userMapper.insertUser(user);
    }

    /**
     * 修改【请填写功能名称】
     * 
     * @param user 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int updateUser(User user)
    {
        return userMapper.updateUser(user);
    }

    /**
     * 删除【请填写功能名称】对象
     * 
     * @param ids 需要删除的数据ID
     * @return 结果
     */
    @Override
    public int deleteUserByIds(String ids)
    {
        return userMapper.deleteUserByIds(ConvertUtils.toStrArray(ids));
    }

    /**
     * 删除【请填写功能名称】信息
     * 
     * @param id 【请填写功能名称】ID
     * @return 结果
     */
    @Override
    public int deleteUserById(Integer id)
    {
        return userMapper.deleteUserById(id);
    }

    
}
/**
 * 
 * @author yangdechao
 * @date 2021-06-24
 */
@Service
@ChoiceDataSource(value = Datasources.DB2)
public class BlogServiceImpl implements IBlogService {
	@Autowired
	private BlogMapper blogMapper;

	/**
	 * 查询【请填写功能名称】
	 * 
	 * @param id 【请填写功能名称】ID
	 * @return 【请填写功能名称】
	 */
	@Override
	public Blog selectBlogById(Long id) {
		return blogMapper.selectBlogById(id);
	}

	/**
	 * 查询【请填写功能名称】列表
	 * 
	 * @param blog 【请填写功能名称】
	 * @return 【请填写功能名称】
	 */
	@Override
	public List<Blog> selectBlogList(Blog blog) {
		return blogMapper.selectBlogList(blog);
	}

	/**
	 * 新增【请填写功能名称】
	 * 
	 * @param blog 【请填写功能名称】
	 * @return 结果
	 */
	public int insertBlog(Blog blog) {
		return blogMapper.insertBlog(blog);
	}

	/**
	 * 修改【请填写功能名称】
	 * 
	 * @param blog 【请填写功能名称】
	 * @return 结果
	 */

	public int updateBlog(Blog blog) {
		return blogMapper.updateBlog(blog);
	}

	/**
	 * 删除【请填写功能名称】对象
	 * 
	 * @param ids 需要删除的数据ID
	 * @return 结果
	 */

	public int deleteBlogByIds(String ids) {
		return blogMapper.deleteBlogByIds(ConvertUtils.toStrArray(ids));
	}

	/**
	 * 删除【请填写功能名称】信息
	 * 
	 * @param id 【请填写功能名称】ID
	 * @return 结果
	 */

	public int deleteBlogById(Long id) {
		return blogMapper.deleteBlogById(id);
	}

}

6、定义TestController层来测试是否访问不同数据源

/**
 *数据源访问测试
 * 
 */
@Controller
@RequestMapping("/test")
public class TestController
{
	
	

    @Autowired
    private IUserService userService;
    @Autowired
    private IBlogService blogService;

    @GetMapping("/getDb1")
    @ResponseBody
    public User getDb1()
    {
    	return userService.selectUserById(1);
    }
    @GetMapping("/getDb2")
    @ResponseBody
    public Blog getDb2()
    {
    	return blogService.selectBlogById(1L);
    }
    
    @GetMapping("/getList")
    @ResponseBody
    public List<Blog> getList()
    {
    	return blogService.selectBlogList(null);
    }
    
    @GetMapping("/getUserList")
    @ResponseBody
    public List<User> getUserList()
    {
    	return userService.selectUserList(null);
    }
}

 7、测试结果访问

  1. 浏览器中访问http://localhost:8080/test/getDb1   返回如下结果:
  •  

   2.浏览器中访问http://localhost:8080/test/getDb2  返回如下结果

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
SpringBoot是一个高效的Java开发框架,它能够方便开发者集成MyBatis-Plus实现数据的动态切换以及支持分页查询。MyBatis-Plus是一种优秀的ORM框架,它增强了MyBatis的基础功能,并支持通过注解方式进行映射。 首先,我们需要在pom.xml文件添加MyBatis-Plus和数据库连接池的依赖。在application.yml文件,我们需要配置多个数据和对应的连接信息。我们可以定义一个DataSourceConfig用于获取多个数据,然后在Mapper配置类使用@MapperScan(basePackages = {"com.test.mapper"})来扫描Mapper接口。 要实现动态切换数据,我们可以自定义一个注解@DataSource来标注Mapper接口或方法,然后使用AOP拦截数据切换实现动态切换。在实现分页查询时,我们可以使用MyBatis-Plus提供的分页插件来支持分页查询。 代码示例: 1. 在pom.xml文件添加MyBatis-Plus和数据库连接池的依赖。 ``` <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> </dependencies> ``` 2. 在application.yml文件配置多个数据和对应的连接信息。以两个数据为例: ``` spring: datasource: druid: db1: url: jdbc:mysql://localhost:3306/db1 username: root password: root driver-class-name: com.mysql.jdbc.Driver db2: url: jdbc:mysql://localhost:3306/db2 username: root password: root driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 指定默认数据 primary: db1 ``` 3. 定义一个DataSourceConfig用于获取多个数据。 ``` @Configuration public class DataSourceConfig { @Bean("db1") @ConfigurationProperties("spring.datasource.druid.db1") public DataSource dataSource1() { return DruidDataSourceBuilder.create().build(); } @Bean("db2") @ConfigurationProperties("spring.datasource.druid.db2") public DataSource dataSource2() { return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 设置数据映射关系 Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("db1", dataSource1()); dataSourceMap.put("db2", dataSource2()); dynamicDataSource.setTargetDataSources(dataSourceMap); // 设置默认数据 dynamicDataSource.setDefaultTargetDataSource(dataSource1()); return dynamicDataSource; } } ``` 4. 在Mapper配置类使用@MapperScan(basePackages = {"com.test.mapper"})来扫描Mapper接口,并使用@DataSource注解来标注Mapper接口或方法。 ``` @Configuration @MapperScan(basePackages = {"com.test.mapper"}) public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } } @DataSource("db1") public interface UserMapper { @Select("select * from user where id = #{id}") User selectById(@Param("id") Long id); } ``` 5. 实现AOP拦截数据切换。 ``` @Aspect @Component public class DataSourceAspect { @Before("@annotation(ds)") public void beforeSwitchDataSource(JoinPoint point, DataSource ds) { String dataSource = ds.value(); if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource)) { System.err.println("数据 " + dataSource + " 不存在,使用默认数据"); } else { System.out.println("使用数据:" + dataSource); DynamicDataSourceContextHolder.setDataSourceKey(dataSource); } } } ``` 6. 分页查询的使用示例: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @DataSource("db1") public IPage<User> getUserList(int pageNum, int pageSize) { Page<User> page = new Page<>(pageNum, pageSize); return userMapper.selectPage(page, null); } } ``` 以上就是SpringBoot整合MyBatis-Plus实现数据的动态切换和分页查询的具体实现过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT瓜哥-杨得朝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值