코딩항해기

[Spring] MyBatis (인텔리제이 Intellij) 본문

Spring

[Spring] MyBatis (인텔리제이 Intellij)

miniBcake 2024. 11. 4. 12:06

 

 

*만약 개발도구(IDE)가 이클립스라면 MyBatis 플러그인이 필요하다. (인텔리제이는 필요없음)

*정리한 내용은 spring 방식으로 관련 어노테이션을 사용하는 springboot에서는 mapper인터페이스를 사용한다.

마이바티스 MyBatis (IBatis)

자바 개발자들이 데이터베이스를 쉽게 다룰 수 있도록 도와주는 오픈 소스 ORM(Object-Relational Mapping) 프레임워크이다.

마이바티스는 SQL문과 프로그래밍 언어 코드를 분리해 응집도를 높이고 결합도를 낮춰 유지보수에 용이하다.

매핑 Mapping
서로 다른 데이터가 서로 오갈 수 있도록 변환하는 것이다.
ORM의 경우 객체(DTO,VO)와 테이블(또는 SQL구문, ResultSet(결과) 등)의 데이터가 서로 오갈 수 있도록 변환(매핑)한다.

 

MyBatis 장점

코드 가독성

JDBCTemplate를 사용할 때처럼 반복되는 코드가 줄어 간결해주고, 쿼리문을 복잡하게 입력하지 않고 명시적으로 입력할 수 있어 가독성이 좋다. (? -> #{email} 바로 어떤 값이 들어가는지 알 수 있다.)

 

간결성, 유연성

SQL문과 프로그래밍 언어 코드가 분리되어 있어 응집도를 높고 결합도를 낮아 유지보수에 용이하며, SQL쿼리를 직접 작성할 수 있어 유연하다.

 

개발 비용 절약

기존에는 SQL문이 java 파일 안에 있어 쿼리문 변경 시에도 재 컴파일로 인한 컴파일 비용이 추가됐다. 마이바티스는 xml에 쿼리문을 정의하기 때문에 쿼리문을 수정해도 컴파일을 다시 진행할 필요가 없어 개발 비용이 절약된다.

 

동적 쿼리 용이

마이바티스를 사용하면 동적쿼리를 구성하는 것이 더욱 용이하고 쉬워진다.

마이바티스에서는 동적 쿼리를 작성하기 위해 <if>, <choose>, <when>, <otherwise>, <foreach> 등의 태그를 사용할 수 있다.

동적 쿼리
실행 시점 조건에 따라 SQL 쿼리를 동적으로 생성하는 것이다.

 

그 외에도 캐시 기능을 통해 연산 속도를 높일 수 있고 다양한 데이터베이스에 대한 지원을 제공하고 있어 용이하다.

 

 

환경 세팅

spring-core, spring-jdbc, mybatis, mybatis-spring에 대한 의존성이 필요하다.

<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-jdbc</artifactId>
     <version>3.2.5.RELEASE</version>
</dependency>
<!--MyBatis-->
<dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.5.6</version>
</dependency>
<dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis-spring</artifactId>
     <version>2.1.0</version>
</dependency>

 

 

필요한 객체 생성

의존 주입을 마쳤으면 구동에 필요한 객체들을 생성해야한다.

DataSource와 SqlSessionFactoryBean, SqlSessionTemplate 객체가 필요하다.

	<!--가장 기본의 JDBCTemplate 클래스에서 사용하게 될 dataSource 클래스의 객체를 등록-->
	<bean class="org.apache.commons.dbcp.BasicDataSource" id="ds" destroy-method="close">
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/minibcake" />
		<property name="username" value="root" />
		<property name="password" value="1234" />
	</bean>
	<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
		<property name="dataSource" ref="ds" />
	</bean>

	<!--mybatis-->
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="ds" />
		<property name="configLocation" value="classpath:sql-map-config.xml" />
	</bean>
	<bean id="mybatis" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg ref="sqlSession" />
	</bean>

 

DataSource가 Connection을 제공하며 Factory가 Connection과 xml 설정을 기반으로 SqlSessionTemplate객체를 생성한다. 이 때 SqlSessionTemplate는 생성자 주입으로 SqlSessionFactoryBean을 필요로 하기 때문에 이전 과정에서 오류가 있다면 아예 객체가 생성되지 않아 후에 주입해 사용할 때 SqlSessionTemplate를 사용할 수 없다는 오류 메세지가 나오게 된다.

(가장 초기 오류를 확인해야 문제를 찾을 수 있다.)

 

이때 사용하는 sql-map-config.xml을 생성해야한다. (resource 폴더 하위)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--의존 주입한 것과 스키마를 맞춰줘야한다.-->

<configuration>
    <!--자료형의 별칭 붙여주기-->
    <typeAliases>
        <!--BoardDTO를 board로 별칭 설정-->
        <typeAlias type="com.koreait.app.biz.board.BoardDTO" alias="board" />
    </typeAliases>

    <!--매퍼 설정 알려주기 SQL 구문 설정파일 알려주기 -->
    <mappers>
        <!--JAVA 코드에서 제거한 SQL 구문들이 xxx.xml에 있다는 것을 알려주는 설정-->
        <mapper resource="mapping/board-mapping.xml" />
    </mappers>
</configuration>

 

sql-map-config.xml에서는 두 가지 설정을 필요로 하는데, DTO를 편하게 불러올 수 있도록 별칭을 설정할 수 있고, sql 구문이 어디 있는지 알려주는 설정을 할 수 있다. 

 

그럼 여기서 필요한 쿼리문이 담긴 board-mapping.xml을 만들어야한다. board-mapping.xml은 resource 하위에 mapping 하위에 위치해야한다. 경로가 틀릴 경우 resource 속성의 내용을 수정해야한다.

 

<?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="BoardDAO"><!--이름-->
    <insert id="insert"> 
        INSERT INTO BOARD (CONTENT,WRITER)
        VALUES(#{content},#{writer})
    </insert>
    <select id="selectOne" resultType="board">
        SELECT BID,CONTENT,WRITER FROM BOARD
        WHERE BID=#{bid}
    </select>
    <select id="selectAll" resultType="board">
        SELECT BID,CONTENT,WRITER FROM BOARD
    </select>
    <select id="selectAll_CONTENT" resultType="board">
        SELECT BID,CONTENT,WRITER FROM BOARD
        WHERE CONTENT LIKE CONCAT('%',#{keyword},'%')
    </select>
    <select id="selectAll_WRITER" resultType="board">
        SELECT BID,CONTENT,WRITER FROM BOARD
        WHERE WRITER=#{keyword}
    </select>
</mapper>

 

board-mapping에서는 mapper의 namespace를 통해 이름을 지정할 수 있고, 해당 이름은 중복되어서는 안된다. 

쿼리문은 insert라면 insert태그로, update라면 update태그로, delete라면 delete태그로, selectAll이나 selectOne이라면 select태그를 사용해 작성해야한다.

 

이때 반환 타입 지정이 필요하다면 resultType 속성으로 작성할 수 있는데, 이 때 들어가는 이름은 sql-map-config.xml에서 지정한 alias(별칭)이다. 지정하지 않았다면 어떤 DTO을 의미하는 것인지 전부 작성해야한다.

 

id 속성은 해당 쿼리문을 불러올 때 사용하게 되며 namespace명.id명으로 쿼리문을 지정하기 때문에 xml에 내에서는 중복되면 안된다. 다른 mapper와는 중복되어도 상관없다. 

 

이제 ? 대신 #{데이터명}을 사용하게 되는데 파라미터가 DTO 객체로 들어온다면 DTO의 필드명과 일치해야 정상적으로 작동된다.

 

사용

@Repository
@Slf4j
public class BoardDAO3 {
    @Autowired //주입
    private SqlSessionTemplate mybatis; //루트컨테이너에서 생성한 SqlSessionTemplate 객체 

    public List<BoardDTO> selectAll(BoardDTO boardDTO) {
        if(boardDTO.getCondition() != null && boardDTO.getCondition().equals("WRITER")){ //작성자 검색이라면
            log.info("log: boardDAO selectAll WRITER");
            return mybatis.selectList("BoardDAO.selectAll_WRITER", boardDTO); //selectAll_WRITER 쿼리문에 boardDTO 값을 넣어 실행
        }
        else if(boardDTO.getCondition() != null && boardDTO.getCondition().equals("CONTENT")){ //내용 검색이라면
            log.info("log: boardDAO selectAll CONTENT");
            return mybatis.selectList("BoardDAO.selectAll_CONTENT", boardDTO); //selectAll_CONTENT 쿼리문에 boardDTO 값을 넣어 실행
        }
        log.info("log: boardDAO selectAll");
        return mybatis.selectList("BoardDAO.selectAll", boardDTO); //따로 지정없을 때 selectAll 쿼리문에 boardDTO 값을 넣어 실행
    }
    public BoardDTO selectOne(BoardDTO boardDTO) {
        log.info("log: boardDAO selectOne");
        return mybatis.selectOne("BoardDAO.selectOne", boardDTO); //selectOne 쿼리문에 boardDTO 값을 넣어 실행
    }
    public boolean insert(BoardDTO boardDTO) {
        log.info("log: boardDAO insert");
        return mybatis.insert("BoardDAO.insert", boardDTO) >= 0; //insert 쿼리문에 boardDTO 값을 넣어 실행
    }
    public boolean update(BoardDTO boardDTO) {
        return false;
    }
    public boolean delete(BoardDTO boardDTO) {
        return false;
    }
}

 

루트 컨테이너(applicationContext.xml)에서 생성한 SqlSessionTemplate 객체를 주입받아 사용하게 되며, 각각의 쿼리문의 종류에 따라 insert update delete selectOne selectList로 사용할 수 있다. 만약 쿼리문을 실행하는데 필요한 값이 없다면 파라미터 객체는 생략할 수 있다.

 

MyBatis 주요 예외

예외 설명 원인
SqlSessionException SQL 세션 관련 작업 중 발생하는 기본적인 예외 세션 생성, 커밋, 롤백 등의 작업 실패
PersistenceException MyBatis의 기본 런타임 예외 SQL 실행 중 발생하는 일반적인 문제
TooManyResultsException 단일 결과를 기대했으나 여러 결과가 반환될 때 발생 selectOne() 메소드 사용 시 결과가 2개 이상
BindingException 파라미터 바인딩 과정에서 발생하는 예외 매퍼 메소드와 XML의 파라미터 불일치
BuilderException MyBatis 설정 빌드 과정에서 발생하는 예외 XML 설정 파일 오류, 매퍼 인터페이스 설정 오류
TypeException 타입 변환 관련 예외 ResultMap 타입 오류, 파라미터 타입 불일치
DataSourceException 데이터소스 관련 예외 커넥션 풀 문제, DB 연결 실패