반응형
해당 글에서는 JDBC, JNDI, DBCP에 대해 이해를 돕기 위해 작성한 글입니다.
1) JDBC(Java Database Connectivity)
💡 JDBC(Java Database Connectivity)
- 자바에서 데이터베이스에 접근할 수 있도록 해주는 API를 의미합니다. 이는 관계형 데이터베이스에서 데이터를 쿼리 하거나 업데이트하는 방법을 제공합니다.
- 자바 프로그램에서는 JDBC 드라이버를 통해 데이터베이스에 연결하고 이 드라이버는 데이터베이스에 대한 모든 세부사항을 처리합니다.
- 이를 통해 개발자는 데이터베이스의 내부 작동 방식에 대해 신경 쓸 필요 없이 데이터를 쉽게 사용할 수 있습니다.
1. 주요 기능
JDBC 기능 | 설명 |
데이터베이스 연결 | 다양한 종류의 데이터베이스에 연결할 수 있습니다. (MySQL, Oracle, SQL Server 등) |
SQL 문 실행 | SQL 문을 실행하고 결과를 Java 어플리케이션에서 사용할 수 있습니다. |
데이터베이스 관리 | 데이터베이스 테이블을 생성하거나 수정하고, 데이터를 삽입, 업데이트, 삭제할 수 있습니다. |
트랜잭션 관리 | 데이터베이스 트랜잭션을 관리하고, 여러 작업을 하나의 단위로 묶을 수 있습니다. |
[더 알아보기]
💡 spring-boot-starter-jdbc
- Spring Boot 프로젝트에서 JDBC를 더 쉽게 사용할 수 있도록 도와주는 스타터입니다.
- 이를 통해 데이터베이스 Connection Pool과 같은 JDBC 관련된 빈들을 자동으로 설정해 줍니다. 이를 통해 개발자는 JDBC 연결 관련 설정이나 세부사항에 대해 신경 쓸 필요 없이 비즈니스 로직 개발에 집중할 수 있습니다.
2. JDBC 주요 객체
💡 JDBC 주요 객체
- 주요 객체를 기반으로 DataSource에서 Connection을 얻어서 이를 통해 Statement를 만들고 이를 실행한 ResultSet을 얻는 과정에서 사용하는 각각의 객체입니다.
객체 | 설명 | 메서드 |
Datasource | 데이터베이스에 연결하기 위한 인터페이스이며 데이터베이스에 연결하기 위한 모든 세부 정보를 캡슐화 합니다. | |
Connection | 애플리케이션과 데이터베이스 간의 연결을 나타내는 객체. SQL 문을 실행하고 결과를 반환받는 역할 | createStatement(): Statement 객체를 생성 |
Statement | SQL 문을 데이터베이스에 보내는 역할을 수행하는 객체 | executeQuery(): SQL SELECT 문을 실행하면 ResultSet 객체가 반환됨 |
ResultSet | SQL 쿼리의 결과를 나타내는 데이터의 집합 | next(): 결과 집합의 다음 행에 액세스 |
💡 [참고] Spring Boot 내에서 application.properties 설정하는 과정에서 datasource를 통해서 환경설정을 구성합니다.
3. 처리과정
💡 처리과정
- Java Application - JDBC - DB 간에 처리되는 과정으로 자바 애플리케이션에서 데이터베이스에 접근하고 데이터를 처리하는 과정을 설명합니다.
- 해당 처리과정은 클라이언트의 SQL 요청이 있을 때마다 아래의 과정을 수행하게 됩니다.
1. JDBC 로드
- JDBC 드라이버를 메모리에 로드합니다. 이를 통해 자바 애플리케이션과 데이터베이스 간의 통신이 가능해집니다.
2. Connection 객체 생성
- 데이터베이스에 연결을 수립합니다. 이 연결은 데이터베이스와의 모든 통신을 위한 기반을 제공합니다.
3. Statement 객체 생성
- 데이터베이스에 전송될 SQL 명령문을 포함하는 Statement 객체를 생성합니다. 이 객체를 통해 SQL 쿼리를 실행할 수 있습니다.
4. Query 수행
- SQL 쿼리를 실행하고, 데이터베이스로부터 결과를 반환받습니다.
5. ResultSet 객체로부터 데이터 저장
- Query의 결과를 ResultSet 객체에 저장합니다. 이 객체를 통해 Query의 결과를 애플리케이션에서 사용할 수 있습니다.
6. 리소스 해제
- 사용이 끝난 데이터베이스와의 연결을 종료하고, 메모리에서 JDBC 드라이버를 제거합니다. 이는 시스템 리소스를 효율적으로 관리하기 위한 중요한 단계입니다.
import java.sql.*;
public class Example {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. JDBC driver 로드
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. Connection 객체 생성
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/databaseName", "username", "password");
// 3. Statement 객체 생성
stmt = conn.createStatement();
// 4. Query 수행
rs = stmt.executeQuery("SELECT * FROM tableName");
// 5. ResultSet 객체로부터 데이터 저장
while(rs.next()) {
// Retrieve by column name
int id = rs.getInt("id");
String name = rs.getString("name");
// Display values
System.out.println("ID: " + id + ", Name: " + name);
}
} catch(SQLException se) {
se.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
// 6. 리소스 해제
try {
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
} catch(SQLException se) {
se.printStackTrace();
}
}
}
}
3. 구성 과정
3.1. 의존성 주입
💡 의존성 주입
- spring-boot-starter-data-jdbc를 통해 PostgreSQL 데이터베이스와 통신을 합니다.
- org.postgresql:postgresql를 통해 실제 데이터베이스를 연결하기 위한 JDBC 드라이버입니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' // Spring Boot JDBC
runtimeOnly 'org.postgresql:postgresql' // PostgreSQL
}
3.2. application.properties 설정
💡 application.properties 설정
- driver-class-name: 데이터베이스 연결을 위한 JDBC 드라이버 클래스를 지정합니다.
- url: 연결하려는 데이터베이스의 URL을 지정합니다.
- username: 데이터베이스 접속을 위한 사용자 이름을 입력합니다.
- password: 데이터베이스 접속을 위한 사용자 비밀번호를 입력합니다.
spring:
# PostgreSQL DB 설정
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/postgres
username: xxx
password: xxx
2.3. JdbcTemplate.java
💡 JdbcTemplate.java
- User 테이블의 사용자 정보를 모두 조회하기 위한 jdbcTemplate을 활용한 예시입니다.
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<User> findAllUsers() {
String sql = "SELECT * FROM User";
return jdbcTemplate.query(sql, (resultSet, rowNum) -> new User(
resultSet.getLong("id"),
resultSet.getString("name"),
resultSet.getString("email")
));
}
}
[ 더 알아보기 ]
💡 JdbcTemplate 대신에 퍼시스턴스 프레임워크(MyBatis, JPA)를 사용하는 거 같은데 맞는 걸까?
- JdbcTemplate는 JDBC 코드를 단순화하고 일반적인 오류를 방지하는 데 도움이 됩니다. 그러나 이는 여전히 SQL 쿼리를 작성하고 결과를 수동으로 매핑해야 하는 단점이 있습니다.
- 이러한 단점을 해결하기 위해 퍼시스턴스 프레임워크인 MyBatis를 이용하여 XML 형태로 SQL을 작성하는 편리한 방식이나 Spring Boot JPA 사용하여 SQL 쿼리를 작성하지 않고도 객체와 DB 컬럼을 매핑하는 방식을 이용하여 개발자가 집중해야 할 비즈니스 로직에 더 집중할 수 있게 돕습니다.
2) DBCP(DataBase Connection Pool)
💡 DBCP(Database Connection Pool)
- 애플리케이션이 시작될 때 미리 여러 개의 데이터베이스 커넥션(Connection)을 커넥션 풀(Connection Pool)에 생성해 놓고 필요할 때마다 사용 후 반환하여 이를 재사용할 수 있게 해 줍니다.
- 대표적으로 DBCP의 종류에는 Apache Commons DBCP, HikariCP 등이 있습니다.
[ 더 알아보기 ]
💡 DBCP를 사용하면 JDBC를 사용하지 않는 것일까?
- 결론적으로는 아닙니다. DBCP에서는 JDBC를 사용하여 데이터베이스와 연결하고 그 연결을 관리합니다. 그렇기에 DBCP를 사용한다고 해서 JDBC를 사용하지 않는 것은 아닙니다.
[참고] Apache Commons DBCP, HikariCP 공식사이트
1. 주요 기능
DBCP 주요 기능 | 설명 |
연결 객체 재사용 | 데이터베이스 연결 객체를 재사용하여 매번 새로운 연결을 생성하는 데 필요한 비용을 줄여줍니다. |
효율적인 리소스 관리 | 미리 생성한 연결 객체들을 풀에 보관하고, 요청이 있을 때마다 제공하고 사용이 끝나면 반환함으로써 리소스를 효율적으로 사용합니다. |
성능 향상 | 데이터베이스 연결을 미리 생성해두기 때문에, 연결을 요청할 때 연결 시간이 단축되어 성능이 향상됩니다. |
2. 처리 과정
💡 처리 과정
- DBCP에 처리과정에 대해서 알아봅니다.
2.1. 최초 커넥션 풀 생성
💡 최초 커넥션 풀 생성
1. 애플리케이션 서버 시작
- 웹 애플리케이션에서 서버가 실행되면 커넥션 풀이 생성 준비가 됩니다.
(* 해당 과정에서 Connection 연결 개수를 사전에 지정합니다.)
2. Connection Pool 생성
- Connection Pool 데이터베이스에 연결할 수 있는 여러 개의 Connection 객체를 미리 생성하고 관리하는 저장소입니다.
3. Connection 생성 → JDBC 연결 → Database
- JDBC를 이용하여 데이터베이스에 연결합니다. 연결이 성공하면, 이 Connection 객체는 Connection Pool에 추가됩니다.
- WAS가 데이터베이스 접속을 관리하므로 연결 풀링 같은 고급 기능을 이용할 수 있어 성능 향상에도 도움이 됩니다.
4. Connection Pool 구성 완료
- Connection Pool이 구성되면 애플리케이션은 필요할 때마다 Connection Pool에서 Connection 객체를 가져와서 사용할 수 있습니다.
[더 알아보기 ]
💡 커넥션 풀링(Connection Pooling)
- 여러 개의 데이터베이스 연결(Connection)을 묶어두어 필요할 때마다 재사용할 수 있게 하는 기술을 의미합니다.
- 이는 매번 새로운 연결을 생성하고 종료하는 대신 이미 생성된 연결을 재사용함으로써 자원을 효율적으로 사용할 수 있게 합니다.
2.2. 커넥션 풀 사용과정
💡 커넥션 풀 사용 과정
- 클라이언트와 서버와 데이터베이스 간의 관계에서 처리과정으로 확인해 봅니다.
1. DB 처리 요청 (Client → Application App)
- 클라이언트에서 Application App으로 DB 처리를 요청합니다.
2. Connection 요청 (Application App → Connection Pool)
- Application에서 DB 처리를 위한 Connection Pool로 커넥션을 요청합니다.
3. Connection 제공(Connection Pool → Application App)
- Connection Pool로부터 커넥션을 제공받습니다.
4. 애플리케이션 DB 처리 수행(Application App)
- 애플리케이션 내에서 DB관련 작업 처리를 수행합니다.
5. Connection 반환(Application App → Connection Pool)
- Application에서 사용한 Connection 객체에 대해서 Connection Pool로 반환을 합니다.
6. 처리결과 반환(Application App → Client)
- 서버에서 처리한 작업을 클라이언트에게 반환합니다.
[ 더 알아보기 ]
💡 JDBC와 다르게 Connection 객체를 매번 생성하는 게 아니라 미리 Connection 객체를 생성하고 사용하면 제공하고 사용을 마치면 반환하는 게 맞는 건가?
- JDBC에서 요청이 있을 때마다 Connection 객체를 생성하여 리소스를 많이 사용하였습니다.
- 이에 반해 DBCP 방식에서는 미리 Connection 객체를 여러 개 생성해 놓습니다. 요청이 있을 때마다 이 Connection 객체들 중 하나를 제공하고 사용이 끝나면 반환하여 리소스를 효율적으로 사용할 수 있습니다.
3. 구성과정
3.1. 의존성 주입
💡 의존성 주입
- spring-boot-starter-data-jdbc를 통해 PostgreSQL 데이터베이스와 통신을 합니다. 또한 JDBC 내장으로 DBCP를 포함하고 있습니다.
- org.postgresql:postgresql를 통해 실제 데이터베이스를 연결하기 위한 JDBC 드라이버입니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' // Spring Boot JDBC
runtimeOnly 'org.postgresql:postgresql' // PostgreSQL
}
3.2. application.properties
💡 application.properties
- 해당 설정 과정에서는 JDBC 내에 HikariCP를 이용하여 데이터베이스를 연결하는 과정을 구성하였습니다.
- driver-class-name: 사용할 JDBC 드라이버의 클래스 이름을 지정합니다.
- jdbc-url: 데이터베이스에 연결하기 위한 JDBC URL을 지정합니다.
- username: 데이터베이스 접속을 위한 사용자 이름을 지정합니다.
- password: 데이터베이스 접속을 위한 비밀번호를 지정합니다.
- pool-name: 데이터베이스 연결 풀의 이름을 지정합니다.
- maximum-pool-size: 풀이 유지할 수 있는 최대 연결 수를 지정합니다.
spring:
datasource:
hikari:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:log4jdbc:postgresql://localhost:5432/multiflex
username: localmaster
password: 1234
pool-name: Hikari Connection Pool # Alias
maximum-pool-size: 5
3.3. DBConfig
💡 DBConfig
- 해당 파일에서는 서버가 로드되는 시점에 @Configuration Hikari에 환경설정을 해주는 과정입니다.
- HikariCP 설정과 최초 데이터베이스 Connection을 수행합니다.
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@PropertySource("classpath:/application.properties")
public class DBConfig {
final
ApplicationContext applicationContext;
public DBConfig(ApplicationContext ac) {
this.applicationContext = ac;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource() {
return new HikariDataSource(hikariConfig());
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean session = new SqlSessionFactoryBean();
session.setDataSource(dataSource);
session.setMapperLocations(applicationContext.getResources("classpath:mapper/*.xml"));
session.setTypeAliasesPackage("com.test.testApi.model");
session.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
return session.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3.4. Application Server
💡 Application Server
- 아래와 같이 Application Server가 실행될 때 DBCP인 HikariCP가 수행되어 Connection Pool이 생성됨을 확인하였습니다.
💡 [참고] HikariCP에 대한 설정에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
3) JNDI(Java Naming and Directory Interface)
💡 JNDI(Java Naming and Directory Interface)
- 자바에서 이름 붙여진 객체를 찾거나 연결하기 위한 API입니다. 이를 통해 개발자들은 다양한 네이밍과 디렉터리 서비스에서 자바 소프트웨어 컴포넌트를 검색하고 사용할 수 있습니다.
- 즉 애플리케이션 내에서가 아닌 외부에서 이를 참조해서 가져오는 형태입니다. 데이터베이스 연결 측면에서 웹 애플리케이션 서버(WAS, Web Application Server)에 데이터베이스 접속 정보를 등록해 놓고, 이를 필요로 하는 웹 애플리케이션들이 WAS로부터 이 정보를 받아 사용할 수 있습니다.
- 이는 WAS에서 데이터베이스 접속 정보를 관리하게 되므로, 각 웹 애플리케이션에서 별도로 데이터베이스 접속 정보를 관리할 필요가 없다는 것을 의미합니다.
💡 [참고] 아래와 같이 외부환경에 접근하여 객체를 가져올 수 있습니다.
JNDI 조회 이름 | 관련 참조 |
java:comp/env | 응용 프로그램 환경 항목 |
java:comp/env/jdbc | JDBC 데이터 소스 자원 관리자 연결 팩토리 |
java:comp/env/ejb | EJB 참조 |
java:comp/UserTransaction | UserTransaction 참조 |
java:comp/env/mail | JavaMail 세션 연결 팩토리 |
java:comp/env/url | URL 연결 팩토리 |
java:comp/env/jms | JMS 연결 팩토리 및 대상 |
java:comp/ORB | 응용 프로그램 구성 요소 간에 공유되는 ORB 인스턴스 |
[ 더 알아보기 ]
💡JNDI를 이용하여 WAS 내에 데이터베이스 접속정보를 구성하면 무엇이 좋을까?
1. 애플리케이션 코드에서 데이터베이스 접속정보를 직접 다루지 않아도 되므로 코드의 가독성과 유지보수성이 향상됩니다.
2. 통일된 방식으로 리소스를 관리할 수 있으므로 개발의 복잡성을 줄일 수 있습니다.
3. WAS가 데이터베이스 접속을 관리하므로 연결 풀링 같은 고급 기능을 이용할 수 있어 성능 향상에도 도움이 됩니다.
1. 주요 기능
주요 기능 | 설명 |
Naming(네이밍) | 리소스나 애플리케이션간의 관계를 이름으로 연결 |
Directory(디렉토리 서비스) | 이메일 주소, 프린터, 파일 시스템 등과 같은 정보를 저장, 검색 |
Event Service(이벤트 서비스) | 이벤트 발생 시 특정 동작을 수행 |
Federation(연합) | 다른 네임스페이스와의 연결을 제공 |
Object Factory(객체 팩토리) | 이름으로부터 객체를 생성 |
State Factory(상태 팩토리) | 객체의 상태를 저장하고 복원 |
2. 구성과정
3.1. 의존성 주입
dependencies {
implementation 'org.apache.commons:commons-dbcp2'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
runtimeOnly 'org.postgresql:postgresql'
}
3.2. application.properties
💡 기존의 JDBC 연결 접속정보를 주석을 하고 datasource.jndi-name로 이름을 jndi/postgres로 지정하였습니다.
spring:
# PostgreSQL DB 설정
datasource:
# driver-class-name: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/postgres
# username: xxx
# password: xxx
jndi-name: jndi/postgres
3.3. JndiConfig.java
💡 JndiConfig.java
- Spirng Boot 환경에서 JNDI를 설정하기 위한 클래스입니다.
1. tomcatFactory() : Tomcat 서버 설정을 위한 메서드입니다.
1.1. getTomcatWebServer
- tomcat.enableNaming() 코드를 통해 Tomcat의 JNDI 기능을 활성화하고, 활성화된 Tomcat 객체를 반환합니다.
1.2. postProcessContext(Context context)
- Tomcat conetxt가 처리된 후에 호출되는 메서드로 getResource()를 호출하여 JNDI 리소스를 추가하는 기능을 수행합니다.
[참고] org.apache.commons.dbcp2.BasicDataSourceFactory
- JDBC 연결과 동시에 DBCP의 종류인 ‘Apache Commons DBCP’와의 연결도 함께 구성하였습니다.
https://commons.apache.org/proper/commons-dbcp/
package com.adjh.multiflexapi.config;
import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Spirng Boot 환경에서 JNDI를 설정 클래스
*
* @author : lee
* @fileName : JndiConfig
* @since : 2/20/24
*/
@Configuration
public class JndiConfig {
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
@Override
protected void postProcessContext(Context context) {
context.getNamingResources().addResource(getResource());
}
};
}
public ContextResource getResource() {
System.out.println("[+] JDBC를 리소스로 등록합니다");
ContextResource resource = new ContextResource();
resource.setName("jndi/postgres"); // JNDI 리소스의 이름을 지정합니다.
resource.setType("javax.sql.DataSource");
resource.setProperty("factory", "org.apache.commons.dbcp2.BasicDataSourceFactory");
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://localhost:5432/postgres");
resource.setProperty("username", "localmaster");
resource.setProperty("password", "1234");
return resource;
}
}
2.4. 결과확인
💡 결과확인
- 서버가 실행되면서 JDBC 연결이 완료됨을 확인하였습니다.
- 또한 실제 데이터베이스로 호출을 하였을 시 DB 호출 역시 잘됨을 확인하였습니다.
4) JDBC, DBCP 비교
💡 JDBC, DBCP 비교
항목 | JDBC | DBCP |
정의 | 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API | 애플리케이션이 시작 될때 미리 여러 개의 데이터베이스 커넥션(Connection)을 커넥션 풀(Connection Pool)에 생성해 놓고 필요할 때마다 사용 후 반환하여 이를 재사용할 수 있게 해줍니다. |
성능 | 매번 새로운 연결(Connection)을 생성하므로 비효율적 | 이미 생성된 연결(Connection)을 재사용하므로 효율적 |
자원 관리 | 연결, 쿼리, 결과처리 등 모든 과정을 개발자가 직접 관리 | 커넥션 풀(Connection Pool)을 통해 자원을 효율적으로 관리 |
편의성 | 복잡한 코드 작성이 필요 | 간단한 설정으로 사용 가능 |
오늘도 감사합니다😀
반응형