From 39f6e58284d9d3f99a05d2022b6eccfb72e1dcf9 Mon Sep 17 00:00:00 2001 From: mjkhan21 Date: Fri, 2 Aug 2024 15:32:46 +0900 Subject: [PATCH] =?UTF-8?q?multi-datasource=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/boot/AbstractDatasource.java | 124 ++++++++++++++++++ .../foundation/boot/AbstractTransaction.java | 73 +++++++++++ .../xit/foundation/boot/CommonConfig.java | 2 - .../xit/foundation/boot/DatasourceConfig.java | 45 ++----- .../boot/FoundationApplication.java | 4 +- .../boot/StandAloneApplication.java | 4 +- .../foundation/boot/TransactionConfig.java | 54 ++------ 7 files changed, 224 insertions(+), 82 deletions(-) create mode 100644 src/main/java/cokr/xit/foundation/boot/AbstractDatasource.java create mode 100644 src/main/java/cokr/xit/foundation/boot/AbstractTransaction.java diff --git a/src/main/java/cokr/xit/foundation/boot/AbstractDatasource.java b/src/main/java/cokr/xit/foundation/boot/AbstractDatasource.java new file mode 100644 index 0000000..a94638e --- /dev/null +++ b/src/main/java/cokr/xit/foundation/boot/AbstractDatasource.java @@ -0,0 +1,124 @@ +package cokr.xit.foundation.boot; + +import java.util.List; +import java.util.Properties; +import java.util.stream.Stream; + +import javax.sql.DataSource; + +import org.apache.ibatis.mapping.VendorDatabaseIdProvider; +import org.egovframe.rte.psl.dataaccess.mapper.MapperConfigurer; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.data.paging.MapperSupport; + +/**데이터베이스 접속 관련 설정 + * + * @author mjkhan + */ +public abstract class AbstractDatasource extends AbstractComponent { + private DataSource dataSource; + + /**데이터소스 Bean을 반환한다. JDBC 접속은 application.yml 파일에 다음과 같이 설정한다. + *
 spring:
+	 *   datasource:
+	 *     hikari:
+	 *       driver-class-name: JDBC 드라이버 클래스 이름
+	 *       jdbc-url: 데이터베이스 접속 URL
+	 *       username: 데이터베이스 접속 아이디
+	 *       password: 데이터베이스 접속 비밀번호
+ * @return 데이터소스 + */ + protected DataSource dataSource() { + return dataSource != null ? dataSource : (dataSource = DataSourceBuilder.create().build()); + } + + /**SqlSessionFactoryBean을 반환한다.
+ * MyBatis의 설정 파일들은 다음 경로에 있어야 한다. + * + * @return SqlSessionFactoryBean + */ + protected SqlSessionFactoryBean sqlSession() { + try { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(dataSource()); + + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + bean.setConfigLocation(resolver.getResource("classpath:sql/mybatis-config.xml")); + + Resource[] mapperLocations = getMapperLocations(resolver); + if (!isEmpty(mapperLocations)) + bean.setMapperLocations(mapperLocations); + bean.setPlugins(new MapperSupport()); + bean.setDatabaseIdProvider(databaseIdProvider()); + return bean; + } catch (Exception e) { + throw Assert.runtimeException(e); + } + } + + /**매퍼 위치목록을 반환한다. + * @param resolver PathMatchingResourcePatternResolver + * @return 매퍼 위치목록 + * @throws Exception + */ + private Resource[] getMapperLocations(PathMatchingResourcePatternResolver resolver) throws Exception { + List resources = Stream.of(mapperLocationPatterns()) + .flatMap(location -> { + try { + return Stream.of(resolver.getResources(location)); + } catch (Exception e) { + throw runtimeException(e); + } + }) + .toList(); + return resources.toArray(new Resource[resources.size()]); + } + + /**매퍼 경로패턴을 반환한다. + * @return 매퍼 경로패턴 목록 + */ + protected String[] mapperLocationPatterns() { + return new String[] {"classpath:sql/mapper/**/*.xml"}; + } + + /**MapperConfigurer를 반환한다.
+ * base package는 cokr.xit로 설정한다. + * @return MapperConfigurer + */ + protected MapperConfigurer mapperConfigurer() { + MapperConfigurer bean = new MapperConfigurer(); + String basePackages = mapperBasePackages(); + if (!isEmpty(basePackages)) + bean.setBasePackage(basePackages); + bean.setSqlSessionFactoryBeanName(sqlSessionName()); + return bean; + } + + protected String mapperBasePackages() { + return "cokr.xit"; + } + + protected String sqlSessionName() { + return "sqlSession"; + } + + protected VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.put("MariaDB", "mariadb"); + properties.put("Oracle", "oracle"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/boot/AbstractTransaction.java b/src/main/java/cokr/xit/foundation/boot/AbstractTransaction.java new file mode 100644 index 0000000..912e670 --- /dev/null +++ b/src/main/java/cokr/xit/foundation/boot/AbstractTransaction.java @@ -0,0 +1,73 @@ +package cokr.xit.foundation.boot; + +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; +import org.springframework.transaction.interceptor.RollbackRuleAttribute; +import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/**트랜잭션 설정 클래스. + *

트랜잭션의 적용 대상은 cokr.xit..service.bean..*ServiceBean 클래스의 메소드들이다. + * @author mjkhan + */ +public class AbstractTransaction { + private DataSource dataSource; + + public DataSource getDataSource() { + return dataSource; + } + + protected void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + protected DataSourceTransactionManager txManager() { + DataSourceTransactionManager bean = new DataSourceTransactionManager(); + bean.setDataSource(getDataSource()); + return bean; + } + + protected TransactionInterceptor txAdvice() { + RuleBasedTransactionAttribute read = new RuleBasedTransactionAttribute( + TransactionDefinition.PROPAGATION_REQUIRED, + List.of(new RollbackRuleAttribute(Throwable.class)) + ); + read.setReadOnly(true); + RuleBasedTransactionAttribute write = new RuleBasedTransactionAttribute( + TransactionDefinition.PROPAGATION_REQUIRED, + List.of(new RollbackRuleAttribute(Throwable.class)) + ); + + NameMatchTransactionAttributeSource txAttrSrc = new NameMatchTransactionAttributeSource(); + txAttrSrc.setNameMap(Map.of( + "get*", read, + "*", write + )); + + TransactionInterceptor bean = new TransactionInterceptor(); + bean.setTransactionManager(txManager()); + bean.setTransactionAttributeSource(txAttrSrc); + return bean; + } + + protected Advisor txAdvisor() { + String expression = Foundation.transactionPointcut(); + AspectJExpressionPointcut requiredTx = new AspectJExpressionPointcut(); + requiredTx.setExpression(expression); + + DefaultPointcutAdvisor bean = new DefaultPointcutAdvisor(); + bean.setPointcut(requiredTx); + bean.setAdvice(txAdvice()); + + return bean; + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/boot/CommonConfig.java b/src/main/java/cokr/xit/foundation/boot/CommonConfig.java index 7f0ff36..387fb4f 100644 --- a/src/main/java/cokr/xit/foundation/boot/CommonConfig.java +++ b/src/main/java/cokr/xit/foundation/boot/CommonConfig.java @@ -9,7 +9,6 @@ import java.util.stream.Collectors; import org.egovframe.rte.fdl.cmmn.trace.LeaveaTrace; import org.egovframe.rte.fdl.property.impl.EgovPropertyServiceImpl; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.util.AntPathMatcher; @@ -21,7 +20,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author mjkhan */ @Configuration -@ComponentScan(basePackages = "cokr.xit") public class CommonConfig { /**AntPathMatcher를 반환한다. * @return AntPathMatcher diff --git a/src/main/java/cokr/xit/foundation/boot/DatasourceConfig.java b/src/main/java/cokr/xit/foundation/boot/DatasourceConfig.java index a0394b6..9768d57 100644 --- a/src/main/java/cokr/xit/foundation/boot/DatasourceConfig.java +++ b/src/main/java/cokr/xit/foundation/boot/DatasourceConfig.java @@ -1,20 +1,13 @@ package cokr.xit.foundation.boot; -import java.util.Properties; - import javax.sql.DataSource; import org.apache.ibatis.mapping.VendorDatabaseIdProvider; import org.egovframe.rte.psl.dataaccess.mapper.MapperConfigurer; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; - -import cokr.xit.foundation.Assert; -import cokr.xit.foundation.data.paging.MapperSupport; /**데이터베이스 접속 관련 설정 *

* @return SqlSessionFactoryBean */ + @Override @Bean public SqlSessionFactoryBean sqlSession() { - try { - SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); - bean.setDataSource(dataSource()); - - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - bean.setConfigLocation(resolver.getResource("classpath:sql/mybatis-config.xml")); - bean.setMapperLocations(resolver.getResources("classpath:sql/mapper/**/*.xml")); - bean.setPlugins(new MapperSupport()); - bean.setDatabaseIdProvider(databaseIdProvider()); - return bean; - } catch (Exception e) { - throw Assert.runtimeException(e); - } + return super.sqlSession(); } /**MapperConfigurer를 반환한다.
* base package는 cokr.xit로 설정한다. * @return MapperConfigurer */ + @Override @Bean public MapperConfigurer mapperConfigurer() { - MapperConfigurer bean = new MapperConfigurer(); - bean.setBasePackage("cokr.xit"); - bean.setSqlSessionFactoryBeanName("sqlSession"); - return bean; + return super.mapperConfigurer(); } + @Override @Bean - VendorDatabaseIdProvider databaseIdProvider() { - VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); - Properties properties = new Properties(); - properties.put("MariaDB", "mariadb"); - properties.put("Oracle", "oracle"); - databaseIdProvider.setProperties(properties); - return databaseIdProvider; + public VendorDatabaseIdProvider databaseIdProvider() { + return super.databaseIdProvider(); } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/boot/FoundationApplication.java b/src/main/java/cokr/xit/foundation/boot/FoundationApplication.java index 3045ba0..3fd6fd8 100644 --- a/src/main/java/cokr/xit/foundation/boot/FoundationApplication.java +++ b/src/main/java/cokr/xit/foundation/boot/FoundationApplication.java @@ -2,6 +2,7 @@ package cokr.xit.foundation.boot; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ContextConfiguration; /**Spring Boot에서 xit-foundation.jar를 사용하는 웹 애플리케이션의 베이스 클래스 @@ -18,7 +19,8 @@ import org.springframework.test.context.ContextConfiguration; ServletConfig.class, MvcConfig.class, DatasourceConfig.class, - TransactionConfig.class, + TransactionConfig.class }) +@ComponentScan(basePackages = "cokr.xit") @ContextConfiguration("classpath:spring/context-*.xml") public class FoundationApplication {} diff --git a/src/main/java/cokr/xit/foundation/boot/StandAloneApplication.java b/src/main/java/cokr/xit/foundation/boot/StandAloneApplication.java index 496a94d..9b3d57a 100644 --- a/src/main/java/cokr/xit/foundation/boot/StandAloneApplication.java +++ b/src/main/java/cokr/xit/foundation/boot/StandAloneApplication.java @@ -6,6 +6,7 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ContextConfiguration; /**Spring Boot에서 xit-foundation.jar를 사용하는 stand-alone 애플리케이션의 베이스 클래스 @@ -22,8 +23,9 @@ import org.springframework.test.context.ContextConfiguration; @ImportAutoConfiguration({ CommonConfig.class, DatasourceConfig.class, - TransactionConfig.class, + TransactionConfig.class }) +@ComponentScan(basePackages = "cokr.xit") @ContextConfiguration("classpath:spring/context-*.xml") public class StandAloneApplication implements CommandLineRunner { @Override diff --git a/src/main/java/cokr/xit/foundation/boot/TransactionConfig.java b/src/main/java/cokr/xit/foundation/boot/TransactionConfig.java index 5eb67cc..11febac 100644 --- a/src/main/java/cokr/xit/foundation/boot/TransactionConfig.java +++ b/src/main/java/cokr/xit/foundation/boot/TransactionConfig.java @@ -1,24 +1,15 @@ package cokr.xit.foundation.boot; -import java.util.List; -import java.util.Map; - import javax.annotation.Resource; import javax.sql.DataSource; import org.aspectj.lang.annotation.Aspect; import org.springframework.aop.Advisor; -import org.springframework.aop.aspectj.AspectJExpressionPointcut; -import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; -import org.springframework.transaction.interceptor.RollbackRuleAttribute; -import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; import org.springframework.transaction.interceptor.TransactionInterceptor; /**트랜잭션 설정 클래스. @@ -29,51 +20,28 @@ import org.springframework.transaction.interceptor.TransactionInterceptor; @Aspect @EnableTransactionManagement @EnableAspectJAutoProxy(proxyTargetClass = true) -public class TransactionConfig { +public class TransactionConfig extends AbstractTransaction { + @Override @Resource(name = "dataSource") - private DataSource dataSource; + public void setDataSource(DataSource dataSource) { + super.setDataSource(dataSource); + } + @Override @Bean public DataSourceTransactionManager txManager() { - DataSourceTransactionManager bean = new DataSourceTransactionManager(); - bean.setDataSource(dataSource); - return bean; + return super.txManager(); } + @Override @Bean public TransactionInterceptor txAdvice() { - RuleBasedTransactionAttribute read = new RuleBasedTransactionAttribute( - TransactionDefinition.PROPAGATION_REQUIRED, - List.of(new RollbackRuleAttribute(Throwable.class)) - ); - read.setReadOnly(true); - RuleBasedTransactionAttribute write = new RuleBasedTransactionAttribute( - TransactionDefinition.PROPAGATION_REQUIRED, - List.of(new RollbackRuleAttribute(Throwable.class)) - ); - - NameMatchTransactionAttributeSource txAttrSrc = new NameMatchTransactionAttributeSource(); - txAttrSrc.setNameMap(Map.of( - "get*", read, - "*", write - )); - - TransactionInterceptor bean = new TransactionInterceptor(); - bean.setTransactionManager(txManager()); - bean.setTransactionAttributeSource(txAttrSrc); - return bean; + return super.txAdvice(); } + @Override @Bean public Advisor txAdvisor() { - String expression = Foundation.transactionPointcut(); - AspectJExpressionPointcut requiredTx = new AspectJExpressionPointcut(); - requiredTx.setExpression(expression); - - DefaultPointcutAdvisor bean = new DefaultPointcutAdvisor(); - bean.setPointcut(requiredTx); - bean.setAdvice(txAdvice()); - - return bean; + return super.txAdvisor(); } } \ No newline at end of file