IT/development

[springBoot + mybatis] 개발환경 세팅(feat. IntelliJ IDEA 2021.2)

알 수 없는 사용자 2022. 11. 20. 06:54
반응형

목차

    스프링부트로 개발 시 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배 이상 더 발전해 있기를 기원한다.

    반응형