[SpringBoot 전환기] - Mybatis: 2

SUN·2023년 3월 12일

SpringBoot Migration

목록 보기
2/2

오늘은 스프링에서 스프링부트로 전환하면서 마이바티스에 대해 알게된 내용에 대해 공유하려고 한다.


마이바티스란?

  • 자바 오브젝트와 SQL사이의 자동 매핑 기능을 지원하는 ORM(Object relational Mapping) 프레임워크

설정

DB 정보 설정

  • yaml이나 properties 파일에서 아래 설정들을 해줘야 한다.
  • 원래 프로젝트에선 properties를 썼는데, yaml이 가독성이 높기 때문에 옮기면서 yaml로 변경했다.
spring:
  datasource:
    url: jdbc:${url}:${port}/${db_name}
    driver-class-name: org.postgresql.Driver
    username: ${id}
    password: ${password}
    
mybatis:
  // 실제 sql문이 저장된 xml파일들 위치이다.
  // 나의 경우는 리소스 폴더에 매퍼 디렉토리안에 각 도메인별로 디렉토리를 만들고 
  // 그 안에 그 도메인에해당하는 sql 파일들을 ~~~_SQL.xml형식으로 등록해서 아래처럼 등록해줬다.
  // 각자 위치에 맞춰서 설정해주면 된다. 
  mapper-locations: classpath:mapper/**/*_SQL.xml

빈 등록

사실 스프링부트에서 mybatis를 사용하려면 마이바티스 스타터 라이브러리를 추가해줘야 되는데,
이걸 사용하면 자동으로 세션 팩토리랑 세션 템플릿 등의 빈을 만들어주기 때문에 빈을 꼭 등록해줄 필요는 없다.
기본설정을 변경하고 싶은 경우에 참고하면 된다. 그 전에 세션 팩토리, 세션 템플릿이 뭔지 간단하게 알아보자
세션 팩토리와 템플릿은 mybatis에서 데이터베이터와 연결 관련 작업을 처리하기 위해 사용된다.

세션 팩토리란?

데이터베이스와의 연결을 관리한다. 디비 연결 정보를 설정하고 마이바티스와 디비 간의 연결을 생성, 관리한다.

역할

  1. 데이터베이스 연결 정보 설정
  2. 마이바티스와 데이터베이스 간 연결 관리
  • 애플리케이션 전체에서 공유되고 통신에 대한 최적화 및 캐싱 기능 제공

세션템플릿이란?

  • mybatis에서 제공하는 api로 디비와 상호작용을 하면서 실제로 sql 쿼리를 실행하고 결과를 처리하는 역할을 한다.
    즉, mybatis에서 제공하는 다양한 메서드로 디비와 연결을 열고, sql쿼리를 실행하고, 결과를 처리한다.
@Configuration
//해당 위치에 있는 @Mapper가 붙은 인터페이스를 스캔하고 실제 SQL문과 연결되게 해줌
@MapperScan(basePackages = {"com.example.mapper"})
public class MybatisConfig {
	// 데이터소스 빈은 스프링부트에서 자동으로 만들어줌
    private final DataSource dataSource;

    public MybatisConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }
	
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean
    public SqlSessionTemplate postgreSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public SqlSessionFactory postgreSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        return sessionFactory.getObject();
    }
}

기본적으로는 이런식으로 만들어주면 된다.
추가할 설정이 있으면 그에 맞게 커스텀해주면 된다.
그리고 @MapperScan이거 할 때 주의점이 있다. 사실 이걸 공유하고 싶어서 포스팅을 했다.

기존 프로젝트에서는 매퍼 인터페이스가 여러군데에 산재해있어서
처음에 나는 basePakages를 루트 디렉토리로 설정했다. 그랬더니 이상한 데서 빈 중복에러가 났다.

구체적으로는 어떤 서비스 인터페이스를 만들고 그를 구현한 구현체를 만들고
그 위에 @Service어노테이션으로 자동 빈 등록을 해줬다.
그러면 당연히 구현체만 빈으로 등록되고 인터페이스는 등록되지 않는 게 정상이다.
하지만 내가 마주친 에러는 인터페이스와 구현객체가 둘다 빈으로 등록되어서 빈이 중복된다는 것이었다.

대체 왜 인터페이스가 빈으로 등록되었는가에 대한 이유를 몇시간을 고민한 것 같다.
그런데 나중에 알고보니까 매퍼스캔 문제였다.
왜냐면 위에 적힌것처럼 매퍼스캔은 매퍼 '인터페이스'를 스캔해서 실제 SQL문과 연결한다.
나는 @Mapper가 붙은 인터페이스만 해당하는 줄 알았더니 그냥 모든 인터페이스를 스캔해서 빈으로 등록해버린 것이다.

그래서 기존 패키지 구조를 버리고 mapper 디렉토리를 만들어서 매퍼 인터페이스를 다 옮기고 거기만 스캔하도록 했다.

아, 그리고 추가로 매퍼스캔이 명시적으로 설정돼있지 않으면 루트부터 자동으로 매퍼 인터페이스만 자동으로 찾아서 등록해준다. 하지만 명시적으로 @MapperScan을 해준다면 딱 그 패키지 아래에 있는 모든 인터페이스를 매퍼로 인식해서 등록한다. 그러니까 매퍼스캔을 명시적으로 넣어주지 않는다면 위와 같은 문제는 일어나지 않는다.

하지만 나는 꼭 매퍼스캔을 사용해야하는 이유가 있었어서 이렇게 해결했다.

사용법

위의 설정을 그대로 따라가면서 예시를 들어보겠다.

com.example.mapper가 매퍼스캔 시작 위치니까 그 아래 적당히 user 디렉토리를 만들고
아래 아래처럼 매퍼를 만들어준다.

@Mapper
public interface UserMapper {

    User findById(int id);

    List<User> findAll();

    void insert(User user);

    void update(User user);

    void delete(int id);

}

그리고 여기에 대응되는 실제 SQL문을 xml 파일에 작성해준다.
위치는 위에 mapper-locations에 설정한 위치에 맞춰서 생성해주면 된다.

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

    <select id="findById" parameterType="int" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="findAll" resultType="User">
        SELECT * FROM users
    </select>

    <insert id="insert" parameterType="User">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>

    <update id="update" parameterType="User">
        UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>

    <delete id="delete" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>

</mapper>

이렇게 하면 마이바티스가 xml과 매퍼 인터페이스를 연결해준다.
즉, 매퍼 인터페이스의 finById를 호출하면 xml에서 id=finById를 부분을 찾아서 실행해주고
resultType이 User니까 실행결과를 User 객체로 매핑해준다.

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(int id);

    @Select("SELECT * FROM users")
    List<User> findAll();

    @Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})")
    void insert(User user);

    @Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
    void update(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    void delete(int id);

}

참고로 xml파일 없이 이렇게 해도 된다.

profile
백엔드 개발자

0개의 댓글