JNDI 사용하여 DB연결하기.

떡ol·2022년 10월 16일
0

1. JNDI란?

JNDI(Java Naming and Directory Interface)는 디렉터리 서비스에서 제공하는 데이터 및 객체를 발견(discover)하고 참고(lookup) 하기 위한 자바 API다.

JNDI는 일반적으로 다음의 용도로 쓰인다:

  • 자바 애플리케이션을 외부 디렉터리 서비스에 연결 (예: 주소 데이터베이스 또는 LDAP 서버)
  • 자바 애플릿이 호스팅 웹 컨테이너가 제공하는 구성 정보를 참고

간단히 요약하자면 우리가 연결하고 싶은 데이터베이스의 DB Pool을 미리 Naming 시켜주는 방법 중 하나이다. 우리가 저장해놓은 WAS 의 데이터베이스 정보에 JNDI를 설정해 놓으면 웹 애플리케이션은 JNDI만 호출하면 간단해진다.

그럼 왜 사용하게 되는걸까? 우리는 보통 JDBC를 설정해서 개발을 한다. 하지만 웹 애플리케이션을 운영서버로 만들경우 얘기가 달라진다. 그 이유는

개발을 한 사람과 실제 서비스를 운영하는 Admin은 다른 경우가 많기 때문에 소스 레벨에서 설정되어 있는 것보다 WAS에서 설정되어 있는 것을 선호한다.
또한 WAS에 여러 개의 웹 애플리케이션을 올려서 사용하기 때문에 WAS에서 한 번에 설정해 주는 것이 자원낭비를 줄일 수 있습니다.
또한 장애가 나거나 성능이 정상적이지 못하면 다른 한 서버가 대신 일을 해줄 수 있습니다.

내용은 블로그에서 퍼온내용인데 딱 저 WAS를 한번에 설정해준다. 이 말이 정말 와닿고 감동적이다... 혹여나 password라든가 DB주소가 바꼈다하면 수많은 WAS서버에서 bean Confing에서 하나하나 변경해줄 필요가 없다. (내용의 자료 출처는 하단 참고)

자 그럼 사용 방법을 익혀보자.

2. 관련 Lib import하기

아래의 library를 Maven이나 Gradle 또는 수동으로 등록해준다.

'org.mybatis.spring.boot:mybatis-spring-boot-starter'
'org.springframework.boot:spring-boot-starter-jdbc'
'mysql:mysql-connector-java'
'org.apache.tomcat:tomcat-dbcp'

3. Tomcat에 JDBC 생성하기.

3.1 server.xml config 설정

아래는 우리가 가장 처음 배우는(?) 기초적인 DB연결 방식중 하나이다.

   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:HostIp주소:포트번호:DB명"/>
        <property name="username" value="유저이름"/>
        <property name="password" value="비밀번호"/>
    </bean>

DataSource와 관련된 객체를 context-param에 주입시켜서 사용한다. DataSource에 연결하는 방식이나 DB종류가 다양하므로 예제와는 다른 객체(DriverManagerDataSource)를 주입시키는 경우도 많다. 하지만 변수들은 대부분 driver종류, url, name, pw는 필수로 사용할것이다.

위에 과정을 tomcat server.xml에 Resource로 생성하고 이를 웹 어플리케이션에서 JNDI lookup 으로 불어와 사용할 것이다.

 <!--Tomcat config파일중 sever.xml-->
 <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    
    <!-- 새로 생성한 부분start-->
    <Resource auth="Container" 
    driverClassName="oracle.jdbc.OracleDriver" 
    maxActive="10" maxIdle="10" maxWait="-1" 
    name="jdbc/db" type="javax.sql.DataSource" 
    url="jdbc:oracle:thin:@localhost:1521:xe" 
    username="java"  password="1234"/>
    <!-- 새로 생성한 부분end-->
    
	<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
  </GlobalNamingResources>
  
  <!-- 중략...-->
  
  <!-- 새로 생성한 부분start-->
  <Context docBase="MyProject" path="/" reloadable="false" source="org.eclipse.jst.jee.server:MyProject">
      	<ResourceLink global="jdbc/db" name="jdbc/db" type="javax.sql.DataSource"/>
        <!--GlobalNamingResources 작성안하고 바로 여기에 작성하여도 된다.
        <Resource auth="Container" 
         driverClassName="oracle.jdbc.OracleDriver" 
         maxActive="10" maxIdle="10" maxWait="-1" 
         name="jdbc/db" type="javax.sql.DataSource" 
         url="jdbc:oracle:thin:@localhost:1521:xe" 
         username="java"  password="1234"/>-->
  </Context>
  <!-- 새로 생성한 부분end-->
  

다음과 같이 GlobalNamingResources에 Resource를 만들어주고, 하단에 Context에서 Link하여 사용한다. (원래 있는건 냅두자.)

GlobalNamingResources 속성

  • driverClassName : 어떤 DB drive를 참조할지 작성한다. 나는 Oracle
  • type : DataSource타입...
  • name : 해당 Resource의 변수명, Global 변수로 tomcat내에서 어디든 불러와 사용된다. DB커넥트 관련이라 그런지 'jdbc/별명' 이런식으로 작성한다.
  • url, username, password : DB주소 및 사용자이름 비밀번호이다. 주소 같은 경우에는 developer에서 정보확인하면 나온다.
  • auth : 권한이다. Application 또는 Container 두가지로 나뉜다. 웹상에서 Application에 대한 예제가 잘 안보여서 확실한건 아니지만, Container의 경우, 해당자원을 서버에서 관리하고 Application은 웹소스에서 관리하나보다(?)

Context안 Resource 속성

  • global : 위에 선언된 name을 작성
  • name : 이 name이 웹 어플리케이션에 넘겨줄 변수명이 된다.
  • type : DataSource 위에랑같이 쓴다...

이제 남은건 전부 DBCP에 대한 내용이다. 따로 설명하겠다.

3.2 DB Connection Pool (DBCP)

웹 어플리케이션을 운영하다보면 DB가 다양한 이유로 뻗는 경우가 많다. Out of memory가 대표적이다. 그럼 어디서 자원(memory)을 많이 쓸까? JDBC 드라이버를 연결함에 있어서 가장 시간을 많이 잡아먹는 부분이 DB와 연결되는 시점이다. 그 후 Query Select하는 부분이나 Commit하는부분은 일도 아니다.
하지만 웹 어플리케이션에서는 하나의 작업을 처리하더라도 2개 이상의 query를 실행해야하는 경우가 많다. 이게 전부 메모리를 잡아먹는 일이다.
이러한 문제를 해결하고자 객체를 생성하는 부분에 대한 비용과 대기 시간을 줄이고, 네트워크 연결에 대한 부담을 줄일수 있는 방법이 있는데 바로, DBCP(Database Connection Pool)이다

속성명설명비고
initialSize최초로 커넥션을 맺을 때 Connection Pool에 생성되는 커넥션 개수Default : 0
minIdle최소한으로 유지할 커넥션 개수Default : 0
maxIdle사용한 커넥션을 풀에 반납 시
최대로 유지할 개수
Default : 8
maxActive동시에 사용할 수 있는 최대 커넥션Default : 8
maxIdle과 대부분 일치시킨다.
커넥션 개수와 관련된 가장 중요한 성능 요소
maxWait커넥션 풀에 연결 가능 커넥션이 없을 경우
반납되는 커넥션을 대기하는 시간
(단위 : millisecond, 10,000ms = 10초)
값을 설정하지 않는 경우 응답올때까지 대기

이밖에 속성들은 웹에서 검색해서 참고하자.

✔ DBCP 방향

maxActive >= initialSize
최대 커넥션 개수는 초기에 생성할 커넥션 개수와 같거나 크게 설정해야 한다.

maxActive = maxIdle
maxActive 값과 maxIdle 값은 같은 것이 바람직하다. 만약 둘의 값이 아래와 같다고 가정해보자.

maxActive = 10
maxIdle = 5
항상 커넥션을 동시에 5개는 사용하고 있는 상황에서 1개의 커넥션이 추가로 요청된다면 maxActive = 10이므로 1개의 추가 커넥션을 데이터베이스에 연결한 후 Pool은 비즈니스 로직으로 커넥션을 전달한다. 이후 비즈니스 로직이 커넥션을 사용 후 풀에 반납할 경우, maxIdle = 5에 영향을 받아 커넥션을 실제로 닫아버리므로, 일부 커넥션을 매번 생성했다 닫는 비용이 발생할 수 있다.

initialSize와 maxActive, maxIdle, minIdle 항목을 동일한 값으로 통일해도 무방하다.
커넥션 개수와 관련된 가장 중요한 성능 요소는 일반적으로 커넥션의 최대 개수다. 4개 항목의 설정 값 차이는 성능을 좌우하는 중요 변수는 아니다.

maxActive 값은 DBMS의 설정과 애플리케이션 서버의 개수, Apache, Tomcat에서 동시에 처리할 수 있는 사용자 수 등을 고려해서 설정해야 한다. DBMS가 수용할 수 있는 커넥션 개수를 확인한 후에 애플리케이션 서버 인스턴스 1개가 사용하기에 적절한 개수를 설정한다. 사용자가 몰려서 커넥션을 많이 사용할 때는 maxActive 값이 충분히 크지 않다면 병목 지점이 될 수 있다. 반대로 사용자가 적어서 사용 중인 커넥션이 많지 않은 시스템에서는 maxActive 값을 지나치게 작게 설정하지 않는 한 성능에 큰 영향이 없다.

Commons DBCP에서는 DBMS에 로그인을 시도하고 있는 커넥션도 사용 중인 것으로 간주한다. 만약 DBMS에 로그인을 시도하고 있는 상태에서 무한으로 대기하고 있다면, 애플리케이션에서 모든 커넥션이 사용 중인 상태가 돼 새로운 요청을 처리하지 못할 수도 있다. 이런 경우 장애 확산을 최소화하려면 Microsoft SQL Server의 JDBC 드라이버에서 설정하는 loginTimeOut 속성같은 JDBC 드라이버별 타임아웃 속성을 설정하는 것이 좋다.

WAS의 Thread 수와 Connection Pool 수의 관계
WAS에서 설정해야 하는 값이 굉장히 많지만, 그 중 가장 성능에 많은 영향을 주는 부분은 Thread와 Connection Pool의 개수 이다.

이들 값은 직접적으로 메모리와 관련이 있기 때문에, 많이 사용하면 할 수록 메모리를 많이 점유하게 된다. 그렇다고 반대로 메모리를 위해 적게 지정한다면, 서버에서는 많은 요청을 처리하지 못하고 대기 할 수 밖에 없다.

3. WAS에서 설정하기 (Java 설정)

톰캣에서의 설정은 이만하면 되었다. 나머지 작업을 진행해보자.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<jee:jndi-lookup id="dataSource" jndi-name="jdbc/db"/>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="classpath:/sqlmap/sqlmapconfig.xml"/> 
	</bean>
	
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory"/>
	</bean>
	
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value ="com.mapper"/>
		<property name="sqlSessionTemplateBeanName" value="sqlSession"/>
	</bean>

</beans>

data_context.xml 파일을 만들고 namespaces에 jee를 추가해준 후 작성한다.

  • jndi-name 지정했던 변수명을 써준다. web.xml에 있는 resource-ref를 참고하는것이다. (아래 참고)
  • SqlSessionFactory 는 데이터베이스와의 연결과 SQL의 실행에 대한 모든 것을 가진 가장 중요한 객체다. 이 객체가 DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
    SqlSessionFactory를 생성해주는 SqlSessionFactoryBean 객체를 먼저 설정하여야 한다. configLocation은 Query가 실행할때 추가 실행 및 조건관련 객체를 불러올때 작성하는 페이지이다.
    이 페이지를 학습 후 여기를 보면 config 공부에 도움이 될것이다.
<!--configLocation.xml 작성, 아직 설정할 내용이 없으니 선언만 해두자.-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
  • SqlSessionTemplate 은 마이바티스 스프링 연동모듈의 핵심 SqlSession을 구현하고 코드에서 SqlSession를 대체하는 역할을 한다. 쓰레드에 안전하고 여러개의 DAO나 매퍼에서 공유할수 있다. 스프링 DAO 모듈의 JdbcTemplate과 동일
  • 이렇게 만들어진 Template을 MapperScannerConfigurer에 주입시켜주며 basePackage를 지정하여 SQL Query 파일들을 관리하는게 가능하다.

위사진의 하나의 Interface file 과 SQL Mapper 파일을 살펴보자.

//DBtestMP.java 파일이다.
package com.mapper;

public interface DBtestMP {
	public String getUser();
}

DBtestMP.java의 파일 위치는 MapperScannerConfigurer에 설정한 basePackage아래에 작성한다. Interface파일을 작성하였으면 SQL Mapper 도 작성해보자

<!--DBtestMP.xml 파일이다.-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.DBtestMP">

	<select id="getUser" resultType="String">
		SELECT ID FROM USERS WHERE ROWNUM= 1
	</select>
    
</mapper>

DBtestMP.xml의 위치도 같은 경로에 맞춰준다. 이때 namespace가 인터페이스파일 경로 및 이름이 같아야한다. 당연히 mapper의 id는 Interface의 추상매서드와 이름과 같아야한다.

select가 뭔지, 다른 구문은 어떻게 작성하는지, resultType가 뭔지는 넘어가겠다.

4. 테스트

이제 서버를 가동해서 테스트해보면된다.

	@Resource
	DBtestMP dbtestMP;

Resouce로 Service단에서 불러와 사용하면 된다.

그런데, 몇가지 오류가 발생할 수도 있다.

4.1 Cannot create JDBC driver of class '' for connect URL 'null'

이 경우 거의 무조건 오타일 확률이 있다. server.xml에서 중간 중간에 콜론 잘 입력했는지, 아이디 비번은 맞는지 다시 확인해 보자.

jdbc:oracle:thin:@localhost:1521:xe
username="java" password="1234"

4.2 Cannot load JDBC driver class 'com.mysql.jdbc.Driver'

다음에 경우에는 jdbc.jar가 빠져서 그런 경우이다. 나는 oracle을 사용하므로 ojdbc8.jar를 웹에서 직접 받아 등록하였다. 여기 중요한점은 java build path 가 아니라 tomcat classpath라는 점이다.

다음과 같이 진행하여 등록해주면 된다. 왜 Project쪽이아닌 Server쪽인가?
여기를 방문하여 한번 읽어보기를 추천한다.

5. 마침

여기까지가 JNDI 설명이었다. Mapper에서 insert upate, type에는 Map List Custom 객체 타입 등등 연습해보면 좋다.


참고자료들___
(참고) [JSP] Cannot create JDBC driver of class '' for connect URL 'null' & Cannot load JDBC driver class 'com.mysql.jdbc.Driver'
(참고) Tomcat 서버 DataSource 설정 방법 (+JNDI)
(참고) [Spring] Mybatis SqlSessionFactory 란
(참고) Commons DBCP 설정값 알아보기
(참고) Commons DBCP 이해하기

profile
하이

0개의 댓글