项目中经常会出现需要同时连接两个数据源的情况,这里还是演示基于MyBatis来配置两个数据源,并演示如何切换不同的数据源。
网上的一些例子都写的有点冗余,这里我通过自定义注解+AOP的方式,来简化这种数据源的切换操作。
maven依赖
初始化数据库
这里我们需要创建两个数据库,初始化脚本如下:
可以看到我创建了两个数据库pos和biz,同时还初始化了用户表,并分别插入两条初始数据。注意用户名数据不相同。
配置文件
接下来修改application.yml配置文件,如下:
解释一下:
这里我添加了一个自定义配置项muti-datasource-open,用来控制是否开启多数据源支持。这个配置项后面我会用到。 接下来定义MyBatis的配置,最后定义了两个MySQL数据库的连接信息,一个是pos库,一个是biz库。
动态切换数据源
这里通过Spring的AOP技术实现数据源的动态切换。
多数据源的常量类:
1234
public interface DSEnum { String DATA_SOURCE_CORE = "dataSourceCore"; //核心数据源 String DATA_SOURCE_BIZ = "dataSourceBiz"; //其他业务的数据源}
datasource的上下文,用来存储当前线程的数据源类型:
1234567891011121314151617181920212223242526
public class DataSourceContextHolder { private static final ThreadLocalcontextHolder = new ThreadLocal(); /** * @param dataSourceType 数据库类型 * @Description: 设置数据源类型 */ public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } /** * @Description: 获取数据源类型 */ public static String getDataSourceType() { return contextHolder.get(); } /** * @Description: 清除数据源类型 */ public static void clearDataSourceType() { contextHolder.remove(); }}
定义动态数据源,继承AbstractRoutingDataSource:
1234567
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); }}
接下来自定义一个注解,用来在Service方法上面注解使用哪个数据源:
1234567891011
/** * 多数据源标识 * * @author xiongneng */@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface DataSource { String name() default "";}
最后,最核心的AOP类定义:
/** * 多数据源切换的aop * * @author xiongneng */@Aspect@Component@ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "true")public class MultiSourceExAop implements Ordered { private Logger log = Logger.getLogger(this.getClass()); @Pointcut(value = "@annotation(com.xncoding.pos.common.annotion.DataSource)") private void cut() { } @Around("cut()") public Object around(ProceedingJoinPoint point) throws Throwable { Signature signature = point.getSignature(); MethodSignature methodSignature = null; if (!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } methodSignature = (MethodSignature) signature; Object target = point.getTarget(); Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); DataSource datasource = currentMethod.getAnnotation(DataSource.class); if (datasource != null) { DataSourceContextHolder.setDataSourceType(datasource.name()); log.debug("设置数据源为:" + datasource.name()); } else { DataSourceContextHolder.setDataSourceType(DSEnum.DATA_SOURCE_CORE); log.debug("设置数据源为:dataSourceCore"); } try { return point.proceed(); } finally { log.debug("清空数据源信息!"); DataSourceContextHolder.clearDataSourceType(); } } /** * aop的顺序要早于spring的事务 */ @Override public int getOrder() { return 1; }}
这里使用到了注解@ConditionalOnProperty,只有当我的配置文件中muti-datasource-open=true的时候注解才会生效。
另外还有一个要注意的地方,就是order,aop的顺序一定要早于spring的事务,这里我将它设置成1,后面你会看到我将spring事务顺序设置成2。
配置类
首先有两个属性类:
DruidProperties连接池的属性类MutiDataSourcePropertiesbiz数据源的属性类
然后定义配置类:
@Configuration@EnableTransactionManagement(order = 2)@MapperScan(basePackages = {"com.xncoding.pos.common.dao.repository"})public class MybatisPlusConfig { @Autowired DruidProperties druidProperties; @Autowired MutiDataSourceProperties mutiDataSourceProperties; /** * 核心数据源 */ private DruidDataSource coreDataSource() { DruidDataSource dataSource = new DruidDataSource(); druidProperties.config(dataSource); return dataSource; } /** * 另一个数据源 */ private DruidDataSource bizDataSource() { DruidDataSource dataSource = new DruidDataSource(); druidProperties.config(dataSource); mutiDataSourceProperties.config(dataSource); return dataSource; } /** * 单数据源连接池配置 */ @Bean @ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "false") public DruidDataSource singleDatasource() { return coreDataSource(); } /** * 多数据源连接池配置 */ @Bean @ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "true") public DynamicDataSource mutiDataSource() { DruidDataSource coreDataSource = coreDataSource(); DruidDataSource bizDataSource = bizDataSource(); try { coreDataSource.init(); bizDataSource.init(); } catch (SQLException sql) { sql.printStackTrace(); } DynamicDataSource dynamicDataSource = new DynamicDataSource(); HashMaphashMap = new HashMap<>(); hashMap.put(DSEnum.DATA_SOURCE_CORE, coreDataSource); hashMap.put(DSEnum.DATA_SOURCE_BIZ, bizDataSource); dynamicDataSource.setTargetDataSources(hashMap); dynamicDataSource.setDefaultTargetDataSource(coreDataSource); return dynamicDataSource; }}
代码其实很好理解,我就不再多做解释了。
然后步骤跟普通的集成MyBatis是一样的,我简单的过一遍。
实体类
@TableName(value = "t_user")public class User extends Model{private static final long serialVersionUID = 1L; /** * 主键ID */ @TableId(value="id", type= IdType.AUTO) private Integer id; /** * 账号 */ private String username; /** * 名字 */ private String name; /** * 密码 */ private String password; /** * md5密码盐 */ private String salt; /** * 联系电话 */ private String phone; /** * 备注 */ private String tips; /** * 状态 1:正常 2:禁用 */ private Integer state; /** * 创建时间 */ private Date createdTime; /** * 更新时间 */ private Date updatedTime; // 省略getter/setter方法}
定义DAO
123
public interface UserMapper extends BaseMapper{}
定义Service
@Servicepublic class UserService { @Resource private UserMapper userMapper; /** * 通过ID查找用户 * @param id * @return */ public User findById(Integer id) { return userMapper.selectById(id); } /** * 通过ID查找用户 * @param id * @return */ @DataSource(name = DSEnum.DATA_SOURCE_BIZ) public User findById1(Integer id) { return userMapper.selectById(id); } /** * 新增用户 * @param user */ public void insertUser(User user) { userMapper.insert(user); } /** * 修改用户 * @param user */ public void updateUser(User user) { userMapper.updateById(user); } /** * 删除用户 * @param id */ public void deleteUser(Integer id) { userMapper.deleteById(id); }}
这里唯一要说明的就是我在方法findById1()上面增加了注解@DataSource(name = DSEnum.DATA_SOURCE_BIZ),这样这个方法就会访问biz数据库。
注意,不加注解就会访问默认数据库pos。
测试
最后编写一个简单的测试,我只测试findById()方法和findById1()方法,看它们是否访问的是不同的数据源。
12345678910111213141516171819202122
@RunWith(SpringRunner.class)@SpringBootTestpublic class ApplicationTests { private static final Logger log = LoggerFactory.getLogger(ApplicationTests.class); @Resource private UserService userService; /** * 测试增删改查 */ @Test public void test() { // 核心数据库中的用户id=1 User user = userService.findById(1); assertThat(user.getUsername(), is("admin")); // biz数据库中的用户id=1 User user1 = userService.findById1(1); assertThat(user1.getUsername(), is("admin1")); }}
运行测试,结果显示为green bar,成功!
1月8日,船舶在黄骅港码头装运煤炭。黄骅港煤炭堆场转运设备在作业(1月8日摄,无人机照片)。 位于河北沧州的黄骅港是西煤东运、北煤南运更多
2023-01-10 10:12:461月7日,2022年煤矿智能化重大进展发布会在京召开。本次发布会旨在展示交流2022年煤矿智能化科技创新和建设成果,总结推广经验,发挥先进更多
2023-01-10 10:16:42记者从1月5日召开的2023年全区能源工作会议上获悉,2023年,我区将继续肩负起保障国家能源安全的重大政治责任,全力以赴保障能源安全稳定更多
2023-01-10 10:00:38据中国煤炭工业协会统计与信息部初步统计,2022年,全国原煤产量超5000万吨企业15家,与去年持平。产量合计约为259亿吨,较去年增加约13亿更多
2023-01-10 09:55:092022年,虽然受到国际能源市场持续紧张、高温干旱暴雨极端天气、疫情扰动以及输入性通胀等因素影响,但是在政策调控和市场机制的配合下,更多
2023-01-10 10:15:312022年,黑龙江省煤矿安全生产取得历史性突破。全省煤矿实现零死亡,事故起数、死亡人数同比分别减少3起、5人;全省煤矿连续14个月未发生更多
2023-01-09 10:01:03福建省发改委近日印发关于做好2023年一季度经济工作若干措施的通知。通知指出,切实保障能源供应。发挥煤电油气运保障工作协调机制作用,更多
2023-01-09 09:59:331月6日从重庆市经济信息委获悉,2022年,陕煤入渝规模达17983万吨,同比增长132%,创历年新高,按照每一列3000吨计算,相当于每天17列运煤更多
2023-01-09 10:02:12沙漠采气,中国石油塔里木油田19座大中型气田开足马力,日产量超过1亿立方米;西煤东运,承担全国铁路煤运总量近15的大秦铁路优化调度,日更多
2023-01-09 10:00:101宏观预期最差的时候可能已经过去,市场将在强预期与弱恢复中反复拉扯。 疫情防控政策优化以来市场不断抬高对复工复产和经济复苏的预期更多
2023-01-09 09:54:59