MySQL 서버는 사람의 머리 역할을 하는 MySQL 엔진과 손발 역할을 하는 스토리지 엔진으로 구분할 수 있다.

 

MySQL 엔진클라이언트로부터의 접속 및 요청을 처리하는 커넥션 핸들러와 SQL 파서, 전처리기, 쿼리의 최적화된 실행을 위한 옵티마이저가 중심을 이룬다. (SQL 문장을 분석하거나 최적화하는 등 두뇌 처리)

 

 

스토리지 엔진은 실제 데이터를 디스크 스토리지에 저장하거나 읽어오는 등 데이터를 읽고 쓰는 역할을 하며 MySQL 엔진의 쿼리 실행기에서 스토리지 엔진에 읽기 또는 쓰기 요청을 하는 것을 핸들러 요청이라 한다. (MySQL 엔진이 스토리지 엔진에게 데이터를 읽어오거나 저장하도록 명령하려면 반드시 핸들러를 통해야 한다)

 

MySQL 엔진은 하나, 스토리지 엔진은 여러 개를 동시에 사용할 수 있다.

 

MySQL의 메모리 공간은 글로벌 메모리와 로컬 메모리 영역으로 나뉘는데 글로벌 메모리 영역MySQL 서버가 시작되면서 운영체제로부터 할당되며 클라이언트 스레드의 수와 무관하게 보통 하나(2개 이상도 되긴 함)의 메모리 공간으로 모든 스레드에 의해 공유된다.

로컬(세션) 메모리 영역은 클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역으로 클라이언트 스레드별로 독립적으로 할당되며 절대 공유되지 않는다. 중요한 특징은 각 쿼리의 용도별로 필요할 때만 공간이 할당되고 필요하지 않은 경우에는 MySQL이 메모리 공간을 할당조차도 하지 않을 수도 있다.

 

거의 대부분의 작업이 MySQL 엔진에서 처리되고, 마지막 데이터 읽기, 쓰기 작업만 스토리지 엔진에 의해 처리(group by, order by등의 복잡한 처리는 MySQL 엔진의 쿼리 실행기에서 처리)가 되는데 하나의 쿼리 작업에 있는 하위 작업들이 각각 어느 영역에서 처리되는지 구분할 줄 아는 것이 중요하다.

 

쿼리 파서는 사용자 요청으로 들어온 쿼리 문장을 MySQL이 인식할 수 있는 토큰 단위로 분리해서 트리 형태의 구조로 만들어낸다.

(이 과정에서 문법 오류가 발견된다.)

 

전처리기는 테이블 이름이나 칼럼 이름 또는 내장 함수와 같은 개체를 매핑해 해당 객체의 존재 여부와 접근 권한 등을 확인한다

 

옵티마이저 쿼리 문장을 저렴한 비용으로 가장 빠르게 처리할지를 결정하는 중요한 역할을 하며 DBMS의 두뇌에 해당한다. 어떻게 하면 옵티마이저가 더 나은 선택을 할 수 있도록 유도하는지 아는 것이 중요하다.

 

쿼리 캐시는 SQL의 실행 결과를 메모리에 캐시하는 것으로 매우 빠른 성능을 보이지만 테이블의 데이터가 변경되면 캐시에 저장된 결과 중에서 변경된 테이블과 관련된 모든 것을 삭제해야 한다. 이는 동시 처리 성능 저하와 많은 버그의 원인이 되기 때문에 MySQL 8.0으로 올라오면서 제거되었다.

 

데이터베이스 서버에서 테이블의 구조 정보와 스토어드 프로그램 등의 정보를 데이터 딕셔너리 또는 메타데이터라고 하는데 MySQL 8.0부터는 메타데이터와 MySQL 서버가 작동하는데 기본적으로 필요한 시스템 테이블 등을 InnoDB의 테이블에 저장해서 트랜잭션을 지원하도록 했다. (이전에는 메타데이터를 파일 기반으로 관리해서 MySQL 서버가 비정상으로 종료시 깨지는 문제 발생)

 

스프링에서 Mybatis를 사용하기 위해서는 Mybatis 스프링 연동 모듈이 필요한데 이는 마이바티스와 스프링을 편하고 간단하게 사용할 수 있도록 지원해준다.

 

SqlSessionFactory

 

데이터 베이스의 연결과 SQL 실행에 대한 모든 것을 가지고 있으며 DataSource를 필요로 한다.

@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}

스프링은 SqlSessionFactoryBean에서 getObject() 메서드를 호출하여 반환된 SqlSessionFactory를 빌드하여 sqlSessionFactory 라는 이름으로 저장한다. SqlSessionFactoryBean이나 SqlSessionFactory를 직접 사용할 일은 없고 MapperFactoryBean를 확장하는 다른 DAO에 주입 될 것이다.

 

 

 

MapperFactoryBean

 

MapperFactoryBean SqlSession을 생성하고 닫는 역할을 하며 실행중인 스프링 트랜잭션을 관리하고 예외 발생시 스프링의 DataAccessException예외로 변환해준다. 

@Configuration
public class MyBatisConfig {
  @Bean
  public UserMapper userMapper() throws Exception {
    SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
    return sqlSessionTemplate.getMapper(UserMapper.class);
  }
}

 

SqlSesssion과 SqlSessionTemplate

SqlSession SqlSessionFactory에 의해 생성되며 매핑 구문을 실행하거나 트랜잭션 커밋, 롤백 등을 할 수 있는데 마이바티스 스프링 연동 모듈은 자동으로 SqlSession을 스프링 빈에 주입하고 스프링이 직접 트랜잭션을 관리한다. 

스프링이 관리하는 SqlSession에서는 SqlSession.commit(), SqlSession.rollback() 또는 SqlSession.close() 메서드를 호출할수가 없다. (사용 시 UnsupportedOperationException 예외가 발생)

 

SqlSessionTemplate은 인자로 SqlSessionFactory를 받아서 생성되는데 SqlSession이 현재의 스프링 트랜잭션에서 사용 될 수 있도록 보장하는데 필요한 시점에 세션을 닫고, 커밋하거나 롤백하는 것을 포함한 세션의 생명주기를 관리한다. 또한 마이바티스 예외를 스프링의 DataAccessException로 변환하는 작업도 처리한다.

 

TransactionManager

 

마이바티스 스프링 연동 모듈은 스프링 PlatformTransactionManager의 구현체인 DataSourceTransactionManager를 사용하여 스프링 트랜잭션에 자연스럽게 연동될 수 있는데 DataSourceTransactionManager는 Connection 객체에 존재하는 트랜잭션 API를 사용하여 트랜잭션을 관리해주는 TransactionManager다.

 

참고)

JPA는 JpaTransactionManager를 사용하며 EntityManagerFactory가 스프링 빈으로 등록된 DataSource를 사용하는데 JPA에서 사용하는 DataSource를 Mybatis에서도 사용할 수 있다. Mybatis만 사용할 경우 DataSourceTransactionManager를 스프링 빈으로 등록하지만, JPA를 사용할 경우 JPATransactionManager로 같이 관리가 가능하기 때문에 별도의 TransactionManager를 등록할 필요가 없다.

 

Spring boot :: JPA, Mybatis Transaction Manager 정리

Introduction 이번에는 Mybatis 와 JPA 를 동시에 적용한 환경에서 어떤 transactionManager 를 사용해야되는지 살펴보려고 한다. 해당 글은 스프링에서 트랜잭션을 담당하는 핵심 인터페이스인 PlatformTransact

wave1994.tistory.com

 

 

Mapper Interface

Mybatis XML 파일에 SQL을 호출하기 위한 인터페이스로  편리하게 XML의 데이터를 찾아서 호출해주고 MyBatis에서 발생한 예외를 스프링 예외 추상화인 DataAccessException에 맞게 변환해주는 등 지금까지 말한 기능들을 담당한다.

 

Mybatis 3.0 이후 버전부터는 SqlSessionTemplate를 직접적으로 사용하는 DAO를 생성하지 않고 다른 빈에 직접 주입할 수 있는 쓰레드에 안전한 매퍼를 생성하여 사용한다. 리포지토리에 Mapper 인터페이스를 주입하여 사용하는 것을 생각하면 된다. 여러 설정들이 간편하게 자동화되며 스프링 부트를 사용하면 Mapper 인터페이스를 Bean으로 등록할 필요도 없이 @Mapper 애노테이션만 사용하면 된다.

 

 

Mybatis-Spring-Boot-Starter

  • DataSource 자동 감지
  • SqlSessionFactoryBean을 사용해서 DataSource를 전달하는 SqlSessionFactory의 객체를 생성하고 등록
  • SqlSessionFactory를 이용해 SqlSessionTemplate를 생성하고 등록
  • Mapper를 스캔한 뒤 SqlSessionTemplate에 연결하고 Mapper 인터페이스를 프록시 구현체로 생성하여 스프링 빈으로 등록해서 리포지토리에 주입할 수 있게 해준다.

 

 

Mybatis-Spring-Boot-Starter 소개 문서 번역

마이바티스-스프링 부트-스타터 문서 번역

kgmyh.github.io

 

 

mybatis-spring –

소개 MyBatis-Spring 은 무엇일까? 마이바티스 스프링 연동모듈은 마이바티스와 스프링을 편하고 간단하게 연동한다. 이 모듈은 마이바티스로 하여금 스프링 트랜잭션에 쉽게 연동되도록 처리한다.

mybatis.org

 

[Mybatis]란? / SqlSessionTemplate, MapperInterface 개념/빈 등록 설정

[MyBatis]란 무엇인가 에 대해서 또 SqlSessionTemplate/MapperInterface 두가지 방식에 따른 전체적인 구조 차이와 Mapper.xml 속성 차이에 대해 정리합니다. [MyBatis]란? 앞서 사용해 보았던 Jdbc-Template도..

u-it.tistory.com

 

MyBatis는 SQL을 XML에 편리하게 작성할 수 있고 동적 쿼리를 편리하게 작성할 수 있는 SQL Mapper 기술로 공식 사이트에 사용법이 잘 나와있다.

 

 

application.properties

#MyBatis
# 타입 정보를 사용할 때 패키지 경로 생략 가능, 지정한 패키지와 그 하위 패키지가 자동으로 인식
mybatis.type-aliases-package=hello.itemservice.domain
# 언더바 -> 카멜 자동 변경
mybatis.configuration.map-underscore-to-camel-case=true
# MyBatis 실행 쿼리 로그 확인
logging.level.hello.itemservice.repository.mybatis=trace
# resources/mapper 를 포함한 하위 폴터 XML을 XML 매핑 파일로 인식
mybatis.mapper-locations=classpath:mapper/**/*.xml

 

@Mapper

스프링 부트가 애플리케이션 로딩 시점에 @Mapper가 붙은 인터페이스를 동적 프록시로 구현체를 생성하고 스프링 빈으로 등록하기 때문에 인터페이스 만으로 XML의 데이터를 찾아서 호출할 수 있고 매퍼 구현체는 MyBatis에서 발생한 예외를 스프링 예외 추상화인 DataAccessException으로 변환해준다.

@Mapper
public interface ItemMapper {

    void save(Item item);

    // 파라미터가 2개 이상이면 @Param("name")으로 매핑
    void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto
updateParam);

    Optional<Item> findById(Long id);

    List<Item> findAll(ItemSearchCond itemSearch);
}

 

XML

XML 파일 경로는 resources 하위에 mapper 와 같은 패키지 위치로 맞추어 생성한다. 

<insert id="save" useGeneratedKeys="true" keyProperty="id">
    insert into item (item_name, price, quantity)
    values (#{itemName}, #{price}, #{quantity})
</insert>
  • id는 인터페이스에 설정한 메서드 명이랑 맞추고 파라미터는 #{} 문법을 사용하면 된다.
  • 파라미터가 2개 이상일 경우는 Mapper 인터페이스에 @Param으로 이름을 지정해서 파라미터를 구분해야 한다.
  • resultType은 반환 타입으로 결과를 해당 타입의 객체에 매핑해준다.
  • 반환 객체가 여러개면 컬렉션을 사용하면 되는데 주로 List를 사용한다.

 

<select id="findAll" resultType="Item">
    select id, item_name, price, quantity
    from item
    <where>
        <if test="itemName != null and itemName != ''">
            and item_name like concat('%',#{itemName},'%')
        </if>
        <if test="maxPrice != null">
            and price &lt;= #{maxPrice}
        </if>
    </where>
</select>
  • 동적 쿼리 문법인 <where>은 내부 동적 쿼리가 생성이 되면 맨 앞에 and를 where로 변환하고 생성이 되지 않을 경우는 where를 만들지 않는다.
  • XML에는 HTML 엔티티를 사용할 수 없는데 CDATA 문법을 사용하면 태그를 단순 문자로 인식하도록 하지만 <if>, <where>같은 동적 쿼리 문법도 적용되지 않기 때문에 잘 써야 한다.  (<![CDATA[ ... ]]>)

 

동적 SQL

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

 

문자열 치환

#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement 를 사용하는데 파라미터 바인딩이 아닌 문자 그대로를 처리하는 경우 ${} 를 사용하면 되는데 SQL Injection 공격을 당할 수 있기 때문에 사용하지 않는 것이 좋다.

 

SQL Fragment

<sql>는 fragment 기능으로 사용할 수 있는데 <include>를 통해 반복 코드를 편리하게 불러올 수 있다.

 

resultMap, 별칭(alias)

컬럼명과 객체의 프로퍼티 명이 다를 경우 별칭 "as"를 사용하거나 resultMap으로 선언해서 사용하면 된다.

 

 

 

* 문법은 필요할 때 찾아서 직접 사용해보는 것이 좋다.

 

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

 

[Java] 자바 예외 (Exception)

자바 예외는 최상위 예외 계층인 Throwable, 그 하위로 Exception과 Error가 있는데 Error는 메모리 부족 같이 애플리케이션에서 복구 불가능한 시스템 예외인데 catch로 예외를 잡으면 하위 예외(Error)까

treecode.tistory.com

 

[DB 접근 기술1] 스프링 트랜잭션

일반적인 웹 애플리케이션 구조는 다음과 같다. 프레젠테이션 계층 (@Controller) 서비스 계층 (@Service) 데이터 접근 계층 (@Repository) 여기서 가장 중요한 곳은 핵심 비즈니스 로직이 있는 서비스 계

treecode.tistory.com

 

스프링 트랜잭션 AOP를 사용하면 트랜잭션 로직과 비즈니스 로직을 깔끔하게 분리할 수 있는데 예외 처리에 대한 의존까지는 해결할 수 없다. 리포지토리에서 서비스가 처리할 수 없는 체크 예외를 던지면 서비스 계층에 불필요한 예외 의존 관계가 생기는데 체크 예외를 런타임 예외로 전환해서 던지면 서비스 계층을 순수하게 유지할 수 있다. (체크 예외의 경우 인터페이스에서도 throws를 선언해야 한다.)

 

try {
    ...
} catch (SQLException e) {
    // RuntimeException을 상속 받은 예외 생성
    // 예외 e를 파라미터로 보내줘야 나중에 예외 출력 시 기존 예외를 확인할 수 있다.
    throw new MyException(e);
} finally {
    ...
}

 

데이터베이스에서 발생한 예외중 특정 상황에는 예외를 잡아서 복구하고 싶은 경우 예외의 ErrorCode를 확인해서 새로운 예외로 변환하면 되는데 데이터베이스마다 똑같은 오류라도 ErrorCode가 다 다르기 때문에 직접 ErrorCode를 확인해서 처리하기는 어렵다.

 

스프링은 데이터 접근과 관련된 예외를 추상화해서 제공하는데 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있고 심지어 스프링이 제공하는 예외로 변환하는 예외 변환기가 ErrorCode를 확인해서 적절한 예외로 변환까지 해준다.

 

스프링이 제공하는 데이터 접근 계층 예외는 RuntimeException을 상속 받은 DataAccessException로 크게 두가지로 나뉜다.

  • Transient : 일시적인 오류로 동일한 SQL로 다시 시도하면 성공할 가능성이 있는 경우 (쿼리 타임아웃, 락 관련 오류 등)
  • NonTransient : SQL 문법 오류, 데이터베이스 제약 조건 위배 등 일시적이지 않은 오류

 

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

+ Recent posts