本文實(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})
1
2
3
4
5
6
|
@SpringBootApplication @Import ({DynamicDataSourceRegister. class }) // 注冊(cè)動(dòng)態(tài)多數(shù)據(jù)源 public class SpringBootSampleApplication { // 省略其他代碼 } |
2、配置文件配置內(nèi)容為: (不包括項(xiàng)目中的其他配置,這里只是數(shù)據(jù)源相關(guān)的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 主數(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、使用方法
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); } /** * 不指定數(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為接口,代碼如下:
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語(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
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; /** * 動(dòng)態(tài)數(shù)據(jù)源 * * @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; /** * 切換數(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(); } } |
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當(dāng)前是否存在 * * @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; /** * 動(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); } } } |
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; /** * 在方法上使用,用于指定使用哪個(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è):
1
|
spring.datasource.maximum-pool-size= 80 |
那么我們所有的數(shù)據(jù)源都會(huì)自動(dòng)應(yīng)用上。
補(bǔ)充:
如果你使用的是SpringMVC,并集成了Shiro,一般按網(wǎng)上的配置你可能是:
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 > |
那么你請(qǐng)不要這樣做,請(qǐng)按下面方法配置:
1
2
3
4
5
6
7
8
|
<!-- 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 > |
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://blog.csdn.net/catoop/article/details/50575038