我知道问题被问到了;但可能会帮助其他人寻找答案(答案可能有点长)。
注意:以下逻辑不会在同一请求中切换到多个数据库源。 JPA 连接到application.properties 文件中指向的主数据源。下面的逻辑只是使用 JDBC TEMPLATE FOR ADDITIONAL DB Connection。
SpringBoot 2.0.1 引入了AbstractRoutingDataSource,使用它可以动态地为每个请求使用适当的数据源。 datasource 的类型为 Map<Object, Object>。
本质上,数据源映射应该包含连接标识符作为键和数据源作为值。有两种填充地图的方法。
第一种方式:这里 dataSourceMap 将仅在应用程序启动时填充。 (假设所有用户都存在于一个不同的单个数据库中(除了主数据库),这种方法应该足够了。)
一个。创建一个在应用程序启动时加载的配置文件ClientDataSourceConfig.java,其中手动准备了dataSourceMap,并为JPA设置了默认的目标数据库源。
ClientDataSourceConfig.java:
import com.company.tripmis.datasource.ClientDataSourceRouter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ClientDataSourceConfig {
@Value("${spring.datasource.driver}")
private String driver;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
public ClientDataSourceRouter dataSource;
@Bean(name = "getDataSource") // <-- IMP: Bean and Returning manually created ClientDataSourceRouter
public ClientDataSourceRouter getDataSource() throws Exception {
dataSource = new ClientDataSourceRouter();
// Here we are initialising default DB from application.properties file manually.
dataSource.init(driver, url, username, password);
return dataSource;
}
}
b.在ClientDataSourceRouter 中,通过连接到主数据库(对于多租户系统)或静态提及其他数据库并将其添加到dataSourceMap 并设置要使用的默认数据库(理想情况下是主数据库)来准备数据源映射。
ClientDataSourceRouter.java:
import static com.company.tripmis.utils.Helpers.prepareDataSourceConnectionObj;
import java.util.HashMap;
import java.util.List;
import com.company.tripmis.model.entites.GlobalSetup;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ClientDataSourceRouter extends AbstractRoutingDataSource {
private HashMap<Object, Object> dataSourceMap;
public void init(String driver, String url, String username, String password) throws Exception {
try {
DriverManagerDataSource defaultDataSource = new DriverManagerDataSource();
defaultDataSource.setDriverClassName(driver);
defaultDataSource.setUrl(url);
defaultDataSource.setUsername(username);
defaultDataSource.setPassword(password);
/**
* I am using below code for MultiTenantDB connection and hence reading and
* initialise multiple DBs but, for the above case, assuming User authentication
* DB is ONE seperate DB, you can replace the below JDBC Template part with User
* Authentication DB connection info and add it to this.dataSourceMap with key.
*/
JdbcTemplate jdbcTemplate = new JdbcTemplate(defaultDataSource);
List<GlobalSetup> globalSetupList = jdbcTemplate
.query("SELECT `id`, `key`, `value`, `description` FROM global_setup;", (rs, rowNum) -> {
GlobalSetup globalSetup = new GlobalSetup();
globalSetup.setId(rs.getString(1));
globalSetup.setKey(rs.getString(2));
globalSetup.setValue(rs.getString(3));
globalSetup.setDescription(rs.getString(4));
return globalSetup;
});
this.dataSourceMap = new HashMap<>();
for (GlobalSetup globalSetup : globalSetupList) {
String key = globalSetup.getKey();
String configString = globalSetup.getValue();
DriverManagerDataSource dataSource = prepareDataSourceConnectionObj(configString);
this.dataSourceMap.put(key, dataSource);
}
// Comment till here and add single DB connection info to DBMap.
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(defaultDataSource);
} catch (Exception ex) {
throw ex;
}
}
@Override
protected Object determineCurrentLookupKey() {
return ClientDataSourceContext.getClientDatabase();
}
public HashMap<Object, Object> getDataSources() {
return this.dataSourceMap;
}
}
第二种方式:为了动态更新dataSourceMap,创建一个过滤器,它将在每次请求时从数据库中读取并更新。 (应该使用 caching 相同,这样,DB 不会在每个请求上都被命中,并定期更新 CacheBucket。)
现在,在应用程序启动时向 datasourceMap 添加了多个 DB 连接,我们需要做的就是在需要时使用JDBCTemplate 连接进行用户身份验证。
这可以通过使用过滤器或 HTTPInterceptors 拦截 HttpRequest 来完成,也可以根据需要在Controller/Service 层完成。
伪代码在Filter级别进行身份验证。
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import com.company.tripmis.datasource.ClientDataSourceContext;
import com.company.tripmis.datasource.ClientDataSourceRouter;
import com.company.tripmis.model.pojo.ErrorModel;
import com.company.tripmis.model.pojo.ResponseModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
@Order(2)
public class RequestDataSourceFilter implements Filter {
@Autowired
private ApplicationContext applicationContext;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String companyId = httpRequest.getRequestURI().split("/")[1];
ClientDataSourceRouter clientDataSourceRouterObj = (ClientDataSourceRouter) applicationContext
.getBean("getDataSource");
HashMap<Object, Object> dataSourcesMap = clientDataSourceRouterObj.getDataSources();
// Some of the following code is for MultiTenant, if not needed, please ignore
if (dataSourcesMap.get(companyId) == null) {
ResponseModel responseModel = new ResponseModel();
ErrorModel errorModel = new ErrorModel();
errorModel.setCode("DB_RES_NOT_FND");
errorModel.setMessage("The DB schema mapping not found for the given: " + companyId);
responseModel.setSuccess(false);
responseModel.setMessage("Requested DB resource not found");
responseModel.setError(errorModel);
((HttpServletResponse) response).setHeader("Content-Type", "application/json");
((HttpServletResponse) response).setStatus(400);
response.getOutputStream().write(new ObjectMapper().writeValueAsString(responseModel).getBytes());
return;
} else {
String username = ""; // Read username from request
JdbcTemplate jdbcTemplate = new JdbcTemplate((DataSource) dataSourcesMap.get(companyId));
String sqlQuery = "select * from user where username = ?";
// Prepare the query
List<Model> userList = jdbcTemplate.query(sqlQuery, new Object[] { username },
new UserAuthMapper());
// Replace with proper query and create a custom mapper as required.
// And continue with the authentication here.
}
ClientDataSourceContext.setClientName(companyId);
chain.doFilter(request, response);
}
}
上面的userList(不是列表)会有用户的详细信息,可以用来进行认证。
可以扩展上述逻辑以根据请求切换到不同的数据库(数据源)(最适合多租户系统,其中 baseUrl 定义了用于该请求生命周期的数据库)。
@Bean(name = "getDataSource") 必须作为@DependsOn 添加到应用程序文件中,以便在应用程序启动期间运行 init 方法。
希望上面的回答至少能帮助你提供一些基本的理解。