1、在启动类添加注解,注册动态多数据源:
1@SpringBootApplication 2@Import({DynamicDataSourceRegister.class}) // 注册动态多数据源 3public class OrangeplusApplication extends WebMvcConfigurerAdapter { 4 5 protected final static Logger logger = LoggerFactory.getLogger(OrangeplusApplication.class); 6 public static void main(String[] args) { 7 SpringApplication.run(OrangeplusApplication.class, args); 8 logger.info("Application is success!"); 9 } 10}
2、yml配置文件中数据源配置内容为:
1spring: 2 profiles: dev 3 datasource: 4 url: jdbc:mysql://127.0.0.1:3306/orangeplus?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull 5 username: root 6 password: 123456 7 driver-class-name: com.mysql.jdbc.Driver 8 9# 更多数据源 10custom: 11 profiles: dev 12 datasource: 13 names: ds1,ds2 14 ds1: 15 driver-class-name: oracle.jdbc.driver.OracleDriver 16 url: jdbc:oracle:thin:@localhost:1521:oracle 17 username: root 18 password: orcl#ncu 19 ds2: 20 driver-class-name: com.mysql.jdbc.Driver 21 url: jdbc:mysql://localhost:3306/orangeplus2?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull 22 username: root 23 password: 123456
3、在需要的方法上使用注解指定数据源,简单代码示例:
1@Service 2public class StudentService { 3 4 @Autowired 5 private JdbcTemplate jdbcTemplate; 6 7 // MyBatis的Mapper方法定义接口 8 @Autowired 9 private StudentMapper studentMapper; 10 11 @TargetDataSource(name="ds2") 12 public List<Student> likeName(String name){ 13 return studentMapper.likeName(name); 14 } 15 16 public List<Student> likeNameByDefaultDataSource(String name){ 17 return studentMapper.likeName(name); 18 } 19 20 /** 21 * 不指定数据源使用默认数据源 22 */ 23 public List<Student> getList(){ 24 String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; 25 return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ 26 27 @Override 28 public Student mapRow(ResultSet rs, int rowNum) throws SQLException { 29 Student stu = new Student(); 30 stu.setId(rs.getInt("ID")); 31 stu.setAge(rs.getInt("AGE")); 32 stu.setName(rs.getString("NAME")); 33 stu.setSumScore(rs.getString("SCORE_SUM")); 34 stu.setAvgScore(rs.getString("SCORE_AVG")); 35 return stu; 36 } 37 38 }); 39 } 40 41 /** 42 * 指定数据源 43 */ 44 @TargetDataSource(name="ds1") 45 public List<Student> getListByDs1(){ 46 String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; 47 return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ 48 49 @Override 50 public Student mapRow(ResultSet rs, int rowNum) throws SQLException { 51 Student stu = new Student(); 52 stu.setId(rs.getInt("ID")); 53 stu.setAge(rs.getInt("AGE")); 54 stu.setName(rs.getString("NAME")); 55 stu.setSumScore(rs.getString("SCORE_SUM")); 56 stu.setAvgScore(rs.getString("SCORE_AVG")); 57 return stu; 58 } 59 60 }); 61 } 62 63 /** 64 * 指定数据源 65 */ 66 @TargetDataSource(name="ds2") 67 public List<Student> getListByDs2(){ 68 String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; 69 return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ 70 71 @Override 72 public Student mapRow(ResultSet rs, int rowNum) throws SQLException { 73 Student stu = new Student(); 74 stu.setId(rs.getInt("ID")); 75 stu.setAge(rs.getInt("AGE")); 76 stu.setName(rs.getString("NAME")); 77 stu.setSumScore(rs.getString("SCORE_SUM")); 78 stu.setAvgScore(rs.getString("SCORE_AVG")); 79 return stu; 80 } 81 82 }); 83 } 84}
要注意的是,在使用MyBatis时,注解@TargetDataSource 不能直接在接口类Mapper上使用。
按上面的代码中StudentMapper为接口,实例代码如下:
1public interface StudentMapper { 2 3 // 多数据源注解 @TargetDataSource 不可以在这里使用 4 List<Student> likeName(String name); 5 6 Student getById(int id); 7 8 String getNameById(int id); 9 10}
请将下面几个类放到Spring Boot项目中:
DynamicDataSource.java
1public class DynamicDataSource extends AbstractRoutingDataSource { 2 3 @Override 4 protected Object determineCurrentLookupKey() { 5 return DynamicDataSourceContextHolder.getDataSourceType(); 6 } 7 8}
DynamicDataSourceAspect.java
1@Aspect 2@Order(-1)// 保证该AOP在@Transactional之前执行 3@Component 4public class DynamicDataSourceAspect { 5 6 private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); 7 8 @Before("@annotation(ds)") 9 public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable { 10 String dsId = ds.name(); 11 if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { 12 logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature()); 13 } else { 14 logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature()); 15 DynamicDataSourceContextHolder.setDataSourceType(ds.name()); 16 } 17 } 18 19 @After("@annotation(ds)") 20 public void restoreDataSource(JoinPoint point, TargetDataSource ds) { 21 logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature()); 22 DynamicDataSourceContextHolder.clearDataSourceType(); 23 } 24 25}
DynamicDataSourceContextHolder.java
1public class DynamicDataSourceContextHolder { 2 3 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 4 public static List<String> dataSourceIds = new ArrayList<>(); 5 6 public static void setDataSourceType(String dataSourceType) { 7 contextHolder.set(dataSourceType); 8 } 9 10 public static String getDataSourceType() { 11 return contextHolder.get(); 12 } 13 14 public static void clearDataSourceType() { 15 contextHolder.remove(); 16 } 17 18 /** 19 * 判断指定DataSrouce当前是否存在 20 * 21 * @param dataSourceId 22 */ 23 public static boolean containsDataSource(String dataSourceId){ 24 return dataSourceIds.contains(dataSourceId); 25 } 26}
DynamicDataSourceRegister.java
1/** 2 * 动态数据源注册<br/> 3 * 启动动态数据源请在启动类中(如SpringBootSampleApplication) 4 * 添加 @Import(DynamicDataSourceRegister.class) 5 * 6 */ 7public class DynamicDataSourceRegister 8 implements ImportBeanDefinitionRegistrar, EnvironmentAware { 9 10 private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); 11 12 private ConversionService conversionService = new DefaultConversionService(); 13 private PropertyValues dataSourcePropertyValues; 14 15 // 如配置文件中未指定数据源类型,使用该默认值 16 private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource"; 17 // private static final Object DATASOURCE_TYPE_DEFAULT = 18 // "com.zaxxer.hikari.HikariDataSource"; 19 20 // 数据源 21 private DataSource defaultDataSource; 22 private Map<String, DataSource> customDataSources = new HashMap<>(); 23 24 @Override 25 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 26 Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); 27 // 将主数据源添加到更多数据源中 28 targetDataSources.put("dataSource", defaultDataSource); 29 DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); 30 // 添加更多数据源 31 targetDataSources.putAll(customDataSources); 32 for (String key : customDataSources.keySet()) { 33 DynamicDataSourceContextHolder.dataSourceIds.add(key); 34 } 35 36 // 创建DynamicDataSource 37 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 38 beanDefinition.setBeanClass(DynamicDataSource.class); 39 beanDefinition.setSynthetic(true); 40 MutablePropertyValues mpv = beanDefinition.getPropertyValues(); 41 mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); 42 mpv.addPropertyValue("targetDataSources", targetDataSources); 43 registry.registerBeanDefinition("dataSource", beanDefinition); 44 45 logger.info("Dynamic DataSource Registry"); 46 } 47 48 /** 49 * 创建DataSource 50 * 51 * @param type 52 * @param driverClassName 53 * @param url 54 * @param username 55 * @param password 56 * @return 57 */ 58 @SuppressWarnings("unchecked") 59 public DataSource buildDataSource(Map<String, Object> dsMap) { 60 try { 61 Object type = dsMap.get("type"); 62 if (type == null) 63 type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource 64 65 Class<? extends DataSource> dataSourceType; 66 dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); 67 68 String driverClassName = dsMap.get("driver-class-name").toString(); 69 String url = dsMap.get("url").toString(); 70 String username = dsMap.get("username").toString(); 71 String password = dsMap.get("password").toString(); 72 73 DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url) 74 .username(username).password(password).type(dataSourceType); 75 return factory.build(); 76 } catch (ClassNotFoundException e) { 77 e.printStackTrace(); 78 } 79 return null; 80 } 81 82 /** 83 * 加载多数据源配置 84 */ 85 @Override 86 public void setEnvironment(Environment env) { 87 initDefaultDataSource(env); 88 initCustomDataSources(env); 89 } 90 91 /** 92 * 初始化主数据源 93 * 94 */ 95 private void initDefaultDataSource(Environment env) { 96 // 读取主数据源 97 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource."); 98 Map<String, Object> dsMap = new HashMap<>(); 99 dsMap.put("type", propertyResolver.getProperty("type")); 100 dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name")); 101 dsMap.put("url", propertyResolver.getProperty("url")); 102 dsMap.put("username", propertyResolver.getProperty("username")); 103 dsMap.put("password", propertyResolver.getProperty("password")); 104 105 defaultDataSource = buildDataSource(dsMap); 106 107 dataBinder(defaultDataSource, env); 108 } 109 110 /** 111 * 为DataSource绑定更多数据 112 * 113 * @param dataSource 114 * @param env 115 */ 116 private void dataBinder(DataSource dataSource, Environment env){ 117 RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource); 118 //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext)); 119 dataBinder.setConversionService(conversionService); 120 dataBinder.setIgnoreNestedProperties(false);//false 121 dataBinder.setIgnoreInvalidFields(false);//false 122 dataBinder.setIgnoreUnknownFields(true);//true 123 if(dataSourcePropertyValues == null){ 124 Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties("."); 125 Map<String, Object> values = new HashMap<>(rpr); 126 // 排除已经设置的属性 127 values.remove("type"); 128 values.remove("driver-class-name"); 129 values.remove("url"); 130 values.remove("username"); 131 values.remove("password"); 132 dataSourcePropertyValues = new MutablePropertyValues(values); 133 } 134 dataBinder.bind(dataSourcePropertyValues); 135 } 136 137 /** 138 * 初始化更多数据源 139 * 140 */ 141 private void initCustomDataSources(Environment env) { 142 // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源 143 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource."); 144 String dsPrefixs = propertyResolver.getProperty("names"); 145 for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源 146 Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + "."); 147 DataSource ds = buildDataSource(dsMap); 148 customDataSources.put(dsPrefix, ds); 149 dataBinder(ds, env); 150 } 151 } 152 153}
TargetDataSource.java
1/** 2 * 在方法上使用,用于指定使用哪个数据源 3 * 4 */ 5@Target({ ElementType.METHOD, ElementType.TYPE }) 6@Retention(RetentionPolicy.RUNTIME) 7@Documented 8public @interface TargetDataSource { 9 String name(); 10}