2025. 1. 17. 00:11ㆍ개발/🦉 뀨업
기존 뀨업 서비스의 코드는 JDBC 기반이었다...
게다가 서비스 인터페이스 정의 안 하고 냅다 서비스 구현체로 코딩했다...
그래서 기능에 대한 수정 회의가 있을 때마다 거의 모든 클래스들 들락날락하면서 코드를 수정했다...
이대론 안 되겠다 싶어서
전체적으로 코드 리팩토링을 진행했다!
🔧 리팩토링 방안과 이유
1. MyBatis로 마이그레이션
SQL 쿼리들이 이리저리 흩어져 있고, 커넥션 관리 때문에 코드가 잔뜩 길어져서 수정하기가 빡세다
-> 도메인별 XML 파일에 SQL문 한꺼번에 관리 가능
-> MyBatis의 SQLSession이 커넥션 관리를 대신 해줌
2. Service Interface 작성
기능 구현 방법이 조금씩 계속 수정되고 있기 때문에
필요한 역할들을 Service Interface에 정의해놓고
구현체들을 만들어나가야겠다고 생각했당
[ MyBatis Migration ]
Dependency 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.mybatis.dynamic-sql:mybatis-dynamic-sql:1.5.2'
SQL문 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="ggyuel.ggyuup.mainPage.mapper.MainMapper">
<select id="selectEwhaInfo" resultType="ggyuel.ggyuup.organization.domain.Organizations">
SELECT ranking, group_name, solved_num
FROM organizations
WHERE group_name = #{groupName}
</select>
<select id="selectRivalInfo" resultType="ggyuel.ggyuup.organization.domain.Organizations">
SELECT ranking, group_name, solved_num
FROM organizations
WHERE ranking = ((SELECT ranking FROM organizations WHERE group_name = #{groupName}) - 1)
</select>
<select id="selectTodayPs" resultType="ggyuel.ggyuup.problem.domain.Problems">
SELECT p.problem_id, p.title, p.link, p.tier, p.solved_num
FROM todayps tp
JOIN problems p ON tp.problem_id = p.problem_id
</select>
</mapper>
- MyBatis 관련 설정
- Mapper Interface 위치를 namespace에 저장
- id에 Mapper Interface에 정의한 메서드 이름 저장
- resultType에 반환값 타입 저장
- SQL문 작성
🚨 주의 사항
1. Mapper Interface 위치 올바르게 명시
대부분 src > main > java 아래부터 path 작성하면 됨
2. id값이 Mapper Interface에 정의한 메서드 이름과 동일해야 함
3. returnType과 SQL문 실행 후 반환값이 일치해야 함
Mapper Interface 작성
@Mapper
public interface MainMapper {
Optional<Organizations> selectEwhaInfo(@Param("groupName") String groupName);
Optional<Organizations> selectRivalInfo(@Param("groupName") String groupName);
List<Problems> selectTodayPs();
}
- @Mapper 를 통해 XML 파일과 연결
Repository 작성
@Repository
@RequiredArgsConstructor
public class MainRepository {
private final MainMapper mainMapper;
public Optional<Organizations> selectEwhaInfo(String groupName) { return mainMapper.selectEwhaInfo(groupName); }
public Optional<Organizations> selectRivalInfo(String groupName) { return mainMapper.selectRivalInfo(groupName); }
public List<Problems> selectTodayPs() { return mainMapper.selectTodayPs(); }
}
- Mapper Interface의 구현체 역할
application.yml에 MyBatis 관련 설정
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
- mapper-locations를 통해서 XML 파일의 위치 정의
[ Service Interface ]
MemberService Interface
public interface MemberService {
Boolean checkEwhain(String request);
}
- checkEwhain : 이화인인지 확인
ProblemService Interface
public interface ProblemService {
List<ProblemAlgoRespDTO> getProblemsByAlgo(String algo);
List<ProblemTierRespDTO> getProblemsByTier(int tier);
}
- getProblemsByAlgo : 알고리즘별로 문제 검색
- getProblemsByTier : 티어별로 문제 검색
MainPageService Interface
public interface MainPageService {
Optional<Organizations> getEwhaInfo();
Optional<Organizations> getRivalInfo();
GroupInfoRespDTO getGroupInfo();
ArrayList<TodayPsRespDTO> getTodayPS();
MainPageRespDTO getMainPage();
}
- getEwhaInfo : 이화여대 정보(백준 순위, 푼 문제 수) 조회
- getRivalInfo : 라이벌 그룹 정보(백준 순위, 푼 문제 수, 그룹 이름) 조회
- getGroupInfo : 라이벌 그룹과의 푼 문제 수 차이 등등 조회
- getTodayPS : 오늘의 문제 조회
- getMainPage : 메인 페이지에 들어갈 정보(라이벌 그룹 정보, 오늘의 문제) 조회
💣 트러블슈팅
분명 XML 파일에 Mapper 위치도 잘 작성하고
returnType이랑 반환값 매칭도 알맞게 해놨고
application.yml에 XML 파일들 위치도 틀림 없이 작성했는데
아래와 같은 Binding Error가 발생했다...
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): ggyuel.ggyuup.mainPage.mapper.MainMapper.selectEwhaInfo
몇번이고 재확인해봐도 도저히 문제가 될 부분이 없어 보였는데...
하... 세상에나...
application.yml 파일의 공백이 문제여따..
아래처럼 spring 아래에 mybatis 관련 설정을 해놨다 보니까
mybatis 설정을 아예 인식을 못해서 XML 파일을 못 찾았었나부다...
spring:
datasource:
url: jdbc:mysql://localhost:3306/ggyuup
username: root
password: number5598
driver-class-name: com.mysql.cj.jdbc.Driver
sql:
init:
mode: never
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
이렇게 수정했더니 해결됐다 휴우..
아아악
spring:
datasource:
url: jdbc:mysql://localhost:3306/ggyuup
username: root
password: number5598
driver-class-name: com.mysql.cj.jdbc.Driver
sql:
init:
mode: never
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
+ ) Swagger도 붙였음
1 ) Dependency 추가
// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
2 ) Controller class에 설정 추가 - @Tag @Operation
package ggyuel.ggyuup.problem.controller;
import ggyuel.ggyuup.dataCrawling.service.DataCrawlingService;
import ggyuel.ggyuup.problem.dto.ProblemAlgoRespDTO;
import ggyuel.ggyuup.problem.dto.ProblemTierRespDTO;
import ggyuel.ggyuup.problem.service.ProblemService;
import ggyuel.ggyuup.global.apiResponse.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/problems")
@Tag(name = "Problem", description = "Problem API")
public class ProblemController {
private final ProblemService problemService;
private final DataCrawlingService dataCrawlingService;
@GetMapping("/algo")
@Operation(summary = "알고리즘별 문제 검색", description = "알고리즘별로 문제 정렬")
public ApiResponse<List<ProblemAlgoRespDTO>> getProblemAlgo(@RequestParam("tag") String algo) {
List<ProblemAlgoRespDTO> problemAlgoRespDTOList = problemService.getProblemsByAlgo(algo);
return ApiResponse.onSuccess(problemAlgoRespDTOList);
}
@GetMapping("/tier")
@Operation(summary = "티어별 문제 검색", description = "티어별로 문제 정렬")
public ApiResponse<List<ProblemTierRespDTO>> getProblemTier(@RequestParam("tier") int tier) {
List<ProblemTierRespDTO> problemTierRespDTOList = problemService.getProblemsByTier(tier);
return ApiResponse.onSuccess(problemTierRespDTOList);
}
@GetMapping("/refresh")
@Operation(summary = "문제 리프레시", description = "리프레시 버튼 눌렀을 때 문제 리프레시")
public void refreshProblems(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(Cookie cookie : cookies){
if(cookie.getName().equals("handle")){
dataCrawlingService.userRefresh(cookie.getValue());
}
}
}
}
}
'개발 > 🦉 뀨업' 카테고리의 다른 글
🦉 뀨업 - 이화 백준 사이트 리팩토링 ( 6 ) - CORS가 또... (0) | 2025.03.01 |
---|---|
🦉 뀨업 - 이화 백준 사이트 리팩토링 ( 5 ) - CORS, 쿠키랑 싸우기 (0) | 2025.02.01 |
🦉 뀨업 - 이화 백준 사이트 리팩토링 ( 3 ) (0) | 2025.01.04 |
🦉 뀨업 - 이화 백준 사이트 리팩토링 ( 2 ) (0) | 2024.09.15 |
🦉 뀨업 - 이화 백준 사이트 리팩토링 ( 1 ) (0) | 2024.09.01 |