ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] 스프링부트에서 Mybatis 사용하기
    Server/MyBatis 2020. 8. 19. 02:32
    728x90
    반응형

     

    먼저 Mybatis를 얘기하기 전에 JAVA의 ORM이 무엇이 있는지 어떤 것인지에 대해서 정리해보자.

     

    ORM(Object Relational Mapping)이란?

    ORM에서 Object는 객체지향 언어의 객체를 의미한다. Relational은 관계형 데이터베이스(Relational Database)의 데이터를 의미한다. Mapping이 의미하는 것은 객체지향 언어의 객체와 관계형 데이터를 서로 변환해 준다는 것이다.

     

    관계형 데이터베이스에서 조회한 데이터를 Java 객체로 변환하여 리턴해 주고, Java 객체를 관계형 데이터베이스에 저장해 주는 라이브러리 혹은 기술을 말한다.

     

    Java ORM 기술로 유명한 것은 JPA, Hibernate가 있다. Mybatis는 JDBC로 처리하는 상당 부분의 코드와 파라미터 설정 및 결과 매핑을 대신해준다. 하지만 직접 쿼리를 작성하여 명시하여야 하기 때문에 ORM으로 보기 힘들다.

     

     

    MyBatis란?

    • 객체지향 언어인 자바의 관계형 데이터베이스 프로그래밍을 좀 더 쉽게 할 수 있게 도와주는 개발 프레임워크
    • 복잡한 JDBC 코드를 걷어내며 깔끔한 소스코드를 유지할 수 있다.
    • 자바의 객체(Object)와 SQL 사이에서 자동 맵핑을 도와주는 프레임워크
    • XML 형태로 쓰인 JDBC 코드라고 생각해도 될 만큼 JDBC의 모든 기능을 제공한다.

     

     

     

    Maven

    그리고 이제 MyBatis를 Spring Boot에서 적용해보고 어떤 특징과 기능이 있는지를 알아보자. 프로젝트는 Maven으로 할 예정이다.

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    pom.xml 에 위의 의존성을 추가해주자. 

     

    Gradle

    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4'

     

     

    application.properties

    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

     

    JDBC 드라이버 클래스의 이름을 지정한다. 위 이름은 MySQL JDBC 드라이버 클래스이다.

     

    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=yes&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Seoul

     데이터베이스의 IP와 스키마 이름을 지정해주면 된다. ex) mybatis는 스키마이름이다. 그리고 뒤에 부분은 UTF-8 인코딩 설정, 서버 타임존 설정이다.

    spring.datasource.username=root
    spring.datasource.password=root

    MySQL 데이터베이스에 연결할 계정의 username과 password를 적어준다.

    mybatis.type-aliases-package=com.example.demo.model

    데이터베이스의 조회 결과 데이트를 담을 클래스들의 패키지를 지정한다. 

    <select id="findById" resultType="Student">

    예를들면 select 태그의 resultType으로 등록된 Student 클래스의 패키지는 com.example.demo.model이어야 한다. 하지만 위에서 mybatis.type-aliases를 설절해줬기 때문에 간단하게 resultType을 적을 수 있다.

     

    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=yes&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Seoul
    spring.datasource.username=root
    spring.datasource.password=root
    mybatis.type-aliases-package=com.example.demo.model   // 두번 째 MyBatis 사용법을 사용한다면 추가

    MyBatis를 사용할 수 있는 방법은 2가지 정도를 소개하려 한다.

     

    일단 첫 번째 방법에 대해서 알아보자.

    import com.example.demo.dto.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    @Mapper
    public interface UserMapper {
    
        @Select("SELECT * FROM user")
        List<User> findAll();
    
        @Select("SELECT * FROM user WHERE userIdx = #{userIdx}")
        User findByUserIdx(@Param("userIdx") int userIdx);
    }

    만약 첫 번째 방법을 사용한다면 위에서 설명한 application.properties에서 마지막 설정은 추가하지 않아도 된다. 그리고 인터페이스를 하나 만든 후에 @Mapper 어노테이션을 추가해준다. 

     

     

    @Mapper

    • Spring IoC 컨테이너에 서비스 Bean으로 등록
    • 해당 인터페이스에 등록된 SQL Annotation을 토대로 실제 SQL문을 실행시켜 준다.
    • 3이상 버전부터 mapper.xml 대신 interface 클래스의 Annotation을 통해 SQL을 사용할 수 있다.

     

     

    원래는 xml에서 mybatis 코드를 작성하지만 3이상 버전부터 @Select, @Insert 등의 어노테이션이 추가되었기 때문에 바로 쿼리를 작성하여 사용할 있다. 그리고 findByUserIdx에서 처럼 동적바인딩을 하고 싶다면 #{ }를 이용하면 된다. 그리고 매개변수를 받아올 때는 @Param(" ")을 통해서 값을 명시한다.

     

    그리고 보통 PK값은 Auto Increment로 되어 있는데 @Insert한 후에 AI값을 받아오고 싶을 때 @Options라는 어노테이션이 존재한다.

    @Getter
    public class User {
        private int userIdx;
        private String name;
        private String part;
    }
    @Mapper
    public interface UserMapper {
    
        @Insert("INSERT INTO user(name, part) VALUES(#{name}, #{part}")
        @Options(useGeneratedKeys = true, keyProperty = "userIdx")
        int save(@Param("user") final User user);
    }

    위에서 userIdx가 AI에 해당하기 때문에 keyColumn에 userIdx를 적어주고, save의 반환형을 int형으로 바꿔주면 insert가 된 후에 Auto Increment값을 반환해준다.

     

    간단한 회원 전체를 조회하는 예제를 진행해보자.

    @RestController
    public class UserController {
    
        private UserService userService;
    
        public UserController(UserService userService) {
            this.userService = userService;
        }
    
        @GetMapping("/users")
        public ResponseEntity AllUsers() {
            return new ResponseEntity(userService.getAllUsers(), HttpStatus.OK);
        }
    }
    @Service
    public class UserService {
    
        private UserMapper userMapper;
    
        public UserService(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    
        // 멤버 전체 조회
        public DefaultRes getAllUsers() {
            final List<Users> userList = userMapper.findAll();
            if (userList.isEmpty())
                return DefaultRes.res(StatusCode.NOT_FOUND, ResponseMessage.NOT_FOUND_USER);
            return DefaultRes.res(StatusCode.OK, ResponseMessage.READ_USER, userList);
        }
    }

    UserMapper도 @Mapper 어노테이션을 통해서 Bean으로 등록되었기 때문에 UserService 클래스에서 생성자를 통해서 주입받을 수 있다.

    {
        "statusCode": 200,
        "responseMessage": "회원 정보 조회 성공",
        "data": [
            {
                "userIdx": 1,
                "name": "lee",
                "part": "server"
            },
            {
                "userIdx": 2,
                "name": "choi",
                "part": "Android"
            },
            {
                "userIdx": 3,
                "name": "park",
                "part": "iOS"
            }
        ]
    }

    그리고 실행한 후에 요청을 보내면 위와 같이 DB에 있는 정보가 잘 출력이 되는 것을 알 수 있다. (예제이기 때문에 성공부분만 구현하였다)

     

     

    이번에는 두 번째 방법에 대해서 알아보자.

    mybatis.type-aliases-package=com.example.demo

    application.properties에 위의 내용을 추가하라고 설명하였다. 이번 방법에서 어떠한 설정인지 알 수 있다 아래 xml 파일에서 쿼리를 작성할 때 resultType이 위치하는 경로를 적어주어야 Mybatis가 찾아서 맵핑을 해줄 수 있다. 

    @Mapper
    public interface UserMapper {
    
        List<Users> findAll();
    }

    이번에는 UserMapper 인터페이스에서 쿼리를 작성하는 것이 아니라 XML파일에서 쿼리를 작성할 것이다.

     

    위와 같이 resources 아래에 현재 mapper 디렉토리 안에 존재하는 인터페이스와 경로를 똑같이 맞추자.

    따라서 me.gyun.demo.mapper 아래에 UserMapper.xml이 존재한다

    <?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="me.gyun.demo.mapper.UserMapper">
        <select id="findALl" resultType="User">
            SELECT * FROM user;
        </select>
    </mapper>

    위와 같이 namespace에 UserMapper 인터페이스가 존재하는 경로를 지정해준다. 그리고 id는 @Mapper가 있는 인터페이스에서 사용한 메소드의 이름, resultType은 결과의 형을 써주면 된다. resultType의 결과 객체에 쿼리 결과가 자동으로 맵핑되어 채워지는데, 조건이 필요하다. DB 컬럼의 결과와 resultType 클래스의 setter 메소드와 정확히 일치해야 한다. 지금은 List<User> 형태로 나올텐데 resultType을 User라고만 써놓으면 된다. 이러한 점이 MyBatis의 장점이다.

     

    이번에는 user를 추가하는 예제를 한번 진행해보자.

    @Mapper
    public interface UserMapper {
    
        void userAdd(User user);
    }
    <insert id="userAdd">
        INSERT user (name, part) VALUES (#{name}, #{part})
    </insert>

    위와같이 @Mapper 어노테이션이 있는 userMapper 인터페이스에 insert하기 위해 메소드를 선언하고 파라미터로 User 객체를 받는다. 그리고 XML 파일에서 insert를 구현하였는데 여기서 한가지 알아두어야 할 점은 #{}으로 동적바인딩을 할 때 파라미터에는 User객체가 있지만 바로 #{name}, #{part}와 같은 형태로 값을 꺼낼 수 있다. (조건은 User 클래스의 필드이름과 #{} 안에 들어가는 이름이 같아야 한다)

     

    하지만 위와 같이 insert 문을 작성하면 한가지 아쉬운 점이 있다. 그것은 현재 insert된 후에 userIdx가 몇인지를 모른다는 것이다. 예시를 보면서 이해해보자.

    User user = new User("파트", "이름");
    System.out.println(user.getUserIdx());  // 0
    
    userMapper.insert(user);
    System.out.println(user.getUserIdx());      // 0

    userIdx는 PK값이기 때문에 디비에 insert할 때 굳이 넣지 않아도 되기 때문에 위와 같이 insert를 진행하게 되면 Mybatis가 자바 클래스와 맵핑을 할 때 userIdx 값을 알 수 없기 때문에 결과는 0이 출력된다. 따라서 userIdx를 자동으로 넣게 해주려면 아래와 같이 하면된다.

    <insert id="userAdd" useGeneratedKeys="true" keyProperty="userIdx">
        INSERT user (name, part) VALUES (#{name}, #{part})
    </insert>

     

     

    MyBatis XML 경로 설정

    XML을 이용해서 MyBatis를 사용하려면 resources 폴더 아래에 본인이 만들었던 패키지 경로를 똑같이 만들어 그 아래에 XML 파일을 작성해야했다. MyBatis 내부적으로 Default 경로가 설정되어 있는 것 같다. 그래서 저렇게 패키지를 만들어 사용하기 번거롭기 때문에 경로를 커스텀해서 사용하려 한다. 사용방법은 아래와 같다.

    mybatis.mapper-locations=/mapper/**/*.xml

    application.properties 파일에 위의 코드를 작성해주자. mapper-loacation은 mapper.xml 파일 형식의 파일 패턴을 넣어주면 된다. **는 하위 폴더 레벨에 상관없이 모든 경로를 뜻하며, *.XML은 어떠한 이름도 가능하다는 뜻이다. 예를들면 mapper/me/gyun/Member.XML도 가능하고, mapper/Member.XML, mapper/a/b/Member.XML도 가능하다는 뜻이다. (자신이 편한대로 커스텀해서 사용하면 된다)

     

    반응형

    댓글

Designed by Tistory.