본문 바로가기

프로그래밍/Spring

전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-datasource.xml 변환)

지난 글에서는 공통 설정인 ContextCommon 클래스를 제작하는 방법에 대해 설명하였다. 저번 글의 핵심은 <context:component-scan> 태그의 변환이 주된 내용이었는데 잘 이해를 했는지 모르겠다. 이번에는 DataSource 설정 파일인 context-datasource.xml을 Java Config 파일로 바꾸는 작업에 대해 설명하도록 하겠다. 

 

이 설정파일은 Sample 프로젝트에서 사용하는 데이터베이스와 연관되는 DataSource를 설정하는 환경 파일이다. 일단 여기에서 사용된 <bean> 태그를 보도록 하자.

 

<jdbc:embedded-database id="dataSource" type="HSQL">
	<jdbc:script location= "classpath:/db/sampledb.sql"/>
</jdbc:embedded-database>

 

context-datasource.xml에서 이 태그를 제외한 나머지 내용들은 모두 주석처리 되어 있는데 주석처리 된 내용은 HSQL을 서버모드로 사용했을때, DB를 Oracle을 사용할 때, DB를 MySQL로 사용할 때 이에 대한 DataSource를 Apache Common-DBCP를 이용해서 생성하는 <bean> 태그들이다. Sample 프로젝트에서 사용할 DB가 주석처리된 내용의 DB에서 사용하고자 할때 해당 주석을 풀고 사용하면 된다. 단 주석을 풀은 뒤의 몇몇 property 내용들은 본인 환경에 맞춰서 수정해야 한다. 또한 pom.xml에서도 주석처리 되어 있는 부분을 본인 환경에 맞춰 해당 라이브러리를 쓸 수 있게끔 주석을 풀어야 한다. 

 

context-datasource.xml에 대한 설명은 이쯤 해두고 이제부터 <jdbc-embedded-database> 태그에 대해 설명하도록 하겠다. Spring에서는 메모리 기반의 내장형 DB를 지원하는데 일반적으로 자바에서 많이 사용되는 내장형 DB인 Derby, HSQL, H2를 지원한다. 위에서 언급한 태그는 바로 이런 내장형 DB를 Spring에서 사용하기 위함이다. type을 HSQL로 지정해서 HSQL을 사용한다고 설정하고, <jdbc:script> 태그를 사용해서 메모리 DB가 로드될때 어떤 SQL 스크립트를 실행시켜야 하는지를 지정한다. 현재 <jdbc:script> 태그에 지정된 /db/sample.sql(이 파일은 src/main/resources/db에 들어가보면 찾을수 있다)을 보면 테이블을 생성하고 생성된 테이블에 데이터를 insert 하는 SQL 문이 들어있다. 그리고 이렇게 올라간 메모리 DB에 대한 DataSource를 Spring의 Bean으로 등록하는 것이다. 즉 sampledb.sql을 사용해서 HSQL 메모리 DB를 올린뒤 이 DB와 연결된 id가 dataSource인 DataSource 타입의 Spring Bean을 <jdbc:embedded-database> 태그가 한다고 보면 된다.

 

그러면 이러한 설정을 Java Config 방식으로 어떻게 바꾸는지 알아보자. Spring은 메모리 기반의 내장형 DB를 올릴때 내장형 DB 빌더를 사용하게 되는데, 이때 사용되는 클래스가 EmbeddedDatabaseBuilder 클래스이다. 이 클래스 객체를 만든뒤에 어떤 타입의 DB를 사용할 것인지 설정하고 메모리 DB를 올릴때 사용할 SQL 파일을 지정한뒤 빌드명령을 내리면 DataSource 인터페이스를 구현한 EmbeddedDatabase 타입의 객체가 return이 된다. 이 내용을 숙지한뒤 <jdbc:embedded-database> 태그를 Java Config 설정으로 바꾼 다음의 내용을 보면 이해가 될 것이다.

 

@Bean
public DataSource dataSource(){
	return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:db/sampledb.sql")
			.build();
}

 

Sample 프로젝트에서는 그런 설정이 아니지만 만약 내장형 DB를 초기화 시키는 SQL 파일이 2개 이상이라면 addScript 메소드를 해당 SQL 파일 갯수만큼 사용하면 된다(.addScript("classpath:db/sampledb.sql").addScript("classpath:/db/inputdata.sql")) addScript 메소드의 return 타입이 EmbeddedDatabaseBuilder 클래스 객체이기 때문에 이런 방법이 가능한 것이다. 그러나 Sample 프로젝트에서는 샘플로 어디서든 작동하기 쉽게끔 하기 위해 메모리 DB 기반으로 했지만 현실적으로 업무 프로젝트에서는 메모리 DB 기반으로 사용하는 경우는 거의 없다. 일반적으로 우리가 잘 알고 있는 RDBMS 서버와 연동해서 작업하는 경우가 대부분이기 때문이다. 그래서 이럴 경우를 대비한 DataSource Java Config 방법을 지금부터 설명하도록 하겠다.

 

현업에서 우리가 DB를 연동할때는 Connection Pool을 이용한 DataSource를 만들어 이를 사용하게 된다. 그러나 이렇게 Connection Pool을 이용하는 경우도 크게 2가지 경우로 나눌수가 있는데...

 

  • 프로젝트에서 자체적으로 Connection Pool을 생성한 뒤 이를 사용하는 경우
  • WAS나 Web Container(ex : Tomcat, Jetty 등)에서 Connection Pool을 생성한 뒤 프로젝트에서는 JNDI로 접근하여 이를 사용하는 경우

 

어느 쪽이든 Spring에서는 이용하는데 어려움이 없다. 일단 Sample 프로젝트에서는 첫번째 경우로 해당이 된다. 현재는 context-datasource.xml 에서 주석처리 되어 있는 부분이 그 첫번째 경우이다. 주석처리 되어 있는 셋팅을 보면 Apache Common DBCP를 이용한 Connection Pool을 생성해서 이를 DataSource로 사용하고 있다. 이 글에서는 2가지의 경우를 모두 설명하도록 하겠다.

 

먼저 첫번째 경우인 프로젝트에서 자체적으로 Connection Pool을 생성한 뒤 이를 사용하는 경우에 대해 설명하도록 하겠다. 여기서는 주석처리 된 것중 다음의 Oracle 것을 기준으로 삼아 설명하도록 하겠다. 먼저 context-datasource.xml에서는 다음과 같이 되어 있다.

 

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
	<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:example" />
	<property name="username" value="user"/>
	<property name="password" value="password"/>
</bean>

 

JDBC를 이용한 프로그래밍을 해본 사람이라면 저 태그만 봐도 어떤 내용이 들어가야 하는지 짐작은 될 것이다. 일단 driverClassName 속성은 JDBC 드라이버 클래스 이름을 넣어주고 URL은 JDBC 접속 URL을 넣어준다. 또 username과 password는 DB 계정 아이디와 패스워드를 넣어준다. 이렇게 4가지를 넣어주면 나머지 속성들은 정해진 기본값에 따라 Connection Pool이 만들어지면서 Spring에서 사용할 수 있는 DataSource Spring Bean이 등록된다. 다만 한가지 생소한 부분이 detroy-method 부분인데 저 속성은 등록된 Spring Bean이 JVM에서 내려가기 전에 실행되어져야 할 method 이름을 지정하는 것이다. Connection Pool의 경우 Connection Pool이 종료되기 전에 기존에 자신이 DB와 연결을 가지고 있던 Connection들을 끊는 과정을 거치는 것이 일반적이므로 destroy-method를 통해 이 작업들을 진행하게 된다. 이 설정에서는 close 메소드를 실행하도록 지정되었다. 부가적으로 설명하자면 Spring Bean이 JVM에 등록이 된뒤 자동적으로 먼저 실행할 메소드를 지정할 수도 있다. 이런 상황은 특정 클래스 객체가 Spring Bean으로 올라 간 뒤에 초기화 작업을 별도로 진행해야 할 때 이러한 메소드를 지정할 수 있다. 이럴때 <bean> 태그에서는 init-method 속성에 실행해야 할 메소드 이름을 지정해주면 된다.

 

<bean> 태그에 대한 설명은 이정도로 마치고 이를 Java Config 방식으로 바꾸어보자. 이 경우엔 아래와 같이 바꿀 수 있다.

 

@Bean(destroyMethod="close")
public DataSource dataSource(){
	
	BasicDataSource basicDataSource = new BasicDataSource();
	basicDataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
	basicDataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:example");
	basicDataSource.setUsername("user");
	basicDataSource.setPassword("password");
	return basicDataSource;
	
}

 

소스를 보면 이해하는데 별로 어려움이 없을것이다. 단 설명을 추가하자면 <bean> 태그의 destroy-method 속성 기능과 동일한 역할을 @Bean 어노테이션의 destroyMethod가 해주고 있다. 위에서 잠깐 언급한 속성인 init-method 속성의 경우는 @Bean 어노테이션의 initMethod가 해주고 있다. 

 

첫번째 경우는 이정도로 설명하고 이제 두번째 경우, 즉 WAS나 Web Container에 Connection Pool을 생성한 뒤 이를 JNDI에서 접근하는 방법에 대해 설명하도록 하겠다. Connection Pool을 생성하는 부분은 WAS나 Web Container에 의존적이기 때문에 어떤 것을 사용하느냐에 따라 그 설정 방법이 달라지므로 여기서는 그 방법에 대해서는 설명하지 않겠다. 대신 여기서는 WAS나 Web Container에 설정된 Connection Pool을 JNDI로 어떤 이름으로 접근할 수 있다..라고 셋팅이 된 시점에서 이때 XML로 설정하는 방법과 Java Config 로 설정하는 방법에 대해 설명하도록 하겠다. XML로 설정하는 방법은 2가지가 있다. XML 환경 설정 파일에 jee namespace및 관련 스키마를 추가한뒤 <jee:jndi-lookup> 태그를 사용하는 방법과 Spring에서 제공하는 JndiObjectFactoryBean 클래스를 <bean> 태그에서 사용하는 방법 이렇게 2가지다. 먼저 <jee:jndi-lookup> 태그를 사용하는 방법이다.

 

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/oracle" resource-ref="true" />

 

<bean> 태그에서 JndiObjectFactoryBean 클래스를 사용하는 방법은 다음과 같이 한다.

 

<bean id="dataSource"  class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName" value="jdbc/oracle"/>
    <property name="resourceRef" value="true" />
</bean>

 

<jee:jndi-lookup> 태그의 jndi-name 속성, JndiObjectFactoryBean 클래스의 jndiName 속성에는 WAS 또는 Web Container에 설정되어 있는 Connection Pool을 접근할 JNDI 이름을 설정해준다. 그리고 <jee:jndi-lookup> 태그의 resource-ref 속성, JndiOnjectFactoryBean 클래스의 resourceRef 속성에는 이런 Connection Pool을 찾는 JNDI lookup 작업이 J2EE 컨테이너에서 일어나는지를 설정한다. 이 값은 default로 false로 설정되어 있는데, 이 속성이 true로 설정될 경우 이런 JNDI lookup 작업이 J2EE 컨테이너에서 발생한다는 걸로 인지하고 jndi-name 속성 또는 jndiName 속성에 입력된 JNDI 이름 앞에 java:comp/env/ 란 문자열을 자동으로 붙인다. 즉 위의 설정된 내용으로 다시 설명하면 JNDI 이름을 jdbc/oracle로 줄 경우에 실제로 JNDI lookup 작업시엔 java:comp/env/jdbc/oracle 이란 값으로 JNDI lookup 작업을 진행하게 된다.

 

그럼 이렇게 설정한 내용을 Java Config로 설정하는 방법을 알아보자. 여기서부터는 우리가 알고 있는 내용을 조금 비틀어야 할 필요성이 있다. 지금까지 우리가 알고 진행했던 방법은 <bean> 태그에서 사용했던 클래스를 new를 이용해서 객체로 생성한뒤 객체에 필요한 설정작업을 하고 이 객체를 return 하고 있었다. 그러나 이 클래스는 FactoryBean 인터페이스를 구현하고 있다. 즉 Spring에서는 이 FactoryBean 인터페이스를 구현한 클래스 객체를 사용하는게 아니라 FactoryBean 인터페이스를 통해 만들어내는 클래스 객체를 사용하는 것이다. 공장..이란 개념을 생각해보자. 공장 자체를 사용하는게 아니라 공장에서 만들어낸 물건을 이용하는거 아닌가..이런 관점에서 이해를 하자. 그래서 FactoryBean 인터페이스를 구현한 것을 그냥 사용하는 것은 아무 의미가 없다. 그래서 지금 설명할 이 부분은 기존 방법으로 접근하지 않고 철저하게 JNDI 관점하에서 접근하겠다.

 

Spring에서는 JNDI를 통해서 DataSource를 lookup 한뒤 이렇게 찾은 DataSource를 return 하는 말그대로 DataSource를 JNDI로 찾는 전용 클래스를 제공하는데 JndiDataSourceLookup 클래스가 그것이다. 이 클래스를 이용해서 DataSource를 찾는 Java Config 설정은 다음과 같다.

 

@Bean
public DataSource dataSource() throws DataSourceLookupFailureException {
	JndiDataSourceLookup jdsl = new JndiDataSourceLookup();
	jdsl.setResourceRef(true);
	DataSource dataSource = jdsl.getDataSource("jdbc/oracle");
	return dataSource;
}

 

소스를 보면 JndiDataSourceLookup 클래스 객체를 생성한 뒤 setResourceRef 메소드를 통해 위에서 설명했던 resource-ref 속성에 대한 설정을 한다. 그리고 이렇게 생성한 객체의 getDataSource 메소드에 파라미터로 JNDI 문자열을 넣어줌으로써 DataSource를 찾은뒤 이를 return 해주고 있다. 다만 getDataSource 메소드의 경우 입력받은 문자열로 DataSource로 찾지 못할 경우를 대비해서 이럴 경우에 DataSourceLookupFailureException 예외를 던지기 때문에 bean을 생성하는 메소드 정의시 이 예외를 던지게끔 했다. 위에서 설명했던 XML 설정과 비교해보면 이해하는데 큰 무리가 없을 것이다. 

 

또 다른 방법은 DataSource를 찾는데만 특화된 것이 아닌 JNDI를 통해 어떠한 형태의 자원이라도 찾는 클래스를 제공해주는데 JndiTemplate 클래스가 그것이다. JndiTemplate 클래스를 이용해서 DataSource를 찾을때는 다음과 같이 한다.

 

@Bean
public DataSource dataSource() throws NamingException {
	JndiTemplate jndiTemplate = new JndiTemplate();
	DataSource dataSource = jndiTemplate.lookup("java:comp/env/jdbc/oracle", DataSource.class);
	return dataSource;
}

 

이것도 소스를 보면 이해하는데 별 문제가 없을 것이다. 다만 추가 설명을 해야 하는데 lookup 메소드 부분이 그것이다. 이제까지는 sample로 사용된 이름을 jdbc/oracle로 했지면 여기에서는 java:comp/env/jdbc/oracle 이렇게 사용했다. 위에서 설명했던 resource-ref(resourceRef) 속성을 JndiTemplate 클래스에서는 사용할 수 없기 때문에 java:comp/env/ 문자열을 인위적으로 결합시켜서 찾는 방식을 사용한다. 그리고 2번째 파라미터로 DataSource.class 를 지정함으로써 찾고자 하는 자원이 DataSource 클래스 타입이 아니면 예외를 던지게 했다. 찾는 과정에서 문제가 발생하면 NamingException 예외를 던지기 때문에 bean을 생성하는 메소드 정의시 이 예외를 던지게끔 했다.

 

마지막으로 지금까지 설명한 모든 내용이 적용된 context-datasource.xml을 Java Config 방식으로 변환한 ContextDataSource 클래스의 전체 소스를 올리는 것으로 이번 글을 마무리 짓겠다. context-datasource.xml에서 사용하는 embeded database 사용하는 부분은 그대로 두고 나머지 이 글에서 설명했던 부분은 주석처리 했다.

 

package egovframework.example.config.root;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.jndi.JndiTemplate;

@Configuration
public class ContextDataSource {

	@Bean
	public DataSource dataSource(){
		return new EmbeddedDatabaseBuilder()
				.setType(EmbeddedDatabaseType.HSQL)
				.addScript("classpath:db/sampledb.sql")
				.build();
	}
	
	/*
	@Bean(destroyMethod="close")
	public DataSource dataSource(){
		
		BasicDataSource basicDataSource = new BasicDataSource();
		basicDataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		basicDataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:example");
		basicDataSource.setUsername("user");
		basicDataSource.setPassword("password");
		return basicDataSource;
		
	}
	
	@Bean
	public DataSource dataSource() throws DataSourceLookupFailureException {
		
		JndiDataSourceLookup jdsl = new JndiDataSourceLookup();
		jdsl.setResourceRef(true);
		DataSource dataSource = jdsl.getDataSource("jdbc/oracle");
		return dataSource;
		
	}
	
	@Bean
	public DataSource dataSource() throws NamingException {
		
		JndiTemplate jndiTemplate = new JndiTemplate();
		DataSource dataSource = jndiTemplate.lookup("java:comp/env/jdbc/oracle", DataSource.class);
		return dataSource;
		
	}
	*/
	
}

 

 

목     차

 1. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(환경구축)

 2. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(기초)

 3. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-aspect.xml 변환)

 4. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-common.xml 변환)

 5. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-datasource.xml 변환)

 6. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-sqlMap.xml, context-mapper.xml 변환)

 7. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-transaction.xml 변환)

 8. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-idgen.xml, context-properties.xml, context-validator.xml 변환)

 9. 작성된 Java Config 클래스 파일들을 실제 등록해보자(RootContext 클래스 제작)

 10. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 1

 11. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 2

 12. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 3