大家好,我是瓜哥。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  返回如下结果

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐