本文的特色
- 使用Spring最小化配置,仅用经典的Spring Framework,并借用Spring Boot的ConfigurationProperties注解。
- 最小化配置
- 最小化依赖
实现说明
- 添加依赖,先添加Spring Framework必要依赖。
- 添加Spring Boot 依赖,以使用ConfigurationProperties注解。【可选】
- 实现AbstractRoutingDataSource,存储数据源
- 定义三套数据源
- 定义切面
- 开启最小化必要注解
- 启动入口定义
添加依赖,先添加Spring Framework必要依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
添加其他依赖
Jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
Mysql驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
Mybatis
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
添加Spring Boot 依赖,以使用ConfigurationProperties注解。【可选】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- ForConfigurationProperties -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>${javax.el.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.6.Final</version>
</dependency>
依赖版本
注意:spring-boot的版本和springframework是匹配的
<jackson.version>2.9.4</jackson.version>
<logback.version>1.2.3</logback.version>
<logback.ext.version>0.1.4</logback.ext.version>
<org.springframework.version>4.3.25.RELEASE</org.springframework.version>
<spring-boot.version>1.5.22.RELEASE</spring-boot.version>
<mybatis.version>3.3.1</mybatis.version>
<mybatis-spring.version>1.2.3</mybatis-spring.version>
<mysql.version>5.1.38</mysql.version>
<javax.el.version>3.0.0</javax.el.version>
<validation-api.version>1.1.0.Final</validation-api.version>
实现AbstractRoutingDataSource,存储数据源
DataSourceNames 枚举,用于指定数据源名称
public enum DataSourceNames {
ONE,
TWO,
SLANKKA;
}
DynamicDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<DataSourceNames> dataSourceNameHolder = new ThreadLocal<>();
public DynamicDataSource(
DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(DataSourceNames dataSource) {
dataSourceNameHolder.set(dataSource);
}
public static DataSourceNames getDataSource() {
return dataSourceNameHolder.get();
}
public static void clearDataSource() {
dataSourceNameHolder.remove();
}
}
定义SwitchSource注解,用于在方法上指定数据源
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchSource {
DataSourceNames value() default DataSourceNames.ONE;
}
定义三套数据源
数据源配置类 (Getter Setter省略)
@ConfigurationProperties
public class DatasourceConfig {
private String url;
private String driver;
private String username;
private String password;
}
@Bean("firstDataSource")
public DataSource secondDataSource(@Qualifier("firstDatasourceConfig") @Autowired DatasourceConfig firstDatasourceConfig) {
final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(firstDatasourceConfig.getDriver());
driverManagerDataSource.setUrl(firstDatasourceConfig.getUrl());
driverManagerDataSource.setUsername(firstDatasourceConfig.getUsername());
driverManagerDataSource.setPassword(firstDatasourceConfig.getPassword());
return driverManagerDataSource;
}
@Bean("secondDataSource")
public DataSource secondDataSource(@Qualifier("secondDatasourceConfig") @Autowired DatasourceConfig secondDatasourceConfig) {
final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(secondDatasourceConfig.getDriver());
driverManagerDataSource.setUrl(secondDatasourceConfig.getUrl());
driverManagerDataSource.setUsername(secondDatasourceConfig.getUsername());
driverManagerDataSource.setPassword(secondDatasourceConfig.getPassword());
return driverManagerDataSource;
}
@Bean("thirdDataSource")
public DataSource thirdDataSource(@Qualifier("slankkaDatasourceConfig") @Autowired DatasourceConfig slankkaDatasourceConfig) {
final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(slankkaDatasourceConfig.getDriver());
driverManagerDataSource.setUrl(slankkaDatasourceConfig.getUrl());
driverManagerDataSource.setUsername(slankkaDatasourceConfig.getUsername());
driverManagerDataSource.setPassword(slankkaDatasourceConfig.getPassword());
return driverManagerDataSource;
}
@Bean
@DependsOn(value = {"firstDataSource", "secondDataSource", "thirdDataSource"})
public DynamicDataSource dataSource(
DataSource firstDataSource, DataSource secondDataSource, DataSource thirdDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.ONE, firstDataSource);
targetDataSources.put(DataSourceNames.TWO, secondDataSource);
targetDataSources.put(DataSourceNames.SLANKKA, thirdDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
数据源的配置
@Bean
@ConfigurationProperties
public DatasourceConfig datasourceConfig() {
return new DatasourceConfig();
}
@Bean
@ConfigurationProperties("slankkatwo.datasource")
public DatasourceConfig cloudDatasourceConfig() {
return new DatasourceConfig();
}
@Bean
@ConfigurationProperties("slankkathree.datasource")
public DatasourceConfig platDatasourceConfig() {
return new DatasourceConfig();
}
定义切面
@Aspect
@Component
public class DataSourceAspect implements Ordered {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.slankka.blog.route.SwitchSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SwitchSource ds = method.getAnnotation(SwitchSource.class);
if (ds == null) {
DynamicDataSource.setDataSource(DataSourceNames.ONE);
logger.debug("set datasource <= " + DataSourceNames.ONE);
} else {
DynamicDataSource.setDataSource(ds.value());
logger.debug("set datasource => " + ds.value());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.debug("clean datasource");
}
}
@Override
public int getOrder() {
return 1;
}
}
Mybatis配置
@Bean
public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource)
throws IOException {
final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath*:mybatis/**/*.xml"));
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
//可选
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) throws IOException {
return new JdbcTemplate(dataSource);
}
@Bean
public DataSourceTransactionManager transactionManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
final MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.slankka.blog.dao");
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return mapperScannerConfigurer;
}
开启最小化必要注解
@Configuration
@EnableConfigurationProperties(DatasourceConfig.class)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AnnotationDrivenAppConfig {
//配置类
}
启动入口定义
说明:这里手动启动Spring Framework,没有用到SpringBoot的方式
public class Application {
public static void main(String[] args) {
post();
}
public static void post() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext("com.slankka.blog");
ctx.start();
try{
//business
}finally {
ctx.close();
}
}
}