Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Module reuse base data source #370

Merged
merged 18 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion samples/springboot-samples/db/mybatis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ biz 包含两个模块,分别为 biz1 和 biz2, 都是普通 springboot,修
注意这里将不同 biz 的web context path 修改成不同的值,以此才能成功在一个 tomcat host 里安装多个 web 应用。


## 实验步骤
## 基座、模块各自定义数据源

### 本地部署 mysql 并启动

Expand Down Expand Up @@ -191,6 +191,77 @@ curl http://localhost:8080/biz1mybatis/testmybatis
```
返回 student 表中的内容,且可以发现使用的数据源已经变为 DruidDataSource

## 模块复用基座数据源

### 修改模块配置

在上一节「基座、模块各自定义数据源」的基础上

1. 移除模块数据源配置,在biz的application.properties文件中注释掉数据源datasource相关配置项
```properties
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.username=root
#spring.datasource.password=Zfj1995!
#spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
#
#mybatis.mapper-locations=classpath:mappers/*.xml
#
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.druid.initial-size=5
#spring.datasource.druid.min-idle=5
#spring.datasource.druid.max-active=200
#spring.datasource.druid.max-wait=60000
#spring.datasource.druid.time-between-eviction-runs-millis=60000
#spring.datasource.druid.min-evictable-idle-time-millis=300000
#spring.datasource.druid.test-while-idle=true
#spring.datasource.druid.test-on-borrow=false
#spring.datasource.druid.test-on-return=false
#spring.datasource.druid.pool-prepared-statements=false
#spring.datasource.druid.filters=stat,wall,slf4j
```

2. 添加模块MybatisConfig
```java
@Configuration
@MapperScan(basePackages = "com.alipay.sofa.biz1.mapper", sqlSessionFactoryRef = "mysqlSqlFactory")
@EnableTransactionManagement
public class MybatisConfig {

//tips:不要初始化一个基座的DataSource,当模块被卸载的是,基座数据源会被销毁,transactionManager,transactionTemplate,mysqlSqlFactory被销毁没有问题

@Bean(name = "transactionManager")
public PlatformTransactionManager platformTransactionManager() {
return (PlatformTransactionManager) getBaseBean("transactionManager");
}

@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate() {
return (TransactionTemplate) getBaseBean("transactionTemplate");
}

@Bean(name = "mysqlSqlFactory")
public SqlSessionFactoryBean mysqlSqlFactory() throws IOException {
//数据源不能申明成模块spring上下文中的bean,因为模块卸载时会触发close方法

DataSource dataSource = (DataSource) getBaseBean("dataSource");
SqlSessionFactoryBean mysqlSqlFactory = new SqlSessionFactoryBean();
mysqlSqlFactory.setDataSource(dataSource);
mysqlSqlFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mappers/*.xml"));
return mysqlSqlFactory;
}

private Object getBaseBean(String name) {
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
Biz masterBiz = ArkClient.getMasterBiz();
BizRuntimeContext bizRuntimeContext = BizRuntimeContextRegistry.getBizRuntimeContext(masterBiz);
return bizRuntimeContext.getRootApplicationContext().getBean(name);
}
}
```

同上一节「基座、模块各自定义数据源」启动基座、部署模块、发起验证即可。


## 注意事项
这里主要使用简单应用做验证,如果复杂应用,需要注意模块做好瘦身,基座有的依赖,模块尽可能设置成 provided,尽可能使用基座的依赖。

18 changes: 12 additions & 6 deletions samples/springboot-samples/db/mybatis/biz1/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
<scope>test</scope>
</dependency>

<!-- <dependency>-->
<!-- <groupId>com.alipay.sofa.serverless</groupId>-->
<!-- <artifactId>sofa-serverless-app-starter</artifactId>-->
<!-- <version>${sofa.serverless.runtime.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alipay.sofa.serverless</groupId>
<artifactId>sofa-serverless-app-starter</artifactId>
<version>${sofa.serverless.runtime.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.kafka</groupId>-->
<!-- <artifactId>spring-kafka</artifactId>-->
Expand All @@ -71,6 +71,12 @@
<version>${sofa.serverless.runtime.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-api</artifactId>
<version>${sofa.ark.version}</version>
<scope>provided</scope>
</dependency>

<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
@MapperScan(basePackages = "com.alipay.sofa.biz1.mapper")
//@MapperScan(basePackages = "com.alipay.sofa.biz1.mapper")
public class Biz1Application {
private static Logger LOGGER = LoggerFactory.getLogger(Biz1Application.class);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.alipay.sofa.biz1;

import com.alipay.sofa.ark.api.ArkClient;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.serverless.common.BizRuntimeContext;
import com.alipay.sofa.serverless.common.BizRuntimeContextRegistry;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.io.IOException;

/**
* @author: yuanyuan
* @date: 2023/12/5 3:37 下午
* 模块复用基座 transactionManager、transactionTemplate、以及 dataSource
*/
@Configuration
@MapperScan(basePackages = "com.alipay.sofa.biz1.mapper", sqlSessionFactoryRef = "mysqlSqlFactory")
@EnableTransactionManagement
public class MybatisConfig {

//tips:不要初始化一个基座的DataSource,当模块被卸载的是,基座数据源会被销毁,transactionManager,transactionTemplate,mysqlSqlFactory被销毁没有问题

@Bean(name = "transactionManager")
public PlatformTransactionManager platformTransactionManager() {
return (PlatformTransactionManager) getBaseBean("transactionManager");
}

@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate() {
return (TransactionTemplate) getBaseBean("transactionTemplate");
}

@Bean(name = "mysqlSqlFactory")
public SqlSessionFactoryBean mysqlSqlFactory() throws IOException {
//数据源不能申明成模块spring上下文中的bean,因为模块卸载时会触发close方法

DataSource dataSource = (DataSource) getBaseBean("dataSource");
SqlSessionFactoryBean mysqlSqlFactory = new SqlSessionFactoryBean();
mysqlSqlFactory.setDataSource(dataSource);
mysqlSqlFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mappers/*.xml"));
return mysqlSqlFactory;
}

private Object getBaseBean(String name) {
Biz masterBiz = ArkClient.getMasterBiz();
BizRuntimeContext bizRuntimeContext = BizRuntimeContextRegistry.getBizRuntimeContext(masterBiz);
return bizRuntimeContext.getRootApplicationContext().getBean(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ logging.level.root=INFO
logging.level.com.alipay.sofa.arklet=INFO
logging.config=classpath:log4j2-spring.xml

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=Zfj1995!
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false

mybatis.mapper-locations=classpath:mappers/*.xml

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=200
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.filters=stat,wall,slf4j
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.username=root
#spring.datasource.password=Zfj1995!
#spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
#
#mybatis.mapper-locations=classpath:mappers/*.xml
#
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.druid.initial-size=5
#spring.datasource.druid.min-idle=5
#spring.datasource.druid.max-active=200
#spring.datasource.druid.max-wait=60000
#spring.datasource.druid.time-between-eviction-runs-millis=60000
#spring.datasource.druid.min-evictable-idle-time-millis=300000
#spring.datasource.druid.test-while-idle=true
#spring.datasource.druid.test-on-borrow=false
#spring.datasource.druid.test-on-return=false
#spring.datasource.druid.pool-prepared-statements=false
#spring.datasource.druid.filters=stat,wall,slf4j
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

/**
* 支持将配置转换为 log4j2 context.
* 1. 设置LoggingSystem=SOFAServerlessLog4j2LoggingSystem
* 2. 添加属性到log4j2 ThreadContext
*
* @author ruoshan
* @version $Id: AlipayLog4j2SpringContextListener.java, v 0.1 2018年08月23日 10:32 PM ruoshan Exp $
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,27 @@
public static <T> T createServiceProxy(Biz biz, String name, Class<T> serviceType,
ClassLoader clientClassLoader) {
T service = getService(biz, name);
return (T) doCreateServiceProxy(biz, service, serviceType, clientClassLoader);
return doCreateServiceProxy(biz, service, serviceType, clientClassLoader);
}

public static <T> T createServiceProxy(Biz biz, Class<T> serviceType,
ClassLoader clientClassLoader) {
Class<?> serviceClass;
try {
serviceClass = biz.getBizClassLoader().loadClass(serviceType.getName());
} catch (ClassNotFoundException e) {
throw new BizRuntimeException(E100005, "Cannot find class " + serviceType.getName()
+ " from the biz " + biz.getIdentity());
}
Class<?> serviceClass = checkBizStateAndGetTargetClass(biz, serviceType);
T service = (T) getService(biz, serviceClass);
return (T) doCreateServiceProxy(biz, service, serviceType, clientClassLoader);
return doCreateServiceProxy(biz, service, serviceType, clientClassLoader);
}

public static <T> Map<String, T> batchCreateServiceProxy(Biz biz, Class<T> serviceType,
ClassLoader clientClassLoader) {
Class<?> serviceClass;
try {
serviceClass = biz.getBizClassLoader().loadClass(serviceType.getName());
} catch (ClassNotFoundException e) {
throw new BizRuntimeException(E100005, "Cannot find class " + serviceType.getName()
+ " from the biz " + biz.getIdentity());
}
Class<?> serviceClass = checkBizStateAndGetTargetClass(biz, serviceType);
Map<String, ?> serviceMap = listService(biz, serviceClass);
Map<String, T> proxyMap = new HashMap<>();
for (String beanName : serviceMap.keySet()) {
proxyMap.put(
beanName,
(T) doCreateServiceProxy(biz, serviceMap.get(beanName), serviceType,
clientClassLoader));
proxyMap
.put(
beanName,
doCreateServiceProxy(biz, serviceMap.get(beanName), serviceType,
clientClassLoader));
}
return proxyMap;
}
Expand All @@ -105,23 +94,6 @@
return new HashMap<>();
}

private static BizRuntimeContext checkBizStateAndGetBizRuntimeContext(Biz biz) {
if (biz == null) {
throw new BizRuntimeException(E100003, "biz is null");
}
if (biz.getBizState() != BizState.ACTIVATED && biz.getBizState() != BizState.DEACTIVATED) {
throw new BizRuntimeException(E100004, "biz state is not valid");
}
BizRuntimeContext bizRuntimeContext = BizRuntimeContextRegistry.getBizRuntimeContext(biz);
if (bizRuntimeContext == null) {
throw new BizRuntimeException(E100002, "biz runtime context is null");
}
if (bizRuntimeContext.getRootApplicationContext() == null) {
throw new BizRuntimeException(E100002, "biz spring context is null");
}
return bizRuntimeContext;
}

/**
* @param biz 目标biz
* @param service 目标biz中符合条件的bean
Expand All @@ -131,7 +103,7 @@
*/
private static <T> T doCreateServiceProxy(Biz biz, Object service, Class<T> serviceType, ClassLoader clientClassLoader) {
if (clientClassLoader == null) {
Class<?> callerClass = ReflectionUtils.getCallerClass(5);
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
Class<?> callerClass = ReflectionUtils.getCallerClass(6);
clientClassLoader = callerClass.getClassLoader();
}

Expand Down Expand Up @@ -173,4 +145,35 @@
}
return biz;
}

private static Class<?> checkBizStateAndGetTargetClass(Biz biz, Class<?> sourceClass) {
checkBizState(biz);
try {
return biz.getBizClassLoader().loadClass(sourceClass.getName());
} catch (ClassNotFoundException e) {
throw new BizRuntimeException(E100005, "Cannot find class " + sourceClass.getName()
+ " from the biz " + biz.getIdentity());

Check warning on line 155 in sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java

View check run for this annotation

Codecov / codecov/patch

sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java#L153-L155

Added lines #L153 - L155 were not covered by tests
}
}

private static BizRuntimeContext checkBizStateAndGetBizRuntimeContext(Biz biz) {
checkBizState(biz);
BizRuntimeContext bizRuntimeContext = BizRuntimeContextRegistry.getBizRuntimeContext(biz);
if (bizRuntimeContext == null) {
throw new BizRuntimeException(E100002, "biz runtime context is null");

Check warning on line 163 in sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java

View check run for this annotation

Codecov / codecov/patch

sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java#L163

Added line #L163 was not covered by tests
}
if (bizRuntimeContext.getRootApplicationContext() == null) {
throw new BizRuntimeException(E100002, "biz spring context is null");

Check warning on line 166 in sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java

View check run for this annotation

Codecov / codecov/patch

sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java#L166

Added line #L166 was not covered by tests
}
return bizRuntimeContext;
}

private static void checkBizState(Biz biz) {
if (biz == null) {
throw new BizRuntimeException(E100003, "biz does not exist");

Check warning on line 173 in sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java

View check run for this annotation

Codecov / codecov/patch

sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java#L173

Added line #L173 was not covered by tests
}
if (biz.getBizState() != BizState.ACTIVATED && biz.getBizState() != BizState.DEACTIVATED) {
throw new BizRuntimeException(E100004, "biz state is not valid");

Check warning on line 176 in sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java

View check run for this annotation

Codecov / codecov/patch

sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/service/ServiceProxyFactory.java#L176

Added line #L176 was not covered by tests
}
}
}