SpringBoot에서 마이바티스 사용하기

MyBatis와 SpringBoot

예제 프로젝트 다운로드



개요

  • 스프링부트, 마이바티스, H2 DB 를 이용한다.
  • 예시 프로젝트는 어노테이션 기반으로 작성되었다.

MyBatis 사용

Mapper란?

  • sql과 매핑하는 기능이다.
  • 인터페이스에 @Mapper 로 명시한다.


@Insert@Param

  • @Param("쿼리로 전달할 파라미터 이름")
    • 쿼리로 전달할 파라미터 이름: 해당 이름의 파라미터를 #{} 으로 사용할 수 있다.
    • 메서드의 파라미터 레벨에 작성한다.
    • 해당 파라미터를 쿼리문에서 사용할 수 있게 해준다.


  • @Insert("INSERT 쿼리")
    • 반환값은 int이어야한다.
    • 반환값: 삽입에 성공한 튜플의 개수


  • 사용 예시

    
      @Insert("INSERT INTO user(email, name, password) VALUES (#{user.email}, #{user.name}, #{user.password})")
      int insert(@Param("user") User user);
    
    
    


@Select()@Results

  • @Select("SELECT 쿼리")


  • @Result( id="맵id", value={ @Result(property="매핑할_필드명", column="매핑할_칼럼명"), ... } )
    • id 속성을 통해, 다른 메서드에서 매핑정보를 재활용할 수 있다.
    • @Select 경우, @Results@Result 를 통해 칼럼과 필드 간의 매핑을 진행해야한다.
    • @Insert 경우, #{} 를 통해 매핑한다.
    • 칼럼명과 필드명이 같다면 @Results 를 생략할 수 있다.


  • 사용 예시

    
    
      //-----------조건 없이 SELECT-------------
      @Select("SELECT * FROM user")
      @Results(id="myResultId", value = {
              @Result(property = "dbid", column = "dbid"),
              @Result(property = "email", column = "email"),
              @Result(property = "name", column = "name"),
              @Result(property = "password", column = "password")
      }) //필드명과 컬럼명이 같으면 @Results 는 필요없다.
      List<User> getAll();
    
      //-----------조건과 함께 SELECT-------------
      @Select("SELECT * FROM user WHERE dbid=#{id}")
      @ResultMap("myResultId") //매핑정보 재활용
      User getById(@Param("id") Long id);
    
    
    


@Update()

  • @Update("UPDATE 쿼리")
    • 반환값은 int형이다.
    • 수정된 튜플의 개수를 반환한다.
  • 사용 예시

    
    
      @Update("update user set name=#{user.name}, password=#{user.password} where dbid=#{user.dbid}")
      int updateById(@Param("user") User user);
    
    
    


@Delete

  • @Delete("DELETE 쿼리")
    • 반환값은 int형이다.
    • 삭제된 튜플의 개수를 반환한다.
  • 사용 예시

    
    
      @Delete("delete from membertab where dbid=#{dbid}")
      int deleteById(@Param("dbid") Long id);
    
    
    


@Options

  • @Options( userGeneratedKeys=true, keyProperty="키_필드", keyColumn="키_애트리뷰트" )
    • 위와 같이 작성하면, SQL에서 생성된 값(AUTO_INCREMENT 등)이 매핑된 객체의 필드에 역으로 바인딩된다.
    • 사용 예시

      
      
        @Insert("INSERT INTO user(email, name, password) VALUES (#{user.email}, #{user.name}, #{user.password})")
        @Options(useGeneratedKeys = true, keyProperty = "dbid", keyColumn="dbid") //SQL이 생성한 KEY 값을 매핑된 객체의 dbid 필드에도 담아주겠다.
        int insert(@Param("user") User user);
      
              
      


@Transactional

  • 해당 메서드를 하나의 DB 작업단위(트랜잭션)로 지정한다.
  • 해당 애너테이션이 붙은 메서드 (보통 서비스 클래스의 메서드)에서 예외가 발생하면, 해당 메서드가 작업한 트랜잭션 (매퍼를 통해 수행한 DB 작업)은 모두 롤백된다.
  • 롤백할 예외 설정
    • @Transactional(rollbackFor=설정할_예외.class)
    • 해당 예외와 자식 예외까지 처리한다.
    • 처리할 예외의 Default값 = RuntimeException.class



1대다 관계 처리: @Many

예시 ERD

Untitled


User 클래스

import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 사용자 정보 도메인
 */
@Data
public class User {

  private Long dbid;
  private String email;
  private String name;
  private String password;

  //1대다 관계 설정 (1: User, 다: Phone)
  //테이블에 해당 컬럼은 존재하지 않는다.
  private List<Phone> phoneList;
}


Phone 클래스

import lombok.Data;

/**
 * 핸드폰 정보 도메인
 */
@Data
public class Phone {
    private Long id;
    private Long dbid;
    private String number;
}


PhoneMapper 인터페이스

@Select("SELECT * FROM phone WHERE id=#{id}")
//@Results 생략
List<Phone> getByUserDbId(@Param("id") Long id);


UserMapper 인터페이스

@Select("SELECT * FROM user")
@Results(@Result(property="phoneList", column="dbid", many=@Many(select="패키지명.Phone.getByUserDbId")))
List<User> getAll();
  • property="phoneList"
    • User.phoneList 필드에 저장하겠다.
  • column="dbid"
    • “User 테이블의 dbid 칼럼의 값”을 “@Many 로 지정한 메서드의 파라미터”로 전달하겠다.
  • many=@Many(select="패키지명.매퍼_인터페이스명.메서드명")
    • 해당 매퍼의 메서드를 호출하겠다.
    • 그리고 그 결과를 User.phoneList 필드에 저장하겠다.



활용 예시

ERD

Untitled


Data 객체: User

import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 사용자 정보 도메인
 */
@Data
public class User {

  private Long dbid;
  private String email;
  private String name;
  private String password;

  //1대다 관계 설정 (1: User, 다: Phone)
  //테이블에 해당 컬럼은 존재하지 않는다.
  private List<Phone> phoneList;
}


Mapper: UserMapper

import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface UserMapper {

  //Create
  @Insert("INSERT INTO user(email, name, password) VALUES (#{user.email}, #{user.name}, #{user.password})")
  @Options(useGeneratedKeys = true, keyProperty = "dbid") //SQL이 생성한 KEY 값을 매핑된 객체의 dbid 필드에도 담아주겠다.
  int insert(@Param("user") User user);

  //Read - 1
  @Select("SELECT * FROM user")
  @Results(id="myResultId", value = {
          @Result(property = "dbid", column = "dbid"),
          @Result(property = "email", column = "email"),
          @Result(property = "name", column = "name"),
          @Result(property = "password", column = "password"),
          @Result(property = "phoneList", column = "dbid", many = @Many(select = "test.mybatis.domain.PhoneMapper.getByUserId"))
  }) //필드명과 컬럼명이 같으면 @Results 는 필요없다.
  List<User> getAll();

  //Read - 2
  //@Results 재활용
  @Select("SELECT * FROM user WHERE dbid=#{id}")
  @ResultMap("myResultId")
  User getById(@Param("id") Long id);
}


Data 객체: Phone

import lombok.Data;

/**
 * 핸드폰 정보 도메인
 */
@Data
public class Phone {
    private Long id;
    private Long dbid;
    private String number;
}


Mapper: PhoneMapper

import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface PhoneMapper {
    //Create
    @Insert("INSERT INTO phone(number, dbid) VALUES (#{phone.number}, #{phone.dbid})")
    @Options(useGeneratedKeys = true, keyProperty = "id") //SQL이 생성한 KEY 값을 매핑된 객체의 dbid 필드에도 담아주겠다.
    int insert(@Param("phone") Phone phone);

    //Read - 1
    @Select("SELECT * FROM phone")
    @Results(id="phoneId", value = {
            @Result(property = "id", column = "id"),
            @Result(property = "number", column = "number"),
            @Result(property = "dbid", column = "dbid")
    }) //필드명과 컬럼명이 같으면 @Results 는 필요없다.
    List<Phone> getAll();

    //Read - 2
    //@Results 재활용
    @Select("SELECT * FROM phone WHERE id=#{id}")
    @ResultMap("phoneId")
    Phone getById(@Param("id") Long id);

    @Select("SELECT * FROM phone WHERE dbid=#{dbid}")
    @ResultMap("phoneId")
    List<Phone> getByUserId(@Param("dbid") Long dbid);
}


컨트롤러: UserController

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import test.mybatis.domain.User;
import test.mybatis.domain.UserMapper;
import test.mybatis.service.UserService;

import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {

    private final UserMapper userMapper;

    @PostMapping("/insert")
    public User insertUser(@RequestBody User user) {
        userMapper.insert(user);
        return user;
    }

    @GetMapping("/selectAll")
    public List<User> selectAllUser() {
        return userMapper.getAll();
    }

    @GetMapping("/selectById/{id}")
    public User selectById(@PathVariable Long id) {
        return userMapper.getById(id);
    }

}


컨트롤러: PhoneController

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import test.mybatis.domain.Phone;
import test.mybatis.domain.PhoneMapper;
import test.mybatis.domain.User;
import test.mybatis.domain.UserMapper;

import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping("/phone")
public class PhoneController {
    private final PhoneMapper phoneMapper;

    @PostMapping("/insert")
    public Phone insertPhone(@RequestBody Phone phone) {
        phoneMapper.insert(phone);
        return phone;
    }

    @GetMapping("/selectAll")
    public List<Phone> selectAllPhone() {
        return phoneMapper.getAll();
    }

    @GetMapping("/selectById/{id}")
    public Phone selectById(@PathVariable Long id) {
        return phoneMapper.getById(id);
    }

}



로깅하기

application.properties 파일

logging.level.패키지명.매퍼_인터페이스명=TRACE
  • SQL 쿼리문과 결과값이 로깅된다.