개발 기록
DB 이중화하기 with Spring AOP
neunggu
2023. 3. 19. 15:12
728x90
목표
데이터 베이스를 이중화하여 슬레이브에서 select를 하려고 한다.
코드는 github에 있다.(https://github.com/neunggu/score.git)
GitHub - neunggu/score
Contribute to neunggu/score development by creating an account on GitHub.
github.com
환경
java 17
spring boot 2.7.6
spring-boot-starter-aop 사용
방법
1. 마스터, 슬레이브 2개의 DataSource를 생성해 AbstractRoutingDataSource에 담는다.
(세션에 접속할 db를 선정해 두고 사용할 예정)
(determineCurrentLookupKey 구현 필요)
@Configuration
public class DatabaseConfig {
@Value("${db.driver-class-name}")
private String driver;
@Value("${db.username}")
private String user;
@Value("${db.password}")
private String pw;
@Value("${db.master.url}")
private String masterUrl;
@Value("${db.slave.url}")
private String slaveUrl;
@Bean
public DataSource createRouterDatasource() {
AbstractRoutingDataSource routingDataSource = new RoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("current:master", createDataSource(driver, masterUrl, user, pw));
targetDataSources.put("current:slave", createDataSource(driver, slaveUrl, user, pw));
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
private DataSource createDataSource(String driver, String url, String user, String password) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(driver);
dataSource.setConnectionInitSql("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;");
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setJdbcUrl(url);
dataSource.setMaxLifetime(30000);
return dataSource;
}
}
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
Object dbKey;
if(RequestContextHolder .getRequestAttributes() == null
|| RequestContextHolder.getRequestAttributes()
.getAttribute("db_key", RequestAttributes.SCOPE_SESSION) == null) {
dbKey = "master";
} else {
dbKey = RequestContextHolder
.getRequestAttributes()
.getAttribute("db_key", RequestAttributes.SCOPE_SESSION);
}
return "current:" + dbKey;
}
}
2. 세션에 선택된 db를 셋팅하는 유틸 생성.
(3번에서 만들 aspect에서만 사용할 것이어서 굳이 클래스로 뺄 필요는 없다.)
@Component
public class DBSessionConfigUtil {
public void setDbKey(HttpSession httpSession, String dbKey) {
if(httpSession != null && httpSession.getAttribute("db_key") != null ) {
String dbSessionkey = (String) httpSession.getAttribute("db_key");
if(!dbSessionkey.equals(dbKey)) {
httpSession.setAttribute("db_key", dbKey);
}
} else {
httpSession.setAttribute("db_key", dbKey);
}
}
}
3. Aspect 생성
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SlaveDB {
}
@Aspect
@Component
@RequiredArgsConstructor
public class DB{
private final HttpSession httpSession;
private final DBSessionConfigUtil dbSessionUtil;
@Around("@annotation(SlaveDB)")
public Object setSlave(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
dbSessionUtil.setDbKey(httpSession, "slave");
result = joinPoint.proceed();
} catch (Throwable e) {
} finally {
dbSessionUtil.setDbKey(httpSession, "master");
}
return result;
}
}
4. 필요한 곳에 AOP 사용
@Mapper
public interface ScoreMapper {
@SlaveDB
@Select("SELECT * FROM score WHERE user_id = #{userId}")
ScoreEntity findById(String userId);
...
}
결론: AOP를 이용하면 편한 점들이 꽤 있다.
728x90
반응형