Hibernate는 어노테이션 기반 설정 외에도 XML을 통한 설정을 지원합니다. 레거시 프로젝트나 설정을 코드와 분리하고 싶을 때 유용합니다. 이 글에서는 XML 기반 Hibernate 설정 방법을 알아보겠습니다.

기본 설정 (hibernate.cfg.xml)

Hibernate의 핵심 설정 파일입니다. 데이터베이스 연결 정보와 매핑 파일을 지정합니다.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <!-- 데이터베이스 연결 설정 -->
        <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
        <property name="connection.username">system</property>
        <property name="connection.password">oracle</property>

        <!-- Hibernate 설정 -->
        <property name="dialect">org.hibernate.dialect.Oracle9Dialect</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>

        <!-- 매핑 파일 -->
        <mapping resource="question.hbm.xml"/>

        <!-- 또는 어노테이션 기반 Entity 클래스 -->
        <mapping class="com.example.Employee"/>
    </session-factory>
</hibernate-configuration>

주요 프로퍼티 설명

프로퍼티 설명
hbm2ddl.auto 테이블 자동 생성 전략
dialect 데이터베이스별 SQL 방언
show_sql 실행되는 SQL 콘솔 출력
format_sql SQL 포맷팅

hbm2ddl.auto 옵션

옵션 설명 사용 환경
create 기존 테이블 삭제 후 새로 생성 테스트
create-drop 세션 종료 시 테이블 삭제 테스트
update 변경사항만 반영 개발
validate 매핑 검증만 수행 운영
none 아무것도 하지 않음 운영

운영 환경에서는 validate 또는 none을 사용하세요. update도 위험할 수 있습니다.

컬렉션 매핑

Hibernate는 <list>, <set>, <bag>, <map> 등의 컬렉션 매핑을 지원합니다.

기본 타입 컬렉션 (List of String)

<class name="com.example.Question" table="q100">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="qname"/>

    <list name="answers" table="ans100">
        <key column="qid"/>
        <index column="type"/>
        <element column="answer" type="string"/>
    </list>
</class>

객체 컬렉션 (List of Entity)

<class name="com.example.Question" table="q100">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="qname"/>

    <list name="answers">
        <key column="qid" not-null="true"/>
        <index column="type"/>
        <one-to-many class="com.example.Answer"/>
    </list>
</class>

컬렉션 타입 비교

타입 순서 보장 중복 허용 특징
<list> O O 인덱스 컬럼 필요
<set> X X 유일한 요소만
<bag> X O 인덱스 없이 순서 무관
<map> X X 키-값 쌍 저장

Key 태그 속성

<key
    column="컬럼명"
    on-delete="noaction|cascade"
    not-null="true|false"
    property-ref="속성명"
    update="true|false"
    unique="true|false"
/>

Component Mapping

별도 테이블 없이 객체를 컬럼으로 포함합니다.

<class name="com.example.Employee" table="emp177">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name"/>

    <component name="address" class="com.example.Address">
        <property name="city"/>
        <property name="country"/>
        <property name="pincode"/>
    </component>
</class>

결과 테이블 구조:

id name city country pincode
1 홍길동 Seoul Korea 12345

Component는 별도 테이블이 아닌 부모 테이블의 컬럼으로 저장됩니다.

객체 참조 매핑

Many-to-One

여러 Employee가 하나의 Address를 참조합니다.

<class name="com.example.Employee" table="emp211">
    <id name="employeeId">
        <generator class="increment"/>
    </id>
    <property name="name"/>
    <property name="email"/>

    <many-to-one name="address" unique="true" cascade="all"/>
</class>

One-to-One

Employee와 Address가 1:1 관계입니다.

<class name="com.example.Employee" table="emp212">
    <id name="employeeId">
        <generator class="increment"/>
    </id>
    <property name="name"/>
    <property name="email"/>

    <one-to-one name="address" cascade="all"/>
</class>

Cascade 옵션

옵션 설명
none 기본값, 연관 객체에 영향 없음
save-update 저장/수정 시 연관 객체도 함께
delete 삭제 시 연관 객체도 삭제
all 모든 작업 전파
all-delete-orphan all + 부모에서 제거된 자식 삭제

Lazy Loading

연관된 객체를 실제로 사용할 때까지 로딩을 지연합니다.

<list name="answers" lazy="true">
    <key column="qid"/>
    <index column="type"/>
    <one-to-many class="com.example.Answer"/>
</list>

Lazy Loading 옵션

옵션 설명
true 지연 로딩 (기본값, Hibernate 3.0+)
false 즉시 로딩
extra 컬렉션 크기 조회 시 COUNT 쿼리만 실행

Lazy Loading은 성능 최적화에 중요합니다. 하지만 세션이 닫힌 후 접근하면 LazyInitializationException이 발생합니다.

캐시 (Cache)

Hibernate는 두 가지 레벨의 캐시를 제공합니다.

First Level Cache (1차 캐시)

  • 범위: Session 객체 단위
  • 기본값: 항상 활성화
  • 특징: 같은 세션 내에서 동일 엔티티 재조회 시 DB 접근 없음
Session session = sessionFactory.openSession();
// 첫 번째 조회 - DB에서 가져옴
Employee emp1 = session.get(Employee.class, 1);
// 두 번째 조회 - 1차 캐시에서 가져옴 (DB 접근 없음)
Employee emp2 = session.get(Employee.class, 1);
session.close();

Second Level Cache (2차 캐시)

  • 범위: SessionFactory 단위 (전체 애플리케이션)
  • 기본값: 비활성화
  • 특징: 여러 세션에서 공유, 별도 캐시 구현체 필요

설정 방법:

<!-- hibernate.cfg.xml -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">
    org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>

엔티티에 캐시 적용:

<class name="com.example.Employee" table="emp">
    <cache usage="read-write"/>
    <!-- ... -->
</class>

캐시 전략

전략 설명 사용 시점
read-only 읽기 전용, 수정 불가 코드 테이블 등 변경 없는 데이터
read-write 읽기/쓰기 가능 일반적인 경우
nonstrict-read-write 약한 일관성 보장 동시 수정이 거의 없는 경우
transactional JTA 트랜잭션과 연동 분산 환경

트랜잭션 관리

데이터 일관성을 위해 트랜잭션을 적절히 관리해야 합니다.

기본 트랜잭션 패턴

Session session = null;
Transaction tx = null;

try {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();

    // 비즈니스 로직 수행
    Employee emp = new Employee();
    emp.setName("홍길동");
    session.save(emp);

    tx.commit();
} catch (Exception ex) {
    if (tx != null) {
        tx.rollback();
    }
    ex.printStackTrace();
} finally {
    if (session != null) {
        session.close();
    }
}

Spring에서의 트랜잭션 관리

Spring을 사용하면 @Transactional 어노테이션으로 간편하게 관리할 수 있습니다.

@Service
@Transactional
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    public void createEmployee(Employee emp) {
        employeeRepository.save(emp);
        // 예외 발생 시 자동 롤백
    }

    @Transactional(readOnly = true)
    public Employee getEmployee(Long id) {
        return employeeRepository.findById(id).orElse(null);
    }
}

트랜잭션 전파 옵션

옵션 설명
REQUIRED 기존 트랜잭션 있으면 참여, 없으면 새로 생성 (기본값)
REQUIRES_NEW 항상 새 트랜잭션 생성
SUPPORTS 트랜잭션 있으면 참여, 없으면 없이 실행
NOT_SUPPORTED 트랜잭션 없이 실행, 기존 있으면 보류
MANDATORY 반드시 기존 트랜잭션 필요
NEVER 트랜잭션 있으면 예외 발생
NESTED 중첩 트랜잭션 생성

어노테이션 vs XML 비교

특징 어노테이션 XML
가독성 코드와 함께 있어 이해 쉬움 설정이 분리되어 코드 간결
수정 시 재컴파일 필요 재컴파일 불필요
IDE 지원 자동완성, 검증 우수 상대적으로 약함
유지보수 일반적으로 선호됨 레거시 시스템에서 사용

현재는 어노테이션 기반 설정이 주류입니다. 새 프로젝트에서는 어노테이션을 사용하세요. XML은 레거시 시스템 유지보수나 설정을 코드와 분리해야 할 때 고려하세요.

결론

Hibernate XML 설정의 핵심을 정리하면:

  • hibernate.cfg.xml: 데이터베이스 연결과 Hibernate 동작 설정
  • 컬렉션 매핑: list, set, bag, map으로 다양한 컬렉션 표현
  • 객체 참조: many-to-one, one-to-one으로 관계 정의
  • Lazy Loading: 성능 최적화를 위한 지연 로딩
  • 캐시: 1차/2차 캐시로 DB 부하 감소
  • 트랜잭션: 데이터 일관성을 위한 트랜잭션 관리

XML 설정을 이해하면 레거시 프로젝트 유지보수에 도움이 되며, Hibernate의 동작 원리를 더 깊이 이해할 수 있습니다.