Java의 DB 접근 기본 구성 정리 및 Mybatis 적용 방법

2025. 1. 19. 00:27·개발

Java의 DB 접근 기본 플로우 

Java의 데이터베이스 접근 기술은 일반적으로 다음과 같은 플로우를 갖게 됨.

데이터 소스 설정 -> 커넥션 가져오기 -> 실제 SQL 작업 수행 -> 에러가 발생했다면 에러 처리 -> 커넥션 종료

 

데이터 소스 설정

  • 데이터 소스란 데이터 베이스와의 연결을 관리하는 역할을 함.
  • 데이터베이스 연결 설정: URL/ID/PW/Driver 등 정보를 관리해 실제 디비 접근이 해당 정보를 활용
  • 커넥션 풀 관리: DB 연결시 일정 수의 커넥션 풀을 미리 만들어 놓고, 이 연결을 재사용하는 등을 통해 성능 향상
  • 스프링부트의 기본 데이터 소스인 HikariCP를 일반적으로 사용하며 톰캣이나 JDBC CP도 따로 존재함.

 

커넥션 가져오기

  • 데이터 소스가 실제 DB 연결을 하고 나서 커넥션 풀을 형성하면, 실제 로직에서는 그 커넥션 풀에서 커넥션 하나를 가져와서 작업을 해야 함.
  • 여기서 만약 트랜잭션을 사용한다면, 아무 커넥션을 하나 가져오는 것이 아닌 해당 스레드에서 기존에 사용하던 커넥션을 보관중인 트랜잭션 동기화 매니저에게서 가져와야 함.

 

실제 SQL 작업 수행

 

  • 데이터베이스에서 SQL 쿼리를 실행하여 데이터를 조회하거나 변경하며, 이는 각 데이터베이스 접근 기술에 따라 방식이 다름
  • JDBC: Statement, PreparedStatement 등을 사용하여 SQL 쿼리를 실행
  • MyBatis:매핑된 쿼리를 실행
  • JPA: EntityManager를 사용하여 엔티티 객체를 저장하거나 조회하는 방식으로 SQL 쿼리를 추상화하여 처리

 

에러 처리

  • DB 작업을 수행하다가 발생한 에러에 대한 처리를 해줘야 하며, 일반적으로 DB 작업시 발생한 에러에 대해서는 딱히 처리해줄만한 작업이 없는게 일반적
  • 예를 들어, DB Connection이 되지 않거나 SQL Query 문법 오류가 나는 경우 이에 대해서는 개발자가 Catch 해서 처리해줄 게 없고 에러가 나는게 정상
  • 만약에 DuplicateKeyException 같은게 발생한다면, Key를 변경해서 쿼리를 다시 전송하는 등의 처리가 가능하긴 함.
  • DuplicateKeyExcpetion 과 같은 Exception을 타입을 알아내려면 error status code를 알아야하는데, 이는 DB마다 모두 다르기 때문에 SQLExceptionTranslator를 사용해 줘야 함.
  • JDBC의 경우에는 SQL 수행시 문제 발생하면 SQLException이 발생하는데 이는 체크 예외이이기 때문에 예외 처리 부담때문에 런타임 예외로 변경해주는게 좋음.

 

커넥션 종료

  • 초기에 가져온 커넥션을 종료하며, 커넥션 풀을 사용중이라면 실제로 커넥션이 종료되는 것은 아니고 커넥션 풀이 반환되어 재활용 됨.
  • 만약에 트랜잭션을 사용중이라면 뒤에 SQL 작업이 더 남았다면 추후 커넥션이 계속 활용되야 하기 때문에 반환되지 않음.
  • 트랜잭션을 사용중일 때는 커밋/롤백 작업 이후에만 커넥션이 반환됨.
  • 작업이 성공하든 실패하든 무조건 close가 불려야 하므로 finally로 부분에 close를 실행함.

 

Mybatis의 DB 접근 기본 구성

Mybatis도 기본 구성 플로우를 따라 가는데, 어떤식으로 사용되는지 확인해보자.

 

데이터 소스 설정(Legacy Spring, Spinrg Boot X)

1. mybatis-config.xml 파일 생성

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            
            <!-- HikariCP 데이터 소스 설정 -->
            <dataSource type="POOLED">
                <property name="dataSourceClassName" value="com.zaxxer.hikari.HikariDataSource"/>
                
                <!-- HikariCP 설정 -->
                <property name="dataSource.user" value="root"/>
                <property name="dataSource.password" value="password"/>
                <property name="dataSource.jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
                <property name="dataSource.driverClassName" value="com.mysql.cj.jdbc.Driver"/>

                <!-- HikariCP 특성 설정 -->
                <property name="maximumPoolSize" value="10"/>  <!-- 최대 커넥션 풀 크기 -->
                <property name="minimumIdle" value="5"/>       <!-- 최소 유휴 커넥션 수 -->
                <property name="connectionTimeout" value="30000"/>  <!-- 커넥션 타임아웃 (밀리초) -->
                <property name="idleTimeout" value="600000"/>    <!-- 유휴 커넥션 타임아웃 (밀리초) -->
                <property name="maxLifetime" value="1800000"/>  <!-- 커넥션의 최대 생명 시간 (밀리초) -->
                <property name="connectionTestQuery" value="SELECT 1"/>  <!-- 커넥션 테스트 쿼리 -->
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>

</configuration>
  • 일반적으로 데이터 소스 설정을 위한 파일을 mybatis-config.xml에 둠(java 파일로 생성도 가능)
  • 어떤 커넥션 풀 기술을 사용할지(HiarkiCP)에 대한 정보, 커넥션 풀 사이즈 등에 대한 정보 세팅
  • database 연결 정보 설정
  • mybatis는 실제 SQL 작업 수행시 mapper를 통해 수행하므로 mapper 경로도 지정해줌.

2. applicationContext.xml(예명) 파일 생성

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
    <property name="maximumPoolSize" value="10"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!-- mybatis-config.xml 위치 지정 -->
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory"/>
</bean>
  • 우리는 데이터 소스를 빈으로 등록해서, 필요할 때 마다 가져다가 사용할 예정
  • Mybatis에서 데이터 소스는 SqlSessionFactoryBean에서 관리됨.
  • xml 파일에 datasource 정보를 넣어주고, sqlSessionFactory에 datasource와 아까 생성한 mybatis-config.xml을 연결해줌.
  • 이러면, SqlSessionFactoryBean은 커넥션(세션)을 찍어서 공급해주는 공장과 같은 역할을 하며, 우리는 이 커넥션이 필요할 때 마다 이 bean에게서 받아가면 됨.
  • sqlSessionTemplate은 추후 우리가 mybatis 코드를 복잡한 형태에서 간편한 형태로 제공해주는 template을 사용할 수 있게 해줌.

 

커넥션 가져오기

@Service
public class UserService {

    private final SqlSessionFactory sqlSessionFactory;

    @Autowired
    public UserService(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

public User getUserById(int id) {
    // SqlSession을 직접 가져오기
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    try {
        // 트랜잭션을 시작하면서, SqlSession을 사용해 Mapper 호출
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        ...
  • 우리가 Bean으로 등록한 sqlSessionFactory를 주입 받음.
  • SQL 작업을 수행하기 위한 세션을 획득하기 위해 SqlSession sqlSession = sqlSessionFactory.openSession() 호출
  • openSession()을 호출하면, MyBatis는 커넥션 풀에서 커넥션을 하나 할당받아 SqlSession에 연결

 

실제 SQL 작업 수행

 

Mapper

  • MyBatis에서 Mapper는 SQL 쿼리와 Java 객체 간의 매핑을 담당하는 중요한 역할을 함
  • MyBatis의 핵심 개념 중 하나는 SQL을 Java 코드와 분리하여 XML이나 애노테이션을 통해 SQL을 관리하는 방식
  • SQL이 분리되어 유지보수가 용이하고, SQL을 직접 작성하여 더 복잡한 쿼리도 효율적으로 처리 가능

 

XML Mapper 예시 (UserMapper.xml)

<mapper namespace="com.example.mapper.UserMapper">

    <select id="getUserById" resultType="com.example.model.User">
        SELECT id, name, email
        FROM users
        WHERE id = #{id}
    </select>
    
</mapper>
  • namespace: 이 Mapper가 속한 Java 인터페이스의 위치를 지정
  • <select>: select 쿼리를 의미
  • id = "getUserById": 해당 쿼리의 ID를 의미하며 이 ID를 호출하여 계속 재사용 가능
  • resultType: 결과를 어떤 타입 혹은 클래스로 바인딩할지 정의
  • #{id}: 파라미터 바인딩

 

Mapper 인터페이스 (UserMapper.java)

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
    User getUserById(int id);
}
  • 위 파일은 com.example.mapper.UserMapper이며 이전 XML Mapper에서 정의한 namespace와 연동
  • 함수 이름 getuserById는 XML Mapper의 id="getUserById"와 연동
  • int id 는#{id}에 파라미터 바인딩 됨.
  • Select 결과는 User 객체에 담겨서 return 됨.

 

서비스 코드

@Service
public class UserService {

    private final SqlSessionFactory sqlSessionFactory;

    @Autowired
    public UserService(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

public User getUserById(int id) {
    // SqlSession을 직접 가져오기
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    try {
        // 트랜잭션을 시작하면서, SqlSession을 사용해 Mapper 호출
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        
        // DB 작업 수행
        User user = userMapper.getUserById(id);
        
        return user;
    } catch (Exception e) {
    
    ...
  • UserMapper userMapper = sqlSession.getMapper(UserMapper.class) 을 통해 UserMapper 객체 생성
  • User user = userMapper.getUserById(id) 를 통해 실제 getuserById Select 쿼리를 실행하고 결과 user 객체를 받음.

 

에러 처리

		...
            return user;

        } catch (Exception e) {
            // 예외를 감싸서 던지거나 적절한 예외 처리
            throw new DataAccessException("Error occurred while retrieving user", e) {};
        } finally {
          
          ...
  • 예외는 SQLException 과 같은 체크 에러가 발생할 수 있으므로 RuntimeError인 DataAccessException으로 감싸서 던지면 좋음
  • DataAccessException의 하위 클래스로는 JdbcSQLException, DuplicateKeyException, CannotAcquireLockException 등이 있으며, 이러한 예외들은 JDBC, MyBatis, JPA 등 다양한 데이터 액세스 기술에서 발생하는 예외들을 통합하여 관리 가능

 

커넥션 종료

        ...
        } finally {
            if (sqlSession != null) {
                sqlSession.close(); // SqlSession을 명시적으로 닫아야 함
            }
        }

 

  • 마지막에는 항상 sqlSession.close()를 통해 커넥션을 반환해 줘야 함.

 

SqlSessionTemplate 사용

기존 코드의 문제점

    SqlSession sqlSession = sqlSessionFactory.openSession();
  
    try {
    	/* 실제 SQL 작업
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(id); */
    } catch (Exception e) {
        throw new DataAccessException("Error occurred while retrieving user", e) {};
    } finally {
        if (sqlSession != null) {
        sqlSession.close(); // SqlSession을 명시적으로 닫아야 함
    }
  • 앞서 설명한 예시의 문제점은 앞으로 다른 함수들에서도 똑같은 코드가 계속 재사용 된다는 것
  • 위는 getUserById 라는 함수 하나에 대한 예시지만 insertUserById, deleteUserById나 다른 함수들이 추가 될 수 있고, 그 경우마다 앞서 말한 커넥션 가져오기 -> 실제 SQL 작업 수행 -> 에러가 발생했다면 에러 처리 -> 커넥션 종료 이 과정이 계속 반복된다는 것임
  • 하지만 그 과정중에 각 함수마다 달라지는건 실제 SQL 작업 수행 과정이고 커넥션 가져오기, 에러 처리, 커넥션 종료는 매 함수마다 같은 동작임.
  • 예를 들어, 위 코드와 같이 openSession, 에러 처리, session close는 매번 해야 하는 작업이라, 이 똑같은 코드가 매 함수마다 들어가야 함.
  • SqlSessionTemplate은 이러한 반복 코드를 탬플릿 콜백 패턴으로 처리하고 실제 SQL 작업 수행 과정만 남김

 

SqlSessionTemplate 예시

@Service
public class UserService {

    private final SqlSessionTemplate sqlSessionTemplate;

    @Autowired
    public UserService(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    @Transcantional
    public User getUserById(int id) {
        // SqlSessionTemplate을 사용해 Mapper 호출
        UserMapper userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
        
        // DB 작업 수행 (단순 조회)
        return userMapper.getUserById(id); // 예외는 DataAccessException으로 자동 변환됨
    }
}
  • SqlSessionFactory 코드의 제거
    • 기존에는 SqlSessionFactory를 선언하여 Bean을 주입하고, Factory에서 session을 가져와 사용했음.
    • 하지만 SqlSessionTemplate은 SqlSessionFactory를 이미 가지고 있는 상태
    • 앞쪽에 applicationContext.xml에 SqlSessionTemplate Bean을 생성할 때 ref로 sqlSessionFactory를 넣어 줬었음.
    • Factory에서 openSession으로 세션을 가져오는 부분은 추후 getMapper를 호출하는 시점에 Template이 Factory를 통해 세션을 가져오게 됨.
  • Catch Exception의 제거
    • SqlSessionTemplate은 예외를 자동으로 DataAccessException으로 감싸서 Throw를 해줌.
    • DataAccessException으로 감싸서 Throw를 하면 체크 예외를 언체크 예외로 변경 가능하며, 예외를 추상화 하여 다양한 DB 엑세스 기술에서 예외를 특정하여 사용할 수 있게 하는 단점이 있음.
    • 즉, 이전에 언급한 장점을 SqlSessionTemplate이 자동으로 적용해 줌.
  • finally close의 제거
    • 마지막에 finally로 무조건 session을 닫아줘야 하는데, 이 작업을 sqlSessionTemplate이 마지막에 자동으로 해줌.

'개발' 카테고리의 다른 글

프롬프트 엔지니어링 작성 방법  (1) 2025.07.12
MCP 사용기(Claude Desktop을 활용하여 Firecrawl, slack 연동)  (4) 2025.07.05
책 정리 - 함께 자라기  (1) 2025.05.18
배포 방법과 배포 전략  (0) 2025.02.16
Java의 JIT(Just-In-Time) 컴파일러  (0) 2025.02.16
'개발' 카테고리의 다른 글
  • MCP 사용기(Claude Desktop을 활용하여 Firecrawl, slack 연동)
  • 책 정리 - 함께 자라기
  • 배포 방법과 배포 전략
  • Java의 JIT(Just-In-Time) 컴파일러
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      @discriminatorvalue
      reentarantlock
      hibernate5module
      JPQL
      프록시
      자바
      조회 성능 최적화
      cglib
      단방향 맵핑
      버퍼
      @discriminatorcolumn
      스레드
      양방향 맵핑
      빈 후처리기
      jdk 동적 프록시
      김영한
      @within
      프록시 팩토리
      gesingleresult
      WAS
      log trace
      requset scope
      락
      @args
      고급
      jpq
      페치 조인
      typequery
      Target
      Thread
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Java의 DB 접근 기본 구성 정리 및 Mybatis 적용 방법
    상단으로

    티스토리툴바