您好,登錄后才能下訂單哦!
本文源碼: GitHub·點這里 || GitEE·點這里
在相對復雜的應用服務中,配置多個數據源是常見現象,例如常見的:配置主從數據庫用來寫數據,再配置一個從庫讀數據,這種讀寫分離模式可以緩解數據庫壓力,提高系統的并發能力和穩定性,執行效率。
在處理這種常見問題,要學會查詢服務基礎框架的API,說直白點就是查詢Spring框架的API(工作幾年,還沒用過Spring之外的框架搭建環境),這種常用的業務模式,基本上Spring都提供了API支持。
核心API:AbstractRoutingDataSource
底層維護Map容器,用來保存數據源集合,提供一個抽象方法,實現自定義的路由策略。
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
protected abstract Object determineCurrentLookupKey();
補刀一句
:為何框架的原理很難通過一篇文章看明白?因為使用的不多,基本意識沒有形成,熟悉框架原理的基本要求:對框架的各種功能都熟悉,經常使用,自然而然的就明白了,鹽大曬的久,咸魚才夠味。
配置兩個數據源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
master:
url: jdbc:mysql://localhost:3306/data_master
username: root
password: 123456
slave:
url: jdbc:mysql://localhost:3306/data_slave
username: root
password: 123456
從實際開發角度,這兩個數據源需要配置主從復制流程,再基于安全角度,寫庫可以只給寫權限,讀庫只給讀權限。
Map容器加載
@Configuration
public class DruidConfig {
// 忽略參數加載,源碼中有
@Bean
@Primary
public DataSource primaryDataSource() {
Map<Object, Object> map = new HashMap<>();
map.put("masterDataSource", masterDataSource());
map.put("slaveDataSource", slaveDataSource());
RouteDataSource routeDataSource = new RouteDataSource();
routeDataSource.setTargetDataSources(map);
routeDataSource.setDefaultTargetDataSource(masterDataSource());
return routeDataSource ;
}
private DataSource masterDataSource() {
return getDefDataSource(masterUrl,masterUsername,masterPassword);
}
private DataSource slaveDataSource() {
return getDefDataSource(slaveUrl,slaveUsername,slavePassword);
}
private DataSource getDefDataSource (String url,String userName,String passWord){
DruidDataSource datasource = new DruidDataSource();
datasource.setDriverClassName(driverClassName);
datasource.setUrl(url);
datasource.setUsername(userName);
datasource.setPassword(passWord);
return datasource;
}
}
這里的Map容器管理兩個key,masterDataSource和slaveDataSource代表兩個不同的庫,使用不同的key即加載對應的庫。
使用ThreadLocal管理當前會會話中線程參數,存取使用極其方便。
public class RouteContext implements AutoCloseable {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void setRouteKey (String key){
threadLocal.set(key);
}
public static String getRouteKey() {
String key = threadLocal.get();
return key == null ? "masterDataSource" : key;
}
@Override
public void close() {
threadLocal.remove();
}
}
獲取ThreadLocal中,當前數據源的key,適配相關聯的數據源。
public class RouteDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RouteContext.getRouteKey();
}
}
基于AOP的切面思想,不同的方法類型,去設置對應路由Key,這樣就可以在業務邏輯執行之前,切換到不同的數據源。
Aspect
@Component
@Order(1)
public class ReadWriteAop {
private static Logger LOGGER = LoggerFactory.getLogger(ReadWriteAop.class) ;
@Before("execution(* com.master.slave.controller.*.*(..))")
public void setReadDataSourceType() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String method = request.getRequestURI() ;
boolean rwFlag = readOrWrite(method) ;
if (rwFlag){
RouteContext.setRouteKey("slaveDataSource");
} else {
RouteContext.setRouteKey("masterDataSource");
}
LOGGER.info("請求方法:"+method+";執行庫:"+RouteContext.getRouteKey());
}
private String[] readArr = new String[]{"select","count","query","get","find"} ;
private boolean readOrWrite (String method){
for (String readVar:readArr) {
if (method.contains(readVar)){
return true ;
}
}
return false ;
}
}
常見的讀取方法:select、count、query、get、find等等,方法的命名要遵循自定義的路由規則。
控制層API
import com.master.slave.entity.User;
import com.master.slave.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class UserController {
@Resource
private UserService userService ;
@GetMapping("/selectById")
public User selectById (@RequestParam("id") Integer id) {
return userService.selectById(id) ;
}
@GetMapping("/insert")
public String insert () {
User user = new User("張三","write") ;
userService.insert(user) ;
return "success" ;
}
}
服務實現
@Service
public class UserService {
@Resource
private UserMapper userMapper ;
public User selectById (Integer id) {
return userMapper.selectById(id) ;
}
public void insert (User user){
userMapper.insert(user);
}
}
這樣數據源基于不同的類型方法就會一直的動態切換。
GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。