spring1.x实现读写分离

转载文章:https://www.jb51.net/article/106555.htm
如有侵权立即删除
本文实现案例场景:
某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。

为了在开发中以最简单的方法使用,本文基于注解和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 likeName(String name){
return studentMapper.likeName(name);
}

public List likeNameByDefaultDataSource(String name){
return studentMapper.likeName(name);
}

/**

  • 不指定数据源使用默认数据源

  • @return

  • @author SHANHY

  • @create 2016年1月24日
    */
    public List getList(){
    String sql = “SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT”;
    return (List) jdbcTemplate.query(sql, new RowMapper(){

    @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 getListByDs1(){
    String sql = “SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT”;
    return (List) jdbcTemplate.query(sql, new RowMapper(){

    @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 getListByDs2(){
    String sql = “SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT”;
    return (List) jdbcTemplate.query(sql, new RowMapper(){

    @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 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 contextHolder = new ThreadLocal();
public static List 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;

/**

  • 动态数据源注册
  • 启动动态数据源请在启动类中(如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


那么你请不要这样做,请按下面方法配置:

1
2
3
4
5
6
7
8

<aop:config proxy-target-class=“true”/>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值