本文實現案例場景:
某系統除了需要從自己的主要數據庫上讀取和管理數據外,還有一部分業務涉及到其他多個數據庫,要求可以在任何方法上可以靈活指定具體要操作的數據庫。
為了在開發中以最簡單的方法使用,本文基于注解和AOP的方法實現,在spring boot框架的項目中,添加本文實現的代碼類后,只需要配置好數據源就可以直接通過注解使用,簡單方便。
一配置二使用
1. 啟動類注冊動態數據源
2. 配置文件中配置多個數據源
3. 在需要的方法上使用注解指定數據源
1、在啟動類添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})
1
2
3
4
5
6
|
@SpringBootApplication @Import ({DynamicDataSourceRegister. class }) // 注冊動態多數據源 public class SpringBootSampleApplication { // 省略其他代碼 } |
2、配置文件配置內容為: (不包括項目中的其他配置,這里只是數據源相關的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 主數據源,默認的 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、使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
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為接口,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
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); } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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(); } |
本文代碼博主是經過測試后沒有問題才發出來共享給大家的。對于連接池參數配置會應用到所有數據源上。
比如配置一個:
1
|
spring.datasource.maximum-pool-size= 80 |
那么我們所有的數據源都會自動應用上。
補充:
如果你使用的是SpringMVC,并集成了Shiro,一般按網上的配置你可能是:
1
2
3
4
5
6
7
|
< 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 > |
那么你請不要這樣做,請按下面方法配置:
1
2
3
4
5
6
7
8
|
<!-- 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 > |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://blog.csdn.net/catoop/article/details/50575038