자바 웹 MyBatis

Dear·2025년 6월 30일

TIL

목록 보기
51/74
post-thumbnail

💙 ORM

Object Relational Mapping Framework
객체 관계 맵핑 프레임 워크


객체 지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블 간의 불일치를 해결해주는 기술

  • 객체 지향 언어 : User 클래스 -> id, name 필드
  • 데이터베이스 : users 테이블 -> id, name 컬럼

이 둘을 매핑해주는 것이 ORM의 역할이다.

ORM의 주요 기능

  • 클래스 ↔ 테이블 매핑
  • 객체 ↔ 레코드 매핑
  • SQL 없이도 데이터 조작 가능(CRUD)
  • 트랜잭션 관리, Lazy Loading, 관계 설정 등

장점

  • SQL 작성 줄어든다.
  • 객체 중심의 개발이 가능하다 -> 생산성 향상
  • 유지보수가 편리하다 (코드 수정 -> DB 반영 자동화)

단점

  • 복잡한 쿼리는 비효율적일 수 있다.
  • 내부 동작 이해 부족하면 성능 문제가 발생할 수 있다.
  • 자동화로 인해 디버깅이 어렵다.

대표적인 ORM 프레임워크

언어ORM 도구
JavaJPA(Hibernate), MyBatis(부분 ORM)
PythonSQLAlchemy, Django ORM
C#Entity Framework

💙 MyBatis

SQL을 XML 또는 어노테이션으로 분리해 관리하면서, 자바 객체와 DB의 데이터를 매핑해주는 반자동 ORM 프레임워크

Hibernate 같은 완전 자동 ORM과 달리, 직접 SQL을 작성해야 한다.
-> 쿼리 튜닝이나 복잡한 조인 처리에 유리

구성요소

  1. Mapper XML

    • SQL문이 들어있는 XML 파일
    • <select>, <insert>, <update>, <delete> 태그 사용
  2. Mapper Interface

    • Java 인터페이스 (SQL 실행용)
    • XML의 SQL과 1:1로 매핑된다.
  3. SqlSession

    • SQL을 실행하고 결과를 받아오는 객체
// Mapper XML (UserMapper.xml)
<mapper namespace="com.example.UserMapper">
  <select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>

// Mapper Interface (UserMapper.java)
public interface UserMapper {
    User getUserById(int id);
}

// 사용 예 (Service 등에서)
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.getUserById(1);

연동

Spring 3 이전

Spring 프레임워크 자체에서 MyBatis와 연동하는 기능을 제공
SqlMapClientFactoryBean, SqlMapClientTemplate 같은 스프링 전용 클래스

Spring 4부터

Spring 자체에서 더 이상 MyBatis 연동 기능을 제공 X
스프링 내부 API로 MyBatis를 다루는 지원이 제거

MyBatis에서 만든 mybatis-spring 별도 연동 모듈 사용해야한다.

💙 select

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>
항목설명
id="selectPerson"이 쿼리의 이름(Mapper 메서드명과 연결됨)
parameterType="int"넘겨받는 파라미터 타입 (int id 라는 의미)
resultType="hashmap"결과를 Java의 HashMap으로 반환 (컬럼명이 키가 됨)
#{id}파라미터 바인딩 부분 → SQL의 ?로 변환됨

#{ }

MyBatis가 내부적으로 PreparedStatement의 ?로 치환하고 해당 위치에 전달받은 값을 설정한다

String selectPerson = "SELECT * FROM PERSON WHERE ID = ?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1, id);  // id를 바인딩

${ }와 #{ }차이점

표현식설명
#{}바인딩: SQL의 ?로 변환되어 안전하게 파라미터 전달됨
${}문자열 치환: 쿼리 안에 직접 삽입됨 (주의: SQL 인젝션 위험 있음)

속성

속성명간단 설명실무 예시
useCache="true"조회 결과를 2차 캐시에 저장해 재사용함자주 조회되며 자주 바뀌지 않는 데이터 (예: 지역 목록, 카테고리)
flushCache="true"이 쿼리를 실행하면 1차 캐시(세션 캐시)를 비움트랜잭션 이후 캐시된 결과가 오래되었을 수 있으므로 강제 초기화
resultOrdered="true"계층형 데이터 정렬 보장부모-자식 조인 쿼리에서 순서 보장 필요할 때 (예: 게시글-댓글)
timeout="3"쿼리 최대 대기 시간을 설정 (초)응답 속도가 중요한 API에서 느린 쿼리 차단
fetchSize="100"한 번에 가져올 row 수를 드라이버에 힌트대용량 결과를 페이지 단위로 효율적으로 가져오고 싶을 때
statementType="CALLABLE"저장 프로시저 호출용CALL my_procedure(#{param})
resultSetType="SCROLL_INSENSITIVE"커서 앞뒤 이동 허용 (변경 감지 X)결과셋을 반복적으로 참조하거나 이동하면서 처리할 때
databaseId="mysql"DB 종류에 따라 쿼리 분기DB별 SQL 문법이 다를 경우 (예: LIMIT vs ROWNUM)
<select id="getUserById"
        parameterType="int"
        resultType="User"
        flushCache="false"
        useCache="true"
        timeout="5"
        fetchSize="100"
        statementType="PREPARED"
        resultSetType="FORWARD_ONLY">
    SELECT * FROM users WHERE id = #{id}
</select>

💙 insert, update, delete

기본 동작

  • flushCache="true" (기본값)
    변경 쿼리가 실행되면 1차 캐시를 비운다.
    -> 이 쿼리를 실행한 후에는 같은 세션 안에서 이전에 조회했던 데이터가 다시 사용되지 않도록 캐시를 강제로 비우는 것

  • useCache="false" (기본값)
    변경 쿼리는 캐시하지 않는다.
    -> insert나 update 결과를 저장해도 재사용할 일이 없어 캐싱할 필요가 없다.

select : 읽기 성능 향상을 위해 캐시를 씀
insert/update/delete : 데이터 일관성 보장을 위해 캐시를 비움, 캐싱하지 않음

속성

보통 insert, update, delete는 특별한 설정 없이도 사용 가능하다

속성명기본값설명실무 예시
flushCachetrue쿼리 실행 시 1차 캐시 자동 초기화데이터 변경 후 항상 캐시를 새로 가져오도록 보장
useCachefalse데이터 변경 쿼리는 보통 캐시하지 않음필요하면 useCache="true"로 설정 가능 (거의 안 씀)
timeout드라이버 기본값쿼리 최대 대기 시간 (초)트랜잭션 중 오래 걸리는 update 쿼리 방지
statementTypePREPAREDJDBC 쿼리 실행 방식 지정저장 프로시저 호출 시 CALLABLE 사용
databaseId-DB별 구문 분기MySQL, Oracle 각각의 update 구문 다를 경우 사용

💙 Result Maps

MyBatis에서 SQL 조회결과(ResultSet)를 Java 객체에 정확하게 매핑하기 위한 설정이다.

단순한 경우에는 resultType으로 매핑이 가능하지만,
쿼리 결과의 컬럼 이름과 Java 객체의 필드 이름이 다르거나 복잡한 경우에, 매핑 방법을 수동으로 정의할 수 있다.

<resultMap> 태그를 이용해 XML에서 설정하거나, 어노테이션 방식으로 사용한다.


<!-- XML -->
<resultMap id="userResultMap" type="com.example.User">
    <id property="id" column="user_id"/>
    <result property="username" column="user_name"/>
    <result property="email" column="email_address"/>
</resultMap>

<select id="getUserById" resultMap="userResultMap">
    SELECT user_id, user_name, email_address
    FROM users
    WHERE user_id = #{id}
</select>

<!-- 중접 객체 매핑 -->
<resultMap id="orderResultMap" type="com.example.Order">
    <id property="id" column="order_id"/>
    <result property="date" column="order_date"/>
    <association property="user" javaType="com.example.User">
        <id property="id" column="user_id"/>
        <result property="username" column="user_name"/>
    </association>
</resultMap>

주요 태그

태그설명
<id>객체의 주 키에 해당하는 필드 매핑
<result>일반 필드 매핑
<association>다른 객체(1:1 관계)와의 매핑
<collection>리스트, 컬렉션(1\:N 관계)과의 매핑
columnSQL 결과의 컬럼명
propertyJava 객체의 필드명

중첩 객체를 정확히 매핑하지 않으면 N + 1 문제나 잘못된 데이터 매핑이 발생할 수 있다.

// 의존성 추가
// Maven (pom.xml)
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>

// application.yml
// application.properties
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb
    username: root
    password: yourpassword
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/ *.xml  
  type-aliases-package: com.example.demo.model

// 도메인 클래스 (User.java)
public class User {
    private int id;
    private String name;

    // Getter, Setter
}

// Mapper 인터페이스 (UserMapper.java)
@Mapper
public interface UserMapper {
	User getUserById(int id);
}

// Mapper XML (resources/mapper/UserMapper.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.example.demo.mapper.UserMapper">
  <select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>

// 서비스 클래스 (UserService.java)
@Service
public class UserService {
	private final User Mapper userMapper;
    
    public UserService(Usermapper userMapper){
    	this.userMapper = userMapper;
    }
    
    public User getUser(int id){
    	return userMapper.getUserById(id);
    }
}
    
// 컨트롤러 (UserController.java)
@RestController
@RequestMapping("/users")
public class UserController {

	private final UserService userService;
    
    public UserController(UserService userService){
    	this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable int id){
    	return userService.getUser(id);
    }
}

🤍 회고

오늘은 ORM과 MyBatis에 대해 공부했다.
ORM은 SQL을 숨기고 객체 중심으로 처리하는 반면, MyBatis는 SQL을 직접 작성하면서도 매핑은 자동으로 해주는 반(半)자동 ORM이다. select는 캐시를 사용하고, insert, update, delete는 캐시를 비우는 기본 동작 차이도 확인했다. 아직은 공부만 해본 단계지만, 기회가 된다면 select 쿼리에서 캐시 기능을 실제로 활용해보고 싶다.

profile
친애하는 개발자

0개의 댓글