您好,登錄后才能下訂單哦!
SpringBoot項(xiàng)目中如何實(shí)現(xiàn)切換動(dòng)態(tài)數(shù)據(jù)源?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
本文實(shí)現(xiàn)案例場(chǎng)景:
某系統(tǒng)除了需要從自己的主要數(shù)據(jù)庫(kù)上讀取和管理數(shù)據(jù)外,還有一部分業(yè)務(wù)涉及到其他多個(gè)數(shù)據(jù)庫(kù),要求可以在任何方法上可以靈活指定具體要操作的數(shù)據(jù)庫(kù)。
為了在開(kāi)發(fā)中以最簡(jiǎn)單的方法使用,本文基于注解和AOP的方法實(shí)現(xiàn),在spring boot框架的項(xiàng)目中,添加本文實(shí)現(xiàn)的代碼類后,只需要配置好數(shù)據(jù)源就可以直接通過(guò)注解使用,簡(jiǎn)單方便。
一配置二使用
1. 啟動(dòng)類注冊(cè)動(dòng)態(tài)數(shù)據(jù)源
2. 配置文件中配置多個(gè)數(shù)據(jù)源
3. 在需要的方法上使用注解指定數(shù)據(jù)源
1、在啟動(dòng)類添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})
@SpringBootApplication @Import({DynamicDataSourceRegister.class}) // 注冊(cè)動(dòng)態(tài)多數(shù)據(jù)源 public class SpringBootSampleApplication { // 省略其他代碼 }
2、配置文件配置內(nèi)容為: (不包括項(xiàng)目中的其他配置,這里只是數(shù)據(jù)源相關(guān)的)
# 主數(shù)據(jù)源,默認(rèn)的 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 # 更多數(shù)據(jù)源 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); } /** * 不指定數(shù)據(jù)源使用默認(rèn)數(shù)據(jù)源 * * @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; } }); } /** * 指定數(shù)據(jù)源 * * @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; } }); } /** * 指定數(shù)據(jù)源 * * @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時(shí),注解@TargetDataSource 不能直接在接口類Mapper上使用。
按上面的代碼中StudentMapper為接口,代碼如下:
package org.springboot.sample.mapper; import java.util.List; import org.springboot.sample.entity.Student; /** * StudentMapper,映射SQL語(yǔ)句的接口,無(wú)邏輯實(shí)現(xiàn) * * @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); }
請(qǐng)將下面幾個(gè)類放到Spring Boot項(xiàng)目中。
DynamicDataSource.Java
DynamicDataSourceAspect.java
DynamicDataSourceContextHolder.java
DynamicDataSourceRegister.java
TargetDataSource.java
package org.springboot.sample.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動(dòng)態(tài)數(shù)據(jù)源 * * @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; /** * 切換數(shù)據(jù)源Advice * * @author 單紅宇(365384722) * @create 2016年1月23日 */ @Aspect @Order(-1)// 保證該AOP在@Transactional之前執(zhí)行 @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("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源 > {}", 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當(dāng)前是否存在 * * @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; /** * 動(dòng)態(tài)數(shù)據(jù)源注冊(cè)<br/> * 啟動(dòng)動(dòng)態(tài)數(shù)據(jù)源請(qǐng)?jiān)趩?dòng)類中(如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; // 如配置文件中未指定數(shù)據(jù)源類型,使用該默認(rèn)值 private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource"; // private static final Object DATASOURCE_TYPE_DEFAULT = // "com.zaxxer.hikari.HikariDataSource"; // 數(shù)據(jù)源 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>(); // 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中 targetDataSources.put("dataSource", defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); // 添加更多數(shù)據(jù)源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } // 創(chuàng)建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"); } /** * 創(chuàng)建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;// 默認(rèn)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; } /** * 加載多數(shù)據(jù)源配置 */ @Override public void setEnvironment(Environment env) { initDefaultDataSource(env); initCustomDataSources(env); } /** * 初始化主數(shù)據(jù)源 * * @author SHANHY * @create 2016年1月24日 */ private void initDefaultDataSource(Environment env) { // 讀取主數(shù)據(jù)源 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綁定更多數(shù)據(jù) * * @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); // 排除已經(jīng)設(shè)置的屬性 values.remove("type"); values.remove("driver-class-name"); values.remove("url"); values.remove("username"); values.remove("password"); dataSourcePropertyValues = new MutablePropertyValues(values); } dataBinder.bind(dataSourcePropertyValues); } /** * 初始化更多數(shù)據(jù)源 * * @author SHANHY * @create 2016年1月24日 */ private void initCustomDataSources(Environment env) { // 讀取配置文件獲取更多數(shù)據(jù)源,也可以通過(guò)defaultDataSource讀取數(shù)據(jù)庫(kù)獲取更多數(shù)據(jù)源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource."); String dsPrefixs = propertyResolver.getProperty("names"); for (String dsPrefix : dsPrefixs.split(",")) {// 多個(gè)數(shù)據(jù)源 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; /** * 在方法上使用,用于指定使用哪個(gè)數(shù)據(jù)源 * * @author 單紅宇(365384722) * @create 2016年1月23日 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); }
本文代碼博主是經(jīng)過(guò)測(cè)試后沒(méi)有問(wèn)題才發(fā)出來(lái)共享給大家的。對(duì)于連接池參數(shù)配置會(huì)應(yīng)用到所有數(shù)據(jù)源上。
比如配置一個(gè):
spring.datasource.maximum-pool-size=80
那么我們所有的數(shù)據(jù)源都會(huì)自動(dòng)應(yīng)用上。
補(bǔ)充:
如果你使用的是SpringMVC,并集成了Shiro,一般按網(wǎng)上的配置你可能是:
<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>
那么你請(qǐng)不要這樣做,請(qǐng)按下面方法配置:
<!-- AOP式方法級(jí)權(quán)限檢查 --> <!-- 不要使用 DefaultAdvisorAutoProxyCreator 會(huì)出現(xiàn)二次代理的問(wèn)題,這里不詳述。 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>
看完上述內(nèi)容,你們掌握SpringBoot項(xiàng)目中如何實(shí)現(xiàn)切換動(dòng)態(tài)數(shù)據(jù)源的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。