CRUD 기능 1탄

나성민·2024년 4월 2일

개요

공항관리 시스템을 개발하면서 사용하였던 기본적인 CRUD 구현 개념에 대해서 다루고자 한다. 각각의 기능마다 추가적인 기능이 필요했고 조금씩 구현방식이 다른 부분이 있었지만, 기본적으로 작성했던 큰 틀에 대해서 정리하고자 한다.

구현순서

  • VO : DB의 속성타입에 맞춰서 camelCase로 클래스 생성
  • DAO : CRUD를 포함한 필요한 메서드를 dao 인터페이스에 정의
  • SQL : 위의 dao를 매핑한 xml파일에 sql문 작성하기
  • Service : 마찬가지로 필요한 서비스 로직에 대해서 인터페이스 생성
  • SerivceImpl : 위의 정의한 service를 메서드를 implement
  • Controller : 위에서 작성한 Service를 가져다가 사용자 요청 처리 로직 작성

동작순서

위의 순서대로 구현한 각각의 레이어들은 다음과 같은 순서로 동작된다.

req > controller > service > dao > db > dao > service > controller > res

적용사례

1. VO : DB의 속성타입에 맞춰서 camelCase로 클래스 생성

DTO vs VO vs Entity

위 프로젝트에서는 이를 비교하지 않고 VO로만, 데이터를 주고 받고 처리하였지만, 이는 구분해서 사용할 필요가 있다. DB Layer와 View Layer 사이에서 Entity는 실제 테이블과 매핑되기 때문에 변경하게 되면, 다른 클래스에 영향을 미치고, DTO는 View와 통신하며 자주 변경되기 때문에 분리해줘야 한다.

DTO (Data Transfer Object)

  • 데이터를 전달하기 위한 용도
  • 비즈니스 로직을 가지지 않는 순수한 데이터 객체 (getter/setter)
  • 여러 레이어간 데이터를 주고 받을 때 사용하며 주로 view & controller 사이에서 활용
  • setter가 있으면 가변 객체, 없으면 불변 객체 (불변성 보장) 둘다 가능

VO (Value Object)

  • 값 자체를 표현하는 용도
  • setter() 메서드를 가지지 않는다. (read-only, 불변성 보장)
  • getter()를 포함한 비즈니스 로직을 가질 수 있다.
  • VO는 두 객체의 모든 필드 값들이 동일하면 두 객체는 같다. equals and hashcode 오버라이딩

Entity

실제 DB와 매핑되는 객체로서, 가장 중요한 핵심 클래스라고 할 수 있다.

  • 실제 DB 테이블과 매핑이 되는 클래스
  • 데이터를 전달하는 클래스로 사용하면 안된다.
  • 비즈니스 로직을 포함할 수도, setter() 메서드를 가질 수 있다.

우선 데이터를 주고받고 사용시에는 DTO를 데이터와 매핑이 되는 클래스는 Entity로 사용하면 된다. 이를 통해서 데이터의 정보은닉 및 캡슐화가 가능해지고, 유연하게 데이터를 사용할 수 있게 된다. 이를 적용한다면, 아래와 같은 코드는 데이터와 매핑이 되는 클래스이므로 Entity로 구분하는 것이 올바른 사용법이다.

@Data
@EqualsAndHashCode(of = "flId")
public class flightLogVO {

	@NotBlank(groups = {UpdateGroup.class, DeleteGroup.class})
	private String flId;
	
	@NotBlank
	private String flTitle;
	
	@NotBlank
	private String flContent;
	
	코드 생략...
}

2. DAO : CRUD를 포함한 필요한 메서드를 dao 인터페이스에 정의

DAO (Data Access Object)란, Service와 DB를 연결하는 역할을 하며, 실제로 DB에 접근하여 data를 삽입, 삭제, 조회, 수정 등의 CRUD 기능을 수행한다.

현재 프로젝트에서는 MyBatis가 DAO의 역할을 담당하여 수행한다.

/**
 * 운항일지 관리 퍼시스턴스 레이어
 */
@Mapper
public interface FlightLogDAO {
	/**
	 * 전체 레코드 수 조회
	 * 
	 * @param paging
	 * @return 전체 레코드 수
	 */
	public long selectTotalRecord();

	/**
	 * 전체 운항일지 조회
	 * 
	 * @return 없으면 size = 0
	 */
	public List<flightLogVO> selectList(PaginationInfo paging);

	/**
	 * 특정 운항일지 조회
	 * 
	 * @param flId : 조회할 운항일지 아이디
	 * @return 없으면 null 반환
	 */
	public flightLogVO selectOne(String flId);

	코드 생략...
}

위와 같이 주석을 포함하여 서비스 레이어에서 이용할 때, 명세의 역할을 하게 된다.
또한, @Mapper 어노테이션을 통하여 mybatis가 이를 인식하고, 매핑할 수 있게 된다.

<!-- mapper-context.xml 일부 코드 -->
<mybatis-spring:scan base-package="kr.or.ddit.**.dao"
	annotation="org.apache.ibatis.annotations.Mapper"
	factory-ref="sqlSessionFactory"
/>

3. SQL : 위의 dao를 매핑한 xml파일에 sql문 작성하기

<mapper namespace="kr.or.ddit.operate.flightLog.dao.FlightLogDAO">

	<select id="selectTotalRecord">
		SELECT COUNT(*)
		FROM FLIGHT_LOG
	</select>
	
	<resultMap type="flightLogVO" id="flListMap" autoMapping="true">
		<id property="flId" column="FL_ID" />
		<result property="atchFileId" column="FL_ATCH_FILE" />
		<association property="employee" javaType="EmployeeVO" autoMapping="true" />
	</resultMap>
	
	<select id="selectList" parameterType="PaginationInfo" resultMap="flListMap">
		SELECT B.* 
		FROM (
		    SELECT ROWNUM RNUM, A.*
		    FROM (
		        SELECT 
		            FL_ID, FL_TITLE, FL_CONTENT, FL_CRT_TS, FL_UPD_TS, FL_WRITER, 
		            EMP_NM
		        FROM FLIGHT_LOG INNER JOIN EMPLOYEE ON FL_WRITER = EMP_NO
		        ORDER BY FL_CRT_TS DESC
		    ) A 
		) B
	</select>
    
    코드 생략...

4. Service : 마찬가지로 필요한 서비스 로직에 대해서 인터페이스 생성

인터페이스를 사용하여 Service 및 DAO 레이어를 추상화하면 유연성확장성을 높이며, 다양한 클래스를 만들고 사용할 수 있도록 한다.

/**
 * 운항일지 관리 비즈니스 로직
 */
public interface FlightLogService {
	/**
	 * 전체 운항일지 조회
	 * 
	 * @return 없으면 size = 0
	 */
	public List<flightLogVO> retrieveList(PaginationInfo paging);

	/**
	 * 특정 운항일지 조회
	 * 
	 * @param flId : 조회할 운항일지 아이디
	 * @return 조회된 운항일지
	 * @throws PKNotFoundException : 해당 일지가 없는 경우
	 */
	public flightLogVO retrieveOne(String flId) throws PKNotFoundException;

코드 생략...

5. SerivceImpl : 위의 정의한 service를 메서드를 implement

아래와 같이, service 인터페이스를 구현하고, 필요한 객체들을 주입받아서 실제 서비스를 구현한다.

@Service
public class FlightLogServiceImpl implements FlightLogService {

	@Inject
	private FlightLogDAO fLogDao;

	@Inject
	private AtchFileService atchService;
	
	@Value("#{appInfo.flAtchPath}")
	private Resource atchPath;

	@Override
	public List<flightLogVO> retrieveList(PaginationInfo paging) {
		long totalRecord = fLogDao.selectTotalRecord();
		paging.setTotalRecord(totalRecord);
		return fLogDao.selectList(paging);
	}

6. Controller : 위에서 작성한 Service를 가져다가 사용자 요청 처리 로직 작성

@Controller
@RequestMapping("/operate/flightlog")
public class FlightLogController {
	
	@Inject
	private FlightLogService fLogService;
	
	@ModelAttribute("fLog")
	public flightLogVO flightLog() {
		return new flightLogVO();
	}
	
	@GetMapping("/main.do")
	public String fLogMain() {
		return "operate/flightLog/flightLogMain";
	}
	
	@GetMapping("/list.do")
	public String fLogList(@RequestParam(name = "page", required = false, defaultValue = "1") long currentPage,
			Model model) {
		PaginationInfo<flightLogVO> paging = new PaginationInfo<>();
		paging.setCurrentPage(currentPage);

		List<flightLogVO> fLogList = fLogService.retrieveList(paging);
		paging.setDataList(fLogList);

		model.addAttribute("paging", paging);

		return "operate/flightLog/flightLogList";
	}
    
   코드 생략...

참고

DTO vs VO vs Entity
DAO의 개념 정리
[10분 테코톡] 📍인비의 DTO vs VO

0개의 댓글