您好,登錄后才能下訂單哦!
SpringBoot項目中如何實現切換動態數據源?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
本文實現案例場景:
某系統除了需要從自己的主要數據庫上讀取和管理數據外,還有一部分業務涉及到其他多個數據庫,要求可以在任何方法上可以靈活指定具體要操作的數據庫。
為了在開發中以最簡單的方法使用,本文基于注解和AOP的方法實現,在spring boot框架的項目中,添加本文實現的代碼類后,只需要配置好數據源就可以直接通過注解使用,簡單方便。
一配置二使用
1. 啟動類注冊動態數據源
2. 配置文件中配置多個數據源
3. 在需要的方法上使用注解指定數據源
1、在啟動類添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})
@SpringBootApplication @Import({DynamicDataSourceRegister.class}) // 注冊動態多數據源 public class SpringBootSampleApplication { // 省略其他代碼 }
2、配置文件配置內容為: (不包括項目中的其他配置,這里只是數據源相關的)
# 主數據源,默認的 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 # 更多數據源 custom.datasource.names=ds1,ds2 custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1 custom.datasource.ds1.username=root custom.datasource.ds1.password=123456 custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2 custom.datasource.ds2.username=root custom.datasource.ds2.password=123456
3、使用方法
package org.springboot.sample.service; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springboot.sample.datasource.TargetDataSource; import org.springboot.sample.entity.Student; import org.springboot.sample.mapper.StudentMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; /** * Student Service * * @author 單紅宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月12日 */ @Service public class StudentService { @Autowired private JdbcTemplate jdbcTemplate; // MyBatis的Mapper方法定義接口 @Autowired private StudentMapper studentMapper; @TargetDataSource(name="ds2") public List<Student> likeName(String name){ return studentMapper.likeName(name); } public List<Student> likeNameByDefaultDataSource(String name){ return studentMapper.likeName(name); } /** * 不指定數據源使用默認數據源 * * @return * @author SHANHY * @create 2016年1月24日 */ public List<Student> getList(){ String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override public Student mapRow(ResultSet rs, int rowNum) throws SQLException { Student stu = new Student(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); return stu; } }); } /** * 指定數據源 * * @return * @author SHANHY * @create 2016年1月24日 */ @TargetDataSource(name="ds1") public List<Student> getListByDs1(){ String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override public Student mapRow(ResultSet rs, int rowNum) throws SQLException { Student stu = new Student(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); return stu; } }); } /** * 指定數據源 * * @return * @author SHANHY * @create 2016年1月24日 */ @TargetDataSource(name="ds2") public List<Student> getListByDs2(){ String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT"; return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override public Student mapRow(ResultSet rs, int rowNum) throws SQLException { Student stu = new Student(); stu.setId(rs.getInt("ID")); stu.setAge(rs.getInt("AGE")); stu.setName(rs.getString("NAME")); stu.setSumScore(rs.getString("SCORE_SUM")); stu.setAvgScore(rs.getString("SCORE_AVG")); return stu; } }); } }
要注意的是,在使用MyBatis時,注解@TargetDataSource 不能直接在接口類Mapper上使用。
按上面的代碼中StudentMapper為接口,代碼如下:
package org.springboot.sample.mapper; import java.util.List; import org.springboot.sample.entity.Student; /** * StudentMapper,映射SQL語句的接口,無邏輯實現 * * @author 單紅宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月20日 */ public interface StudentMapper { // 注解 @TargetDataSource 不可以在這里使用 List<Student> likeName(String name); Student getById(int id); String getNameById(int id); }
請將下面幾個類放到Spring Boot項目中。
DynamicDataSource.Java
DynamicDataSourceAspect.java
DynamicDataSourceContextHolder.java
DynamicDataSourceRegister.java
TargetDataSource.java
package org.springboot.sample.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動態數據源 * * @author 單紅宇(365384722) * @create 2016年1月22日 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
package org.springboot.sample.datasource; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 切換數據源Advice * * @author 單紅宇(365384722) * @create 2016年1月23日 */ @Aspect @Order(-1)// 保證該AOP在@Transactional之前執行 @Component public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(ds)") public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable { String dsId = ds.name(); if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { logger.error("數據源[{}]不存在,使用默認數據源 > {}", ds.name(), point.getSignature()); } else { logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(ds.name()); } } @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, TargetDataSource ds) { logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
package org.springboot.sample.datasource; import java.util.ArrayList; import java.util.List; public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceIds = new ArrayList<>(); public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } /** * 判斷指定DataSrouce當前是否存在 * * @param dataSourceId * @return * @author SHANHY * @create 2016年1月24日 */ public static boolean containsDataSource(String dataSourceId){ return dataSourceIds.contains(dataSourceId); } }
package org.springboot.sample.datasource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; /** * 動態數據源注冊<br/> * 啟動動態數據源請在啟動類中(如SpringBootSampleApplication) * 添加 @Import(DynamicDataSourceRegister.class) * * @author 單紅宇(365384722) * @create 2016年1月24日 */ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); private ConversionService conversionService = new DefaultConversionService(); private PropertyValues dataSourcePropertyValues; // 如配置文件中未指定數據源類型,使用該默認值 private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource"; // private static final Object DATASOURCE_TYPE_DEFAULT = // "com.zaxxer.hikari.HikariDataSource"; // 數據源 private DataSource defaultDataSource; private Map<String, DataSource> customDataSources = new HashMap<>(); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); // 將主數據源添加到更多數據源中 targetDataSources.put("dataSource", defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); // 添加更多數據源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } // 創建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry"); } /** * 創建DataSource * * @param type * @param driverClassName * @param url * @param username * @param password * @return * @author SHANHY * @create 2016年1月24日 */ @SuppressWarnings("unchecked") public DataSource buildDataSource(Map<String, Object> dsMap) { try { Object type = dsMap.get("type"); if (type == null) type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource Class<? extends DataSource> dataSourceType; dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); String driverClassName = dsMap.get("driver-class-name").toString(); String url = dsMap.get("url").toString(); String username = dsMap.get("username").toString(); String password = dsMap.get("password").toString(); DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url) .username(username).password(password).type(dataSourceType); return factory.build(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } /** * 加載多數據源配置 */ @Override public void setEnvironment(Environment env) { initDefaultDataSource(env); initCustomDataSources(env); } /** * 初始化主數據源 * * @author SHANHY * @create 2016年1月24日 */ private void initDefaultDataSource(Environment env) { // 讀取主數據源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource."); Map<String, Object> dsMap = new HashMap<>(); dsMap.put("type", propertyResolver.getProperty("type")); dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name")); dsMap.put("url", propertyResolver.getProperty("url")); dsMap.put("username", propertyResolver.getProperty("username")); dsMap.put("password", propertyResolver.getProperty("password")); defaultDataSource = buildDataSource(dsMap); dataBinder(defaultDataSource, env); } /** * 為DataSource綁定更多數據 * * @param dataSource * @param env * @author SHANHY * @create 2016年1月25日 */ private void dataBinder(DataSource dataSource, Environment env){ RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource); //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext)); dataBinder.setConversionService(conversionService); dataBinder.setIgnoreNestedProperties(false);//false dataBinder.setIgnoreInvalidFields(false);//false dataBinder.setIgnoreUnknownFields(true);//true if(dataSourcePropertyValues == null){ Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties("."); Map<String, Object> values = new HashMap<>(rpr); // 排除已經設置的屬性 values.remove("type"); values.remove("driver-class-name"); values.remove("url"); values.remove("username"); values.remove("password"); dataSourcePropertyValues = new MutablePropertyValues(values); } dataBinder.bind(dataSourcePropertyValues); } /** * 初始化更多數據源 * * @author SHANHY * @create 2016年1月24日 */ private void initCustomDataSources(Environment env) { // 讀取配置文件獲取更多數據源,也可以通過defaultDataSource讀取數據庫獲取更多數據源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource."); String dsPrefixs = propertyResolver.getProperty("names"); for (String dsPrefix : dsPrefixs.split(",")) {// 多個數據源 Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + "."); DataSource ds = buildDataSource(dsMap); customDataSources.put(dsPrefix, ds); dataBinder(ds, env); } } }
package org.springboot.sample.datasource; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 在方法上使用,用于指定使用哪個數據源 * * @author 單紅宇(365384722) * @create 2016年1月23日 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); }
本文代碼博主是經過測試后沒有問題才發出來共享給大家的。對于連接池參數配置會應用到所有數據源上。
比如配置一個:
spring.datasource.maximum-pool-size=80
那么我們所有的數據源都會自動應用上。
補充:
如果你使用的是SpringMVC,并集成了Shiro,一般按網上的配置你可能是:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
那么你請不要這樣做,請按下面方法配置:
<!-- AOP式方法級權限檢查 --> <!-- 不要使用 DefaultAdvisorAutoProxyCreator 會出現二次代理的問題,這里不詳述。 mark by shanhy 2016-05-15 --> <aop:config proxy-target-class="true"/> <!-- 或者你使用了 <aop:aspectj-autoproxy proxy-target-class="true" /> 也可以。 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
看完上述內容,你們掌握SpringBoot項目中如何實現切換動態數據源的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。