본문 바로가기
Project/MangoPlate Clone

MyBatis Setting 및 Test

by 혀눅짱 2023. 2. 14.

프로퍼티 설정이 마무리되고 깡통프로젝트가 배포되는것을 확인한 후  myBatis 세팅을 해보았다.

사실 myBatis는 현재 실무에서 사용하고있는 기술이라 공부하기 위한 프로젝트에서는 안쓰는게 맞지 않나란 생각을 했는데

강의만 들어본 jpa만 활용하여 완성하기엔 어려움이 있다고 판단하여 jpa와 myBatis를 같이 사용하기로 하였다.

(물론 같은 비즈니스 로직에 두개의 패러다임을 모두 사용하는건 지양할거다.. jpa의 영속성 컨텍스트 특성상 즉발로 쿼리가 나가는게 아니여서 꼬이면 머리아플거 같다..)

 

<?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>
    <settings>
        <!-- 카멜 케이스 VO 매핑 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

        <!-- 쿼리 결과 필드가 null인 경우, 누락이 되서 나오지 않게 설정-->
        <setting name="callSettersOnNulls" value="true"/>

        <!-- 쿼리에 보내는 파라미터가 null인 경우, 오류가 발생하는 것 방지 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
    <typeAliases>
        <typeAlias alias="MyBatisTestDto" type="com.project.BingoApi.mybatis.dto.MyBatisTestDto"/>
        <typeAlias alias="MbImageRestaurantDto" type="com.project.BingoApi.mybatis.dto.MbImageRestaurantDto"/>
        <typeAlias alias="MbRestaurantDto" type="com.project.BingoApi.mybatis.dto.MbRestaurantDto"/>
        <typeAlias alias="MbRegionDto" type="com.project.BingoApi.mybatis.dto.MbRegionDto"/>
    </typeAliases>

</configuration>

 

myBatis-config.xml 파일이다.

보통 디비 컬럼은 스네이크문 자바변수는 카멜문이라 둘이 호환되도록 카멜케이스의 변수에 자동매핑되도록 설정하였다.

그리고 alias설정도 진행하였다 저거 안하면 각각 sql mapping xml 파일의 resultType이나 parameterType에 해당 데이터 전달객체의 풀패키지 경로를 써야해서 되게 귀찮다. 현재 근무하고있는 프로젝트에서는 alias 설정이 없어서 풀패지키를 적는데 조금 지저분해보인다.

 

참고로 config파일의 경로를 설정과 sql mapping xml 경로설정은  저번 시간 properties세팅에서 진행하였다.

 

##myBatis##
mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
mybatis.config-location=classpath:mybatis/config/mybatis-config.xml

 

설정완료 후  러프하게 설계된 테이블 기반으로  리뷰개수,평점,지역별 상위 6개의 음식점을 select 하는 api를 만들어보았다.

 

컨트롤러

@RestController
@RequiredArgsConstructor
public class MbRestaurantController {

    private final MbRestaurantService mbRestaurantService;

    @RequestMapping("mainList")
    public HashMap<String,Object> mainList() throws  Exception{
        return mbRestaurantService.getMainList();
    }
}

필자가 구현하는 서버는 API이기 때문에 데이터를 json으로 응답할뿐 뷰를 리턴하지 않는다.

그렇기 때문에 @RestController 어노테이션을 사용하였다.

또한 Spring DI 의존성 주입방식은 생성자 주입으로 통일하였다.

근데 첨부된 화면에서 보이는 소스에는 생성자도 없고 의존성주입에 필요한 Autowired 어노테이션도 없다.

@RequiredArgsConstructor 어노테이션은 초기화가 필요한 final 변수들을 아규먼트로 생성자를 만들어준다.

여기서 먼저 생성자가 안보이는 이유는 납득이된다. 또한  현재 진행중인 스프링부트 프로젝트에서는 생성자가 하나면 @Autowired를 생략가능하다. jpa든 myBatis는 의존성주입방식은 앞으로 이방식으로 통일할 생각이다.

 

 

서비스

@Service
@RequiredArgsConstructor
public class MbRestaurantService {

    private  final MbRestaurantMapper mbRestaurantMapper;


    public HashMap<String,Object> getMainList(){
        HashMap<String,Object> result = new HashMap<>();
        result.put("topAvg",mbRestaurantMapper.getTopAvgRestaurantList());
        result.put("topCount",mbRestaurantMapper.getTopCntRestaurantList());
        result.put("topRegion",mbRestaurantMapper.getTopRegionList());
        return result;
    }
}

 

 

매퍼

@Mapper
public interface MbRestaurantMapper {

    public List<MbRestaurantDto> getTopAvgRestaurantList();

    public List<MbRestaurantDto> getTopCntRestaurantList();

    public List<MbRegionDto> getTopRegionList();
}

 

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="com.project.BingoApi.mybatis.mapper.MbRestaurantMapper">

    <select id="getTopAvgRestaurantList" resultMap="getTopAvgResult" >
        SELECT  R.*,
                RV.AVG_RATING,
                RV.CNT,
                IR.image_restaurant_id,
                IR.image_url,
                IR.image_key,
                RG.REGION_ID,
                RG.REGION_NAME
        FROM RESTAURANT R
        INNER JOIN (SELECT
                        ROUND(AVG(RATING),1) AVG_RATING,
                        COUNT(RATING) CNT,
                        RESTAURANT_ID
                    FROM REVIEW
                    GROUP BY RESTAURANT_ID
                    LIMIT 6) RV
        ON (R.RESTAURANT_ID = RV.RESTAURANT_ID)
        LEFT JOIN IMAGE_RESTAURANT IR
        ON R.RESTAURANT_ID = IR.RESTAURANT_ID
        LEFT JOIN REGION RG
        ON R.REGION_ID = RG.REGION_ID
        ORDER BY RV.AVG_RATING DESC

    </select>

    <resultMap id="getTopAvgResult" type="MbRestaurantDto">
        <id column="restaurant_id" property="restaurantId"/>
        <result column="lms_key" property="lmsKey"/>
        <result column="name" property="name"/>
        <result column="address" property="address"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="latitude" property="latitude"/>
        <result column="longitude" property="longitude"/>
        <result column="open_time" property="openTime"/>
        <result column="avg_rating" property="avgRating"/>
        <result column="cnt" property="cnt"/>
        <association property="region" javaType="MbRegionDto">
            <id column="region_id" property="regionId"/>
            <result column="region_name" property="regionName"/>
        </association>

        <collection property="imagesRestaurants" ofType="MbImageRestaurantDto" javaType="java.util.List">
            <id column="image_restaurant_id" property="imageRestaurantId"/>
            <result column="image_url" property="imageUrl"/>
            <result column="image_key" property="imageKey"/>
        </collection>

    </resultMap>



    <select id="getTopCntRestaurantList" resultMap="getTopCntResult" >
        SELECT  R.*,
                RV.CNT,
                RV.AVG_RATING,
                IR.image_restaurant_id,
                IR.image_url,
                IR.image_key,
                RG.REGION_ID,
                RG.REGION_NAME
        FROM RESTAURANT R
        INNER JOIN  (SELECT
                    ROUND(AVG(RATING),1) AVG_RATING,
                    COUNT(RATING) CNT,
                    RESTAURANT_ID
                    FROM REVIEW
                    GROUP BY RESTAURANT_ID
                    LIMIT 6) RV
        ON (R.RESTAURANT_ID = RV.RESTAURANT_ID)
        LEFT JOIN IMAGE_RESTAURANT IR
        ON R.RESTAURANT_ID = IR.RESTAURANT_ID
        LEFT JOIN REGION RG
        ON R.REGION_ID = RG.REGION_ID
        ORDER BY RV.CNT DESC

    </select>

    <resultMap id="getTopCntResult" type="MbRestaurantDto">
        <id column="restaurant_id" property="restaurantId"/>
        <result column="lms_key" property="lmsKey"/>
        <result column="name" property="name"/>
        <result column="address" property="address"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="latitude" property="latitude"/>
        <result column="longitude" property="longitude"/>
        <result column="open_time" property="openTime"/>
        <result column="cnt" property="cnt"/>
        <result column="avg_rating" property="avgRating"/>

        <association property="region" javaType="MbRegionDto">
            <id column="region_id" property="regionId"/>
            <result column="region_name" property="regionName"/>
        </association>

        <collection property="imagesRestaurants" ofType="MbImageRestaurantDto" javaType="java.util.List">
            <id column="image_restaurant_id" property="imageRestaurantId"/>
            <result column="image_url" property="imageUrl"/>
            <result column="image_key" property="imageKey"/>
        </collection>

    </resultMap>


    <select id="getTopRegionList" resultType="MbRegionDto">
        SELECT RG.REGION_ID,
                RG.REGION_NAME,
                RG.REGION_IMAGE_URL
        FROM REGION RG
        INNER JOIN (SELECT
                        REGION_ID,
                        COUNT(REGION_ID) CNT
                        FROM RESTAURANT
                        GROUP BY REGION_ID) R
        ON RG.REGION_ID  = R.REGION_ID
        ORDER BY CNT DESC
        LIMIT 6
    </select>
</mapper>

 

데이터 전달 객체

 

package com.project.BingoApi.mybatis.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.project.BingoApi.jpa.dto.ImageRestaurantDto;
import lombok.Data;

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

@Data
@JsonInclude(JsonInclude.Include.NON_NULL) // Null 값인 필드 제외
public class MbRestaurantDto {

    private Long restaurantId;

    private String lmsKey;

    private String name;

    private String address;

    private String phoneNumber;

    private String latitude;

    private String longitude;

    private String openTime;

    //리뷰평균평점
    private Double avgRating;

    //리뷰수
    private Integer cnt;

    private MbRegionDto region;

    private List<MbImageRestaurantDto> imagesRestaurants = new ArrayList<>();


}

 

xml에서 리뷰평점순과 갯수순은 쿼리가 거의 동일하여 myBatis의 if문을 활용해서 파라미터로 구분하여 동적쿼리를 만들어 쿼리 하나로 이용해도 된다. 이건 필자와 같이하는 개발자가 추후에 변경할거 같고 필자는 앞으로 jpa에 신경을 더쓸거같다. 

망고플레이트 특성상 식당테이블을 중심으로 조인해서 기타 테이블정보를 가져오는 구조가 많을 거같은데 이거 jpa로하면 myBatis보단

select 로직이 조금 번거롭긴 할 거같네..

'Project > MangoPlate Clone' 카테고리의 다른 글

공통컴포넌트 및 유틸  (0) 2023.02.14
JPA Setting 및 Test  (0) 2023.02.14
Project Detail Setting  (0) 2023.02.14
Project Setting  (0) 2023.02.14
MangoPlate Clone Project  (0) 2023.02.14