diff --git a/README.md b/README.md index 61c3553..b0f129b 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,15 @@ # Readme -## 프로젝트 소개 +![title_image](./readme/wide.png) -### 문제 인식 +## 서비스 개요 -2010년대 유행한 BNPL(Buy Now, Pay Later) 서비스는 선구매 후결제의 편리함을 제공했지만, 연체와 부채 증가라는 구조적 한계가 드러났습니다. 이에 따라 2020년대에는 **SNPL(Save -Now, Pay Later)**, 즉 ‘저축을 통한 지불’이라는 새로운 개념이 등장했습니다. 이는 보상과 건전한 소비 습관을 중시하는 MZ세대의 성향과 잘 맞는 방식입니다. - -한편, 리처드 탈러 박사의 ‘심리적 회계’ 이론에 따르면 사람들은 돈을 단순한 총액으로 보지 않고, 마음속에서 ‘저축’, ‘여행비’, ‘투자금’ 등으로 구분하여 관리합니다. 그러나 현실의 금융 계좌는 대부분 단일 -구조로 되어 있어, 마음속의 구분이 실제 금융 관리로는 이어지지 못하고 자금의 흐름이 불명확해지는 문제가 있습니다. - -### 만들게 된 계기 - -저희 팀은 이러한 한계를 해결하기 위해, **심리적 회계 개념을 반영한 개인 맞춤형 금융 관리 서비스의 필요성**을 느꼈습니다. 특히 MZ세대의 ‘미닝아웃 소비’나 ‘YONO(You Only Need One)족’과 -같은 현상은 개인의 가치관과 목적이 소비에 직접 반영된다는 점에서 SNPL의 철학과 맞닿아 있다고 생각했습니다. - -하지만 기존 시중 은행이나 대중적인 금융 플랫폼에서는 이러한 심리적·행동적 특성을 충분히 반영한 서비스를 찾아보기 어려웠습니다. - -이에 저희는 **MZ세대의 심리적 회계 구조를 실제 금융 계좌 설계에 녹여낸 서비스, PLANIT을 기획하게 되었습니다.** +PLANIT은 ‘심리적 회계’ 이론에서 착안한 목표지향 자산관리 서비스입니다. +사람들이 마음속으로는 자금을 ‘여행비’, ‘저축’, ‘투자금’처럼 나누어 관리하지만, +실제 금융 계좌에서는 이를 구분하기 어렵다는 점에서 아이디어를 얻었습니다. +PLANIT은 사용자가 목표별로 자산을 시각화·분리 관리할 수 있도록 도와 +‘Save Now, Pay Later’ — 저축을 통한 실현 문화를 제안합니다. +MZ세대의 가치소비와 자기주도적 금융습관 형성을 지향합니다. ## 상세 기능 diff --git a/build.gradle b/build.gradle index b6a2f8c..71260ce 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ dependencies { { exclude group: 'commons-logging', module: 'commons-logging' } implementation "org.springframework:spring-webmvc:${springVersion}" implementation 'javax.inject:javax.inject:1' + implementation 'org.springframework:spring-aspects:5.3.39' // AOP runtimeOnly 'org.aspectj:aspectjrt:1.9.20' diff --git a/readme/wide.png b/readme/wide.png new file mode 100644 index 0000000..a12fd29 Binary files /dev/null and b/readme/wide.png differ diff --git a/src/main/java/woojooin/planit/global/config/RootConfig.java b/src/main/java/woojooin/planit/global/config/RootConfig.java index 98c5755..e28eb7e 100644 --- a/src/main/java/woojooin/planit/global/config/RootConfig.java +++ b/src/main/java/woojooin/planit/global/config/RootConfig.java @@ -1,5 +1,8 @@ package woojooin.planit.global.config; +import java.util.HashMap; +import java.util.Map; + import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; @@ -22,6 +25,8 @@ import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; +import woojooin.planit.global.config.datasource.RoutingDataSource; +import woojooin.planit.global.enums.DataSourceType; @Configuration @PropertySource({"classpath:/application.properties"}) @@ -53,17 +58,51 @@ public class RootConfig { @Value("${jdbc.password}") String password; - @Bean - public DataSource dataSource() { + @Value("${jdbc.master.url}") + private String masterUrl; + @Value("${jdbc.master.username}") + private String masterUsername; + @Value("${jdbc.master.password}") + private String masterPassword; + @Value("${jdbc.slave.url}") + private String slaveUrl; + @Value("${jdbc.slave.username}") + private String slaveUsername; + @Value("${jdbc.slave.password}") + private String slavePassword; + + @Bean(name = "masterDataSource") + public DataSource masterDataSource() { HikariConfig config = new HikariConfig(); + config.setDriverClassName(driver); + config.setJdbcUrl(masterUrl); + config.setUsername(masterUsername); + config.setPassword(masterPassword); + + log.info("id = {} password = {}", masterUsername, masterPassword); + return new HikariDataSource(config); + } + @Bean(name = "slaveDataSource") + public DataSource slaveDataSource() { + HikariConfig config = new HikariConfig(); config.setDriverClassName(driver); - config.setJdbcUrl(url); - config.setUsername(username); - config.setPassword(password); + config.setJdbcUrl(slaveUrl); + config.setUsername(slaveUsername); + config.setPassword(slavePassword); + log.info("id={} password={}", slaveUsername, slavePassword); + return new HikariDataSource(config); + } - HikariDataSource dataSource = new HikariDataSource(config); - return dataSource; + @Bean + public DataSource routingDataSource() { + RoutingDataSource routingDataSource = new RoutingDataSource(); + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER, masterDataSource()); + targetDataSources.put(DataSourceType.SLAVE, slaveDataSource()); + routingDataSource.setTargetDataSources(targetDataSources); + routingDataSource.setDefaultTargetDataSource(masterDataSource()); + return routingDataSource; } @Autowired @@ -74,17 +113,16 @@ public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); sqlSessionFactory.setConfigLocation( applicationContext.getResource("classpath:/mybatis-config.xml")); - sqlSessionFactory.setDataSource(dataSource()); - sqlSessionFactory.setMapperLocations( applicationContext.getResources("classpath:/mapper/**/*.xml")); + sqlSessionFactory.setDataSource(routingDataSource()); return (SqlSessionFactory)sqlSessionFactory.getObject(); } @Bean public DataSourceTransactionManager transactionManager() { - DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource()); + DataSourceTransactionManager manager = new DataSourceTransactionManager(routingDataSource()); return manager; } } \ No newline at end of file diff --git a/src/main/java/woojooin/planit/global/config/datasource/DataSourceAspect.java b/src/main/java/woojooin/planit/global/config/datasource/DataSourceAspect.java new file mode 100644 index 0000000..9d875e9 --- /dev/null +++ b/src/main/java/woojooin/planit/global/config/datasource/DataSourceAspect.java @@ -0,0 +1,23 @@ +package woojooin.planit.global.config.datasource; + +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +import woojooin.planit.global.enums.DataSourceType; + +@Aspect +@Component +public class DataSourceAspect { + + @Before("@annotation(woojooin.planit.global.config.datasource.ReadOnly)") + public void setReadDataSource() { + DataSourceContextHolder.set(DataSourceType.SLAVE); + } + + @After("@annotation(woojooin.planit.global.config.datasource.ReadOnly)") + public void clearDataSource() { + DataSourceContextHolder.clear(); + } +} diff --git a/src/main/java/woojooin/planit/global/config/datasource/DataSourceContextHolder.java b/src/main/java/woojooin/planit/global/config/datasource/DataSourceContextHolder.java new file mode 100644 index 0000000..9b315e7 --- /dev/null +++ b/src/main/java/woojooin/planit/global/config/datasource/DataSourceContextHolder.java @@ -0,0 +1,20 @@ +package woojooin.planit.global.config.datasource; + +import woojooin.planit.global.enums.DataSourceType; + +public class DataSourceContextHolder { + + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static void set(DataSourceType type) { + contextHolder.set(type); + } + + public static DataSourceType get() { + return contextHolder.get() == null ? DataSourceType.MASTER : contextHolder.get(); + } + + public static void clear() { + contextHolder.remove(); + } +} diff --git a/src/main/java/woojooin/planit/global/config/datasource/ReadOnly.java b/src/main/java/woojooin/planit/global/config/datasource/ReadOnly.java new file mode 100644 index 0000000..c9cca9f --- /dev/null +++ b/src/main/java/woojooin/planit/global/config/datasource/ReadOnly.java @@ -0,0 +1,13 @@ +package woojooin.planit.global.config.datasource; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ReadOnly { +} diff --git a/src/main/java/woojooin/planit/global/config/datasource/RoutingDataSource.java b/src/main/java/woojooin/planit/global/config/datasource/RoutingDataSource.java new file mode 100644 index 0000000..916c65e --- /dev/null +++ b/src/main/java/woojooin/planit/global/config/datasource/RoutingDataSource.java @@ -0,0 +1,10 @@ +package woojooin.planit.global.config.datasource; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +public class RoutingDataSource extends AbstractRoutingDataSource { + @Override + protected Object determineCurrentLookupKey() { + return DataSourceContextHolder.get(); + } +} diff --git a/src/main/java/woojooin/planit/global/enums/DataSourceType.java b/src/main/java/woojooin/planit/global/enums/DataSourceType.java new file mode 100644 index 0000000..12fe83c --- /dev/null +++ b/src/main/java/woojooin/planit/global/enums/DataSourceType.java @@ -0,0 +1,5 @@ +package woojooin.planit.global.enums; + +public enum DataSourceType { + MASTER, SLAVE; +}