公司这周开始安排了某一项目的统计需求,考虑到统计需求是很多客户都提到过的共同需求,我们决定将统计单独拆分出来一个微服务,我们需要统计的数据属于不同项目,但数据表和统计结果都是一样的,所以我们使用spring boot框架支持多数据源,从而满足相同的业务,只需要做一次,就可以满足各个项目的统计需求。
下面自己就用SpringBoot + Mybatis框架,简单实现了多数据源的支持。项目搭建就不再一一截图了,有了前面几篇框架的实践,应该是很熟悉了。主要是配置的核心代码:
一. Maven相关依赖
<dependencies>
<!-- springboot核心包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springboot-mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- jdbcTemple -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- springboot测试模块包junit -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 开发测试环境修改文件实时生效包,生产默认不使用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql数据库连接包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
二. application.properties,包含多数据源,mybatis相关配置
server.port=7788
#数据源1 uplus
spring.datasource.db1.url=jdbc:mysql://****:3306/uqierp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db1.username=****
spring.datasource.db1.password=****
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db1.max-idle=10
spring.datasource.db1.max-wait=10000
spring.datasource.db1.min-idle=5
spring.datasource.db1.initial-size=5
#数据源2 第一联盟
spring.datasource.db2.url=jdbc:mysql://****:3306/uqi_union?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db2.username=****
spring.datasource.db2.password=****
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db2.max-idle=10
spring.datasource.db2.max-wait=10000
spring.datasource.db2.min-idle=5
spring.datasource.db2.initial-size=5
#mybatis
mybatis.mapper-locations=classpath*:mapping/*.xml
mybatis.type-aliases-package=com.uqiauto.statistics.model
三. 数据源相关配置核心代码,AOP实现数据源动态切换
1 DataSourceConfig 多数据源配置类:
@Configuration
public class DataSourceConfig {
/**
* 数据源1
* spring.datasource.db1 :application.properteis中对应属性的前缀
* @return
*/
@Bean(name = "datasource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 数据源1
* spring.datasource.db2 :application.properteis中对应属性的前缀
* @return
*/
@Bean(name = "datasource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap();
dsMap.put("datasource1", dataSource1());
dsMap.put("datasource2", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
2 DataSourceContextHolder 数据源相关操作类:
public class DataSourceContextHolder {
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "datasource1";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置数据源名
* @param dbType
*/
public static void setDB(String dbType) {
System.out.println("切换到{"+dbType+"}数据源");
contextHolder.set(dbType);
}
/**
* 获取数据源名
* @return
*/
public static String getDB() {
return (contextHolder.get());
}
/**
* 清除数据源名
*/
public static void clearDB() {
contextHolder.remove();
}
}
3 DS 自定义注解:
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
String value() default "datasource1";
}
4 DynamicDataSource 动态获取数据源:
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
System.out.println("数据源为"+DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
5 DynamicDataSourceAspect AOP实现数据源切换:
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
有了上面的相关配置,基本上就支持多数据源了,下面就是和业务相关的实现。
四. 利用Mybatis的自动生成配置,生成相关的Mapper,model类。在生成的基础上,自己写个查询10条订单的实现,新建了DTO,只包含订单中的个别字段,没有返回所有字段,方便测试。相关代码如下:
1 WmsOrdersMapper.xml
<resultMap id="BaseResultMap" type="com.uqiauto.statistics.dto.WmsOrdersDTO" >
<id column="id_" property="id" jdbcType="INTEGER" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="order_sn" property="orderSn" jdbcType="VARCHAR" />
<result column="order_money" property="orderMoney" jdbcType="DECIMAL" />
</resultMap>
...
<select id="selectAllOrders" resultMap="BaseResultMap">
SELECT id_,create_time,order_sn,order_money FROM wms_orders limit 0,10
</select>
2 WmsOrdersMapper
@Mapper
public interface WmsOrdersMapper {
int deleteByPrimaryKey(Integer id);
int insert(WmsOrders record);
...
List<WmsOrders> selectAllOrders();
}
3 WmsOrdersService
@Service
public class WmsOrdersService {
@Resource
private WmsOrdersMapper wmsOrdersMapper;
/**
* 设置了默认数据源为数据源1,不需要加自定义注解
* @return
* /
public List<WmsOrders> selectErpOrders(){
return wmsOrdersMapper.selectAllOrders();
}
/**
* 使用数据源2查询
* @return
*/
@DS("datasource2")
public List<WmsOrders> selectUnionOrders(){
return wmsOrdersMapper.selectAllOrders();
}
}
4 WmsOrdersController
@RestController
@RequestMapping("/statistics")
public class WmsOrdersController {
@Autowired
private WmsOrdersService wmsOrdersService;
@RequestMapping(value = "/getErpOrders",method= RequestMethod.GET)
public List<WmsOrders> getErpOrders() {
List<WmsOrders> list = wmsOrdersService.selectErpOrders();
return list;
}
@RequestMapping(value = "/getUnionOrders",method=RequestMethod.GET)
public List<WmsOrders> getUnionOrders() {
List<WmsOrders> list = wmsOrdersService.selectUnionOrders();
return list;
}
}
五. 接口测试结果:
六. 思考总结:
这样的一个框架,基本上算是简单支持了项目中的多数据源以及动态切换数据源,底层可以共用一套,满足了不用在各个系统中都单独去实现。但框架还是存在各种问题,例如不支持可配置,再新添一个数据源的情况下,要新增不少代码,想着应该实现所有的业务都只写一套,根据前端传值去数据库中读取数据源的相关信息,而不需要改动任何业务代码。当新增一个数据源,也只需要去数据库中添加数据源的相关信息即可。所以,项目框架还需要进一步优化。