💻 it/live-practice

[springBoot + mybatis] 개발환경 세팅(테스트 영상 & 소스코드 포함)

꼬비랩 2025. 11. 10.

스프링부트로 개발 시 mybatis를 정말 간편하게 세팅할 수 있다. 😄

역시나 시간이 지나면 기억을 못하기 때문에 미래의 내가 보기 위해 이곳에 기록한다.

예시를 위해서 테이블을 하나 생성하고 여기에 간단한 CRUD를 하겠다.

라이브 코딩(시간 상 많이 복붙) 👹

프로젝트 생성(인텔리제이) 😄

의존성은 아래처럼 세팅

웹 개발을 할 것이니까 Spring Web, 메모리 DB 사용을 위해 h2와 getter/setter, toString 등 단순반복 줄여주는 lombok, mybatis를 사용할거니까 mybatis, view template은 Thymeleaf 설정 후 FINISH 눌러서 프로젝트 생성

테스트 테이블 생성 🤗

create table member (
member_id bigint not null AUTO_INCREMENT,
email varchar(255),
member_name varchar(255),
password varchar(255),
primary key (member_id)
);

h2 버전: H2 2.1.214 (2022-06-13)

h2 세팅방법은 다루지 않겠음(중요한 점은 스프링부트에서 의존성 주입한 h2 버전과 클라이언트 h2 버전을 맞춰줘야 함)

 

mybatis mapper 경로 설정 🤗

기본적으로 있는 application.properteis 대신 application.yml을 쓰겠다.(기존 application.properteis는 삭제)

application.yml파일 생성 후 아래처럼 내용 기입

mapper-locations에 스프링부트에서 mapper를 인식할 수 있도록 mapper xml파일의 경로를 적어준다.(이게 끝이다.)

경로: src/main/resources/application.yml

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/devlsy-service1
    username: sa
    password:
    driver-class-name: org.h2.Driver

# 이 부분이 mybatis mapper 설정
mybatis:
  mapper-locations: mybatis-mapper/**/*.xml

url: jdbc:h2:tcp://localhost/~/devlsy-service1(여기에서 devlsy-service1은 db명인데 아래처럼 미리 db파일이 만들어져 있어야 된다. 없으면 만들면 된다.)

src/main/resources에 mybatis-mapper 폴더를 만들고 그 밑에 mapper 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"> <!-- dtd는 스프링부트에서 사용하는 mybatis 버전 입력 -->
<mapper namespace="com.example.mybatistest.mapper.MemberMapper">
    
    <!-- db column(스네이크케이스)과 vo(카멜케이스)의 필드명이 상이하므로 resultmap에서 매핑해줌 --> 
    <resultMap id="memberMap" type="com.example.mybatistest.domain.vo.MemberVO">
        <id column="member_id" property="memberId"/>
        <result column="email" property="email"/>
        <result column="member_name" property="memberName"/>
        <result column="password" property="password"/>
    </resultMap>
	
    <!-- ※ mapper interface의 method명과 id가 일치해야 함 -->
    <!-- 회원 등록 -->
    <insert id="insertMember" parameterType="com.example.mybatistest.domain.vo.MemberVO" useGeneratedKeys="true" keyProperty="memberId">
        INSERT INTO member (email, member_name, password)
        VALUES (#{email}, #{memberName}, #{password})
    </insert>

    <!-- 회원 단건 조회 -->
    <select id="findOneMember" parameterType="Long" resultMap="memberMap">
        SELECT * FROM member
        WHERE member_id = #{memberId}
    </select>

    <!-- 회원 목록 조회 -->
    <select id="findAllMember" resultMap="memberMap">
        SELECT * FROM member
    </select>

    <!-- 회원 수정 -->
    <update id="updateMember" parameterType="com.example.mybatistest.domain.vo.MemberVO">
        UPDATE member set
        email = #{email},
        member_name = #{memberName},
        password = #{password}
        WHERE member_id = #{memberId}
    </update>

    <!-- 회원 삭제 -->
    <delete id="deleteMember" parameterType="Long">
        DELETE FROM member
        WHERE member_id = #{memberId}
    </delete>

</mapper>

스프링부트에서는 starter 의존성에서 해당 스프링부트에 맞는 의존성을 자동으로 가져온다. 😎

VO/DTO 생성 😸

package com.example.mybatistest.domain.vo;

import com.example.mybatistest.domain.dto.MemberDto;
import lombok.*;

@Getter @Setter
@ToString
// 객체를 외부에서 함부로 생성하지 못하게 제한(생성 메서드를 이용하도록)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberVO {
    private Long memberId;
    private String email;
    private String memberName;
    private String password;

    /**
     * dto를 vo로 변환
     * @param memberDto
     * @return
     */
    public static MemberVO toVO(MemberDto memberDto) {
        MemberVO memberVO = new MemberVO();
        memberVO.setEmail(memberDto.getEmail());
        memberVO.setMemberName(memberDto.getMemberName());
        memberVO.setPassword(memberDto.getPassword());
        return memberVO;
    }

    /**
     * 객체 생성 메서드
     * @param email
     * @param memberName
     * @param password
     * @return
     */
    public static MemberVO createMember(String email, String memberName, String password) {
        MemberVO memberVO = new MemberVO();
        memberVO.setEmail(email);
        memberVO.setMemberName(memberName);
        memberVO.setPassword(password);
        return memberVO;
    }

    /**
     * 수정 메서드
     * @param email
     * @param memberName
     * @param password
     */
    public void updatemember(String email, String memberName, String password) {
        this.email = email;
        this.memberName = memberName;
        this.password = password;
    }
}
package com.example.mybatistest.domain.dto;

import com.example.mybatistest.domain.vo.MemberVO;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter @Setter
@ToString
public class MemberDto {
    private Long memberId;
    private String email;
    private String memberName;
    private String password;

    /**
     * vo를 dto로 변환
     * @param memberVO
     * @return
     */
    public static MemberDto toDto(MemberVO memberVO) {
        MemberDto memberDto = new MemberDto();
        memberDto.setMemberId(memberVO.getMemberId());
        memberDto.setEmail(memberVO.getEmail());
        memberDto.setMemberName(memberVO.getMemberName());
        memberDto.setPassword(memberVO.getPassword());
        return memberDto;
    }
}

mapper 인터페이스 생성 😃

package com.example.mybatistest.mapper;

import com.example.mybatistest.domain.vo.MemberVO;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper // 제일 중요한 어노테이션(이걸 설정해야 스프링부트에서 이 클래스를 찾아서 빈으로 등록한다.)
public interface MemberMapper {
	// ※ 메서드명과 mapper.xml의 id가 일치해야 한다.
    // 등록
    void insertMember(MemberVO memberVO);
    // 단건 조회
    MemberVO findOneMember(Long memberId);
    // 목록 조회
    List<MemberVO> findAllMember();
    // 수정
    void updateMember(MemberVO memberVO);
    // 삭제
    void deleteMember(Long memberId);

}

service 🙂

클라이언트와 송수신하는 용도인 dto와 db와 송수신 용도인 vo를 분리했다.

package com.example.mybatistest.service;

import com.example.mybatistest.domain.dto.MemberDto;
import com.example.mybatistest.domain.vo.MemberVO;
import com.example.mybatistest.mapper.MemberMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

import static com.example.mybatistest.domain.dto.MemberDto.toDto;
import static java.util.stream.Collectors.toList;

@Service
@Slf4j
@RequiredArgsConstructor
public class MemberService {

    private final MemberMapper memberMapper;

    /**
     * 등록
     * @param memberVO
     */
    public void save(MemberVO memberVO) {
        memberMapper.insertMember(memberVO);
    }

    /**
     * 단건 조회
     * @param memberId
     * @return
     */
    public MemberVO findOne(Long memberId) {
        return memberMapper.findOneMember(memberId);
    }

    /**
     * 목록 조회
     * @return
     */
    public List<MemberDto> findAll()     {
        List<MemberVO> members = memberMapper.findAllMember();
//        일반 foreach 사용
//        List<MemberDto> result = new ArrayList<>();
//        for (MemberVO m : members) {
//            MemberDto memberDto = toDto(m);
//            result.add(memberDto);
//        }
        // java 1.8 stream api 사용
        List<MemberDto> result = members.stream()
                .map(m -> toDto(m)).collect(toList());
        return result;
    }

    /**
     * 수정
     */
    public void updateMember(MemberVO memberVO) {
        memberMapper.updateMember(memberVO);
    }


    /**
     * 삭제
     * @param memberId
     */
    public void remove(Long memberId) {
        memberMapper.deleteMember(memberId);
    }
}

단위 테스트 🤠

서비스 클래스에 커서를 두고 윈도우 기준 ctrl + shirt + t를 누르면 JUnit 테스트 클래스를 만들 수 있다.

회원등록

기분 좋게 한번에 성공했다.

실제 DB에도 잘 저장되었다.

회원단건조회

회원목록조회

회원삭제

15번에 해당되는 loki를 삭제했기 때문에 assertThat으로 검증 시 실패가 되야 한다.

DB에는 이제 17번 회원만 남아 있는 상태이다.

이 상태에서 17번 회원을 조회 시 아래처럼 정상적으로 테스트 성공 한다.(이 회원은 있으니까)

수정테스트

DB

간단한 단위 테스트가 끝났다.(물론 더 디테일하게 해야겠지만 지금은 여기서 끝)

전체 테스트 클래스 코드

package com.example.mybatistest.service;

import com.example.mybatistest.domain.dto.MemberDto;
import com.example.mybatistest.domain.vo.MemberVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Slf4j
class MemberServiceTest {

    @Autowired MemberService memberService;
    
    @Test
    public void 회원등록() throws Exception {
        //givin
        MemberVO member = MemberVO.createMember("test@naver.com", "good", "1234");
        memberService.save(member);

        //when
        
        //then 
    }

    @Test
    public void 회원단건조회() throws Exception {
        //givin
        MemberVO member = memberService.findOne(15L);
        //when

        //then
        assertThat(member.getMemberName()).isEqualTo("loki");
    }

    @Test
    public void 회원목록조회() throws Exception {
        //givin
        List<MemberDto> result = memberService.findAll();
        //when

        //then
        System.out.println("result = " + result.toString());
    }

    @Test
    public void 회원삭제() throws Exception {
        //givin
//        memberService.remove(17L);
        //when

        //then
        assertThat(memberService.findOne(17L).getMemberId()).isEqualTo(17L);
    }

    @Test
    public void 회원수정() throws Exception {
        //givin
        MemberVO findMember = memberService.findOne(17L);
        findMember.updatemember("update_ironMan@naver.com", "update_ironMan", "4321");
        //when
        memberService.updateMember(findMember);
        System.out.println("findMember = " + findMember.toString());
        //then
    }

}

포스팅을 하기 위해 임시로 만든 프로젝트라서 Controller는 일부러 만들지 않았다.

반드시 JUnit으로 단위 테스트 하면서 개발하는 습관을 길러야 한다.

개발속도가 엄청 향상 되므로..

백엔드 먼저 싹 단위테스트 하면서 개발 후 나중에 프론트를 작업하거나 RestFul방식일 경우 API로 만들어서 제공하고 협업하면 된다.

미래에 이 포스팅을 봤을 때 너는 지금보다 2배 이상 더 발전해 있기를 기원한다.

댓글