[Spring] mybatis

DANI·2023년 12월 24일

[Class] Spring-basic

목록 보기
3/8
post-thumbnail

📕 마이바티스(MyBatis) 란?

마이바티스(MyBatis)는 자바 언어를 기반으로 하는 오픈 소스 데이터베이스 퍼시스턴스 프레임워크입니다.

이는 데이터베이스와 자바 객체 간의 매핑을 처리하여 데이터베이스 연동을 간편하게 만들어줍니다. 마이바티스는 SQL 쿼리와 자바 코드 사이의 매핑(ORM)을 설정하고, 쿼리 실행 및 결과 매핑을 담당하여 개발자가 데이터베이스와의 상호작용을 편리하게 할 수 있도록 돕습니다.


📥 의존성 추가

  • implementation 'org.mybatis:mybatis:3.5.14'

    MyBatis 라이브러리의 버전 3.5.14

  • implementation 'org.mybatis:mybatis-spring:3.0.3'

    Spring 프레임워크와 MyBatis 간의 편리한 통합을 제공하는 모듈



🔍 스프링부트에서 사용


  • implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' : 빈 등록 과정을 자동화 시켜줌


💾 DBConfig.class

@Configuration
@MapperScan("mapper") // 경로설정
public class DBConfig {

    @Bean(destroyMethod = "close")
    public DataSource dataSource(){
        DataSource dataSource = new DataSource();
        dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
        dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:orcl");
        dataSource.setUsername("SPRING6");
        dataSource.setPassword("_aA123456");

        // 커넥션 풀 성정
        dataSource.setInitialSize(2);
        // 유휴 상태 커넥션 객체를 체크 여부
        dataSource.setTestWhileIdle(true);
        // 3초마다 커넥션 상태 체크
        dataSource.setTimeBetweenEvictionRunsMillis(3000);
        // 30초가 최대 유휴 시간 -> 경과시 소멸
        dataSource.setMinEvictableIdleTimeMillis(30*1000);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        return sqlSessionFactoryBean.getObject();
    }
}
  • SqlSessionFactoryBean : mybatis-spring에서 제공되는 클래스로, 스프링과 마이바이스를 연동하는데 사용되며, 이 클래스는 마이바티스의 SqlSessionFactory를 스프링 빈으로 만들어 주는 역할, 스프링 빈으로 등록되면 다양한 스프링 기능(스프링의 트랜잭션 관리등)과 통합하여 사용할 수 있다.

  • SqlSessionFactory : 마이바티스에서 제공하는 인터페이스로, 데이터베이스와의 세션을 관리하고 SQL 매핑을 처리합니다. SqlSessionFactory는 데이터베이스와의 연결을 설정하고, 내부적으로 SQL 문을 실행하기 위한 SqlSession을 생성합니다

  • DataSource : SqlSessionFactory를 생성하기 위해서는 데이터베이스 연결 정보를 제공하는 데이터 소스DataSource가 필요합니다. DataSource는 일반적으로 데이터베이스 연결 풀을 관리하거나 데이터베이스와의 연결을 설정하기 위한 정보를 제공하는 인터페이스입니다. 여기에는 데이터베이스 URL, 사용자 이름, 암호 등이 포함될 수 있습니다.

  • SqlSession : SQL쿼리를 실행 / 인터페이스임 / 구현체 없이 인터페이스 만으로 사용 가능

  • @MapperScan : MyBatis와 스프링을 함께 사용할 때, MyBatis의 매퍼 인터페이스를 스캔하여 자동으로 구현체를 생성하도록 지시하는 어노테이션입니다. 이 어노테이션을 사용하면 매퍼 인터페이스를 직접 구현체를 작성하지 않고도 MyBatis가 필요한 구현체를 생성

🔍 구현체를 마이바티스가 구현해준다는 의미가 뭘까?


구현을 마이바티스가 해준다는 것은, 애너테이션을 통해 작성한 쿼리문이 MyBatis에 의해 실행될 때, MyBatis가 해당 쿼리를 실행하기 위한 구현체를 동적으로 생성한다는 의미입니다. 이 동적으로 생성된 구현체는 실제 데이터베이스 연동 작업을 수행하며, 개발자는 이를 마치 자바 인터페이스를 호출하는 것처럼 사용할 수 있습니다.

즉, MyBatis가 애너테이션으로 작성된 쿼리를 기반으로 동적으로 매퍼 인터페이스의 구현 클래스를 만들어주는 것이며, 이렇게 생성된 구현 클래스를 통해 개발자는 데이터베이스와 상호작용할 수 있습니다. 개발자는 구현 클래스를 직접 작성할 필요가 없고, MyBatis가 자동으로 생성해주는 것이 특징입니다.




📝 간단 요약

✅ 인터페이스만 있어도 구현체를 만들어줌

✅ 팩토리는 세션을 만들어 내는 존재 -> xml로 설정해야함

✅ 세션을 통해서 커넥션을 생성하거나 원하는 sql을 전달하고 결과를 리턴받음

✅ 스프링에 팩토리를 등록하는 작업은 sql세션팩토리 빈을 이용하여 기존 datasource빈과 설정을 통합 시킬 수 있음 그냥 할때는 xml로 설정해야함

✅ 마이바티스 스프링을 추가하면 세션팩토리빈을 사용할 수 있음

✅ 커넥션풀(설정했던 계정이나 커넥션풀에 대한 설정)을 추가하면 마이바티스 셋팅을 다 해줌




📝 spring-jdbc(JdbcTemplate)과 MyBatis의 차이점

✅ MyBatis는 기존의 SQL을 그대로 활용할 수 있다는 장접이 있고, 진입 장벽이 낮은 편이어서 JDBC의 대안으로 많이 사용
✅ 스프링 프레임워크의 특징 중 하나는 다른 프레임워크를 배척하는 대신에 다른 프레임워크들과의 연동을 쉽게 하는 추가적인 라이브러리들이 많다는 것입니다.
✅ MyBatis 역시 mybatis-spring이라는 라이브러리를 통해서 쉽게 연동작업을 처리할 수 있습니다.
✅ 스프링 jdbc는 스프링이 제공하는 모듈이며, MyBatis는 프레임워크

spring-jdbc (JdbcTemplate)

Spring 애플리케이션에서 데이터베이스 연동 및 JDBC 작업을 수행하기 위해 사용되며, JdbcTemplate을 통해 간단하고 효과적인 JDBC 코드 작성이 가능하고, 데이터베이스와의 연결 관리, 예외 처리 등을 자동으로 처리합니다.

MyBatis

MyBatis는 SQL 매핑 프레임워크로, SQL 쿼리를 XML 또는 어노테이션을 사용하여 자바 코드에서 분리할 수 있, 객체와 데이터베이스 레코드 간의 매핑을 설정 파일이나 어노테이션을 통해 쉽게 정의할 수 있다.

동적 쿼리를 작성하기 용이하며, 복잡한 쿼리와 프로시저를 다루는 데 유용합니다.


❓mybatis-spring 라이브러리가 없었다면?

<!-- applicationContext.xml -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!-- 데이터베이스 연결 정보 설정 -->
    <!-- ... -->
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!-- MyBatis의 설정 파일 위치 지정 -->
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <!-- MyBatis 매퍼 파일 위치 지정 -->
    <property name="mapperLocations" value="classpath:mappers/*.xml" />
</bean>

위와 같이 xml파일을 이용하여 sqlSessionFactory를 등록해야함. 그러나
스프링에서는 MyBatis와의 통합을 위해 SqlSessionFactoryBean을 제공한다. 이를 사용하면 MyBatis의 SqlSessionFactory를 스프링의 빈으로 쉽게 설정할 수 있다.




SqlSessionFactory는 MyBatis에서 데이터베이스와의 세션을 관리하는 핵심 인터페이스입니다. 스프링에서는 MyBatis와의 통합을 편리하게 하기 위해 SqlSessionFactoryBean을 제공하며, 이를 통해 MyBatis의 SqlSessionFactory를 스프링의 빈으로 등록할 수 있습니다.




💾 MemberMapper 인터페이스

public interface MemberMapper {

    // 실행할 쿼리를 애너테이션을 이용하여 넣음
    // 인터페이스 만으로 구현체가 만들어짐
    /*
    @Select("select * from member")
    List<Member> getMembers();
    */
    
    // into columns 값은 db의 속성이름을 입력
    // values 값은 객체의 필드명을 입력
    /*
    @Insert("insert into member (user_no, user_id, user_name, user_pw) values " +
            "(seq_member.nextval, #{userId}, #{userName}, #{userPw})")
    int register(Member member);
    */
    
    List<Member> getMember(Member member);
    int register(Member member);
    int delete(String userId);
    int update(Member member);

}

인터페이스 만으로도 구현체가 만들어짐



💾 MemberMapper.xml파일

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mapper.MemberMapper">
    <!--id : 필드명 resultType : 반환값-->
    <select id="getMember" resultType="models.Member">
        <bind name="pattern" value="'%' + _parameter.getUserId() + '%'" />
        <if test="userName != null">
            <bind name="userName" value="'%' + _parameter.getUserName() + '%'" />
        </if>
        SELECT * FROM MEMBER
        <!--"<where>"일 경우 앞에 있는 AND가 제거됨-->
        <!--suffixOverrides : 뒤에 제거 prefixOverrides : 앞 제거 -->
        <trim prefix="WHERE" suffixOverrides="AND | OR">
            <if test="userId!=null">
                USER_ID LIKE #{pattern} AND
            </if>
            <if test="userName!=null">
                USER_NAME LIKE #{userName}
            </if>
        </trim>
    </select>
    <insert id="register">
        <!--keyProperty="userNo"에 value가 담김 / order="BEFORE" 쿼리 실행 전에-->
        <!--jdbc템플릿의 keyHolder는 PreparedStatement에 값을 저장해서 반환값을 받았다-->
        <selectKey keyProperty="userNo" order="BEFORE" resultType="long">
            SELECT SEQ_MEMBER.nextval FROM DUAL
        </selectKey>
        INSERT INTO MEMBER (USER_NO, USER_ID, USER_PW, USER_NAME, EMAIL)
        VALUES (#{userNo}, #{userId}, #{userPw}, #{userName}, #{email})
    </insert>
    <update id="update">
        UPDATE MEMBER
        <!--set은 마지막에 있는 컴마를 제거해줌-->
        <set>
            <if test="userPw!=null">
                USER_PW = #{userPw},
            </if>
            <if test="userName!=null">
                USER_NAME=#{userName},
            </if>
            <if test="email!=null">
                EMAIL=#{email}
            </if>
        </set>
        WHERE USER_ID = #{userId}
    </update>
    <delete id="delete">
        DELETE FROM MEMBER WHERE USER_ID=#{userId}
    </delete>
</mapper>

복잡한 쿼리는 애너테이션방식으로는 구현하기 힘들기 때문에 mapper라는 형태의 xml파일로 생성하며, 인터페이스와 동일한 명칭의 xml파일과 통합된다. 또한 xml파일은 정적자원이기 때문에 패키지 경로를 resource내부에 해도 무방하다.



💾 MemberMapperTest 테스트 파일


@ExtendWith(SpringExtension.class)
// 테스트환경에서의 스프링 컨테이너로 사용할 클래스
@ContextConfiguration(classes = AppConfig.class)
class MemberMapperTest {

    // memberMapper 는 인터페이스
    @Autowired
    MemberMapper memberMapper;

    @Test
    @DisplayName("멤버 전체 조회")
    void memberListTest(){
        // 인터페이스에 쿼리만 입력해도 결과가 나올 수 있는 과정이 추가됨
        //List<Member> members = memberMapper.getMembers();
        //System.out.println(memberMapper.getClass());
        // memberMapper는 프록시 -> class jdk.proxy2.$Proxy26 프록시
        // 구현체는 스프링 내부에서 만들어짐
        //members.stream().forEach(System.out::println);
    }

    @Test
    @DisplayName("멤버 리스트 조회")
    void memberListTest1(){
        Member member = Member.builder().userId("USER").build();
        memberMapper.getMember(member).stream().forEach(System.out::println);
        //System.out.println(memberMapper.getClass().getName()); // 프록시
    }

    @Test
    @DisplayName("멤버 추가하기")
    void insertTest(){
        Member member = Member.builder().userName("이순신")
                .userPw("123456").userId("user100")
                .email("test@org.test").build();
        memberMapper.register(member);
        System.out.println(member);
    }

    @Test
    @DisplayName("멤버 수정하기")
    void memberUpdateTest(){
        Member member = Member.builder().userId("user100")
                .userPw("123456").build();
        int update = memberMapper.update(member);
        System.out.println(update);
    }

    @Test
    @DisplayName("멤버 삭제하기")
    void memberDeleteTest(){
        int delete = memberMapper.delete("user100");
        System.out.println(delete);
    }
}




🔍 마이바티스의 동작 원리

기본적으로 MyBatis에서 매퍼 인터페이스의 메서드를 호출하면, MyBatis는 해당 메서드와 연결된 SQL을 찾아서 실행하고 그 결과를 반환합니다. 이때 프록시 객체가 메서드 호출을 가로채서 SQL 세션과의 상호 작용을 처리합니다.

프록시는 인터페이스의 메서드 호출에 대해 필요한 SQL 세션과의 연결, 트랜잭션 관리, 캐싱, 매개변수 매핑 등을 처리하는 역할을 합니다. 따라서 사용자가 작성한 인터페이스의 메서드 호출이 실제로 데이터베이스와 통신하는 구체적인 로직으로 변환되어 실행됩니다.

간단히 말해서, MyBatis의 프록시는 사용자가 작성한 인터페이스에 대한 호출을 가로채서 필요한 SQL 처리를 수행하는 역할을 합니다.

프록시 역할을 하는 것은 SqlSessionFactory입니다. SqlSessionFactory는 SqlSession 객체를 생성하는 팩토리 역할을 하며, 사용자가 SqlSession을 필요로 할 때마다 필요한 설정 정보를 기반으로 SqlSession을 생성하며, SqlSession은 실제로 데이터베이스와의 연결 및 상호 작용을 담당합니다.

SqlSession은 MyBatis에서 데이터베이스와의 상호작용을 위한 메인 인터페이스 중 하나입니다. MyBatis는 SQL 쿼리를 실행하고 데이터베이스와의 세션을 관리하기 위해 SqlSession을 제공합니다.

간단히 말하면, SqlSession은 데이터베이스와의 트랜잭션을 담당하며, SQL 문을 실행하고, 결과를 매핑하고, 데이터베이스와의 커넥션을 관리합니다. 각각의 데이터베이스 트랜잭션은 SqlSession을 통해 시작되고 커밋 또는 롤백됩니다.



SqlSessionFactory:

마이바티스의 핵심 객체 중 하나로, 데이터베이스와의 연결을 설정하고 SqlSession 객체를 생성합니다.
SqlSessionFactory는 애플리케이션 전체에 대해 단일한 인스턴스로 유지되며, 주로 애플리케이션의 초기화 단계에서 생성됩니다.
SqlSessionFactory는 XML 또는 자바 코드로 정의된 설정을 기반으로 DataSource 및 트랜잭션 관리자 등을 설정합니다.

SqlSession

SqlSession은 애플리케이션과 데이터베이스 간의 상호작용을 담당하는 인터페이스입니다.
데이터베이스와의 각각의 트랜잭션 단위마다 SqlSession이 생성됩니다.
SqlSession은 SQL 실행, 데이터베이스와의 커밋 또는 롤백 등의 작업을 수행합니다.
SqlSession은 직접 사용한 후에는 반드시 닫아주어야 합니다. 주로 try-with-resources나 finally 블록에서 close() 메서드를 호출하여 자원을 해제합니다.

트랜잭션 관리

SqlSessionFactory는 트랜잭션 관리에 대한 설정을 포함하고, SqlSession은 이를 실제로 수행합니다. SqlSession은 트랜잭션을 시작하고, SQL 실행 후에 커밋 또는 롤백을 수행합니다.




🔍 mybatis-spring를 추가했을 때의 편리한 점:

  • 트랜잭션 관리의 용이성
    : Spring-MyBatis를 사용하면 Spring의 선언적 트랜잭션 관리를 손쉽게 사용할 수 있습니다. @Transactional 어노테이션을 활용하여 트랜잭션을 관리할 수 있습니다.

  • 의존성 주입의 편리함
    : Spring-MyBatis를 사용하면 SqlSessionFactory나 SqlSession과 같은 MyBatis의 핵심 객체를 Spring의 IoC 컨테이너를 통해 주입받을 수 있습니다.

  • AOP의 용이성
    : Spring-MyBatis를 통해 AOP를 사용하여 cross-cutting한 관심사를 쉽게 적용할 수 있습니다. 예를 들어 로깅, 예외 처리, 성능 모니터링 등을 AOP로 구현할 수 있습니다.

  • 데이터 소스 관리 편의성
    : Spring-MyBatis를 사용하면 데이터베이스 연결 정보와 트랜잭션 관리를 위한 DataSource를 간편하게 설정할 수 있습니다.

  • XML 설정 및 어노테이션 기반의 설정
    : Spring-MyBatis를 통해 XML 설정 파일을 통해 MyBatis 설정을 지원하며, 어노테이션을 사용하여 간편한 설정이 가능합니다.

  • 캐시 관리 편의성
    : Spring-MyBatis를 사용하면 MyBatis의 결과 캐시를 Spring에서 관리하거나, Spring의 캐시 추상화를 사용하여 효율적인 캐시 전략을 적용할 수 있습니다.




🔑 MyBatis 활용하기

1. CRUD 구현

1) CREATE(INSERT) 처리

  • INSERT만 처리되고 생성된 PK 값을 알 필요가 없는 경우
  • INSERT문이 실행되고 생성된 PK 값을 알아야 하는 경우
<selectKey keyProperty="bno" order="BEFORE" resultType="long">
      SELECT SEQ_BOARD.nextval FROM DUAL
</selectKey>
	<insert id="register">
        <!--keyProperty="userNo"에 value가 담김 / order="BEFORE" 쿼리 실행 전에-->
        <!--jdbc템플릿의 keyHolder는 PreparedStatement에 값을 저장해서 반환값을 받았다-->
        <selectKey keyProperty="userNo" order="BEFORE" resultType="long">
            SELECT SEQ_MEMBER.nextval FROM DUAL
        </selectKey>
        INSERT INTO MEMBER (USER_NO, USER_ID, USER_PW, USER_NAME, EMAIL)
        VALUES (#{userNo}, #{userId}, #{userPw}, #{userName}, #{email})
    </insert>

2) READ(SELECT) 처리

    <select id="getMember" resultType="models.Member">
        <bind name="pattern" value="'%' + _parameter.getUserId() + '%'" />
        <if test="userName != null">
            <bind name="userName" value="'%' + _parameter.getUserName() + '%'" />
        </if>
        SELECT * FROM MEMBER
        <!--"<where>"일 경우 앞에 있는 AND가 제거됨-->
        <!--suffixOverrides : 뒤에 제거 prefixOverrides : 앞 제거 -->
        <trim prefix="WHERE" suffixOverrides="AND | OR">
            <if test="userId!=null">
                USER_ID LIKE #{pattern} AND
            </if>
            <if test="userName!=null">
                USER_NAME LIKE #{userName}
            </if>
        </trim>
    </select>

3) DELETE 처리

    <delete id="delete">
        DELETE FROM MEMBER WHERE USER_ID=#{userId}
    </delete>

4) UPDATE 처리

    <update id="update">
        UPDATE MEMBER
        <!--set은 마지막에 있는 컴마를 제거해줌-->
        <set>
            <if test="userPw!=null">
                USER_PW = #{userPw},
            </if>
            <if test="userName!=null">
                USER_NAME=#{userName},
            </if>
            <if test="email!=null">
                EMAIL=#{email}
            </if>
        </set>
        WHERE USER_ID = #{userId}
    </update>

2. 동적 SQL

  1. if
  2. choose, when, otherwise
  3. trim, where, set

0개의 댓글