Springboot项目中自定义注解实现多数据源切换访问
大家好,我是瓜哥。java程序中多数据访问实现的方式有好几种,可以使用开源的第三方插件,可以通过AOP的方式,可以自定义注解。今天我用自定义注解的方式亲自做了一遍。下面总结如下。1、首先新建一个自定义注解ChoiceDataSource@Target({ElementType.METHOD,ElementType.TYPE}) 可以使注解在方法和接口或者类上都可以在springboot项目中定义不
大家好,我是瓜哥。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、测试结果访问
- 浏览器中访问http://localhost:8080/test/getDb1 返回如下结果:
2.浏览器中访问http://localhost:8080/test/getDb2 返回如下结果
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)