SQL Mapper vs ORM vs QueryBuilder

SQL Mapper

  • Spring에서 사용하는 SQL Mapper로는 대표적으로 MyBatis가 있음

    • SQL Mapper는 XML에 SQL을 작성하고, Java 코드에서 SQL을 호출하는 방식

    • 프로젝트의 기본적인 구조가 Controller.java -> Service.java -> Mapper.java -> Mapper.xml로 이루어지는 구조

    • 아래와 같이 Mapper.xml에 실제 SQL 쿼리를 작성함

<?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="org.example.mapper.UserMapper">
  <insert id="insertUser">
      INSERT INTO users (username, email) VALUES (#{params.username}, #{params.email})
  </insert>
</mapper>
  • Mapper.java에는 Mapper.xml에 작성한 SQL 쿼리를 호출하는 메서드를 작성함

@Mapper
public interface UserMapper {
    @Insert("INSERT INTO users (username, email) VALUES (#{username}, #{email})")
    void insertUser(@Param("username") String username, @Param("email") String email);
}

MyBatis with Custom DAO

@Mapper 없이 DAO를 사용한다면 아래와 같이 사용할 수 있음

  • SQL Session Factory

@Configuration
public class MyBatisConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mappers/*.xml"));
        return sessionFactory.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • Custom DAO

@Repository
public class UserDao {
    private final SqlSession sqlSession;

    @Autowired
    public UserDao(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    public void insertUser(User user) {
        sqlSession.insert("org.example.mapper.UserMapper.insertUser", user);
    }
}
  • Mapper 방식

    • 장점

      • SQL 매핑이 인터페이스에 직접 명시되므로, SQL 쿼리와 Java 메서드 간의 관계가 명확하고, 리팩토링이나 코드 탐색이 용이함

      • Spring Framework와의 통합이 용이하여, 스프링의 의존성 주입, 트랜잭션 관리 등의 기능을 자연스럽게 활용할 수 있음

    • 단점

      • @Mapper 애너테이션을 사용할 때는 MyBatis가 제공하는 방식을 따라야 하므로, 사용자 정의 처리가 필요할 때 제약이 따름

      • SQL과 밀접하게 연결되어 있어, 데이터베이스 로직이 비즈니스 로직에 침투할 가능성이 있음

        • 👉 Repository(DAO)로 감싸는 것으로 이 단점을 해소할 수 있으나, Depth가 깊어질 수 있음

  • Custom DAO 방식

    • 장점

      • 데이터베이스 액세스 로직을 완전히 추상화할 수 있음

      • 공통의 DAO 인터페이스를 정의하고, 다양한 구현체를 통해 다른 데이터베이스 기술로 쉽게 전환가능

    • 단점

      • DAO를 사용하면 추가적인 인터페이스와 구현 클래스를 작성해야 하므로, 프로젝트의 코드 베이스가 커지고 복잡해짐

      • @Mapper 애너테이션을 사용할 때보다 개발 속도가 느릴 수 있으며, 데이터베이스 연결과 관련된 코드를 직접 관리해야 함

Mapper도 DAO인가?

Mapper는 Data Access에 대한 모든 역할을 담당하지는 않음. Mapper는 XML을 감싸는 Interface Adapter 역할을 함. 하지만, DAO가 BusinessLogic과 독립적으로 데이터 액세스에 관한 책임을 가지는 오브젝트라는 관점에서 볼 때, Mapper 또한 DAO라고 볼 수 있을 것 같음 (동일한 관점에서 볼 때 JPARepository 또한 DAO라고 볼 수 있음)

ORM

ORM 참고

QueryBuilder

  • SQL 쿼리 빌더는 원시 데이터베이스 네이티브 쿼리 언어 위에 추상화 계층을 추가 함

    • 쉽게 말해서 사용하는 프로그래밍 언어로 작성되고 네이트비 클래스 및 함수를 사용하는 라이브러리를 Qeury Builder라고 할 수 있음

  • SQL 네이티브 쿼리 언어를 함수로 호출해서 사용한다는 점에 데이터 베이스와 비교적 가깝다고 할 수 있음

    public List<Post> searchByPostIdWithComment(Long postId) {
        JPAQuery<Post> query = queryFactory.selectFrom(post)
                .where(post.id.eq(postId))
                .leftJoin(post, comment.post)
                .on(post.id.eq(comment.post.id))
                .fetchJoin();
        return query.fetch();
    }

총평

  • Mapper

    • 장점

      • 직접 SQL을 작성하므로, ORM에 비해 성능 최적화가 용이

      • SQL 쿼리에 대한 완전한 제어가 가능하며 특정 데이터 베이스에 대한 최적화 기능 활용 가능

    • 단점

      • 파라미터 명이 틀리면 쿼리 실행 중에 오류가 남

      • 문자열로 SQL을 작성하므로, 오타가 발생할 수 있음

      • 중복된 코드가 많아질 수 있음

      • ORM이나 QueryBuilder에 비해서 Depth가 깊어짐

  • ORM

    • 장점

      • 객체지향 프로그램을 자연스럽게 활용 가능

      • 기본적인 CRUD는 자동으로 생성되므로, 개발 속도가 빠름

      • 데이터 베이스 스키마와 맞지 않으면 처음 실행하는 시점에 오류 발견 가능

    • 단점

      • 복잡한 쿼리의 경우 ORM이 생성하는 SQL이 최적화되지 않을 수 있어 성능저하가 발생

      • 복잡한 쿼리 작성이 어려움

      • N + 1 문제가 발생하며 해결하기 힘듬

  • QueryBuilder

    • 장점

      • 프로그래밍 언어의 구문을 활용하여 SQL 쿼리를 구성하므로 직관적임

      • 쿼리 조각을 프로그래밍 방식으로 쉽게 재사용하고 조합할 수 있음 (Procedure 처럼 사용 가능)

      • N + 1 문제에서 자유로움 (N + 1 문제를 고의로 발생시킬 수도 있고 N + 1문제가 발생안하게 할 수도 있음)

    • 단점

      • SQL을 직접 작성하지 않기 때문에 때때로 생성된 쿼리가 예상과 다를 수 있음

      • 컴파일 타임 위빙 방식을 사용하기 때문에 세팅이 번거롭고 꼬일 수 있음

      • build/*.class 파일이 잘못생성되면 제대로 작성된 코드도 동작하지 않을 수 있음

Reference

Last updated