JavaScript에서 ||논리 합(Logical OR) 연산자입니다. 이 연산자는 기본적으로 "둘 중 하나라도 true면 true"라는 역할을 하지만, 실제 코드 작성 시에는 기본값(Default Value) 설정을 위해 아주 빈번하게 사용됩니다.

주요 역할과 작동 원리를 정리해 드립니다.


1. 기본적인 논리 연산

두 개의 피연산자 중 하나만 true여도 결과는 true가 됩니다.

연산 결과
`true  
`true  
`false  
`false  

2. 단축 평가 (Short-circuit Evaluation)

JavaScript의 ||는 왼쪽부터 오른쪽으로 값을 평가하며, 첫 번째로 만나는 truthy한 값(true로 간주되는 값)을 즉시 반환하고 연산을 멈춥니다. 만약 끝까지 truthy한 값이 없다면 마지막 값을 반환합니다.

"사과" || "포도"  // "사과" (첫 번째 값이 truthy이므로 바로 반환)
false || "포도"   // "포도" (첫 번째가 falsy이므로 다음 값인 "포도" 반환)

3. 실무에서의 활용: 기본값 설정

변수에 값이 없을 때(null, undefined, 빈 문자열 등) 대체값을 넣고 싶을 때 가장 많이 쓰입니다.

let userName = ""; // 사용자가 이름을 입력하지 않은 상황 (falsy)
let displayName = userName || "익명사용자";

console.log(displayName); // "익명사용자"

⚠️ 주의할 점 (falsy 값의 함정)

|| 연산자는 0, ""(빈 문자열), false, null, undefined를 모두 falsy로 취급합니다.

만약 "값이 0일 때는 0을 그대로 쓰고 싶고, 오직 null이나 undefined일 때만 기본값을 쓰고 싶다"면, 최근 자바스크립트(ES2020)에서는 || 대신 null 병합 연산자 ??를 사용하는 것이 더 안전합니다.

let count = 0;

// || 를 사용하면 0을 falsy로 봐서 10이 할당됨
let result1 = count || 10; // 10

// ?? 를 사용하면 null/undefined만 체크하므로 0이 그대로 유지됨
let result2 = count ?? 10; // 0

요약하자면:
||"이거 아니면 저거(기본값)"를 선택할 때 사용하는 연산자라고 이해하시면 됩니다.

혹시 블로그 포스팅 중인 if 문 condition 조건에 관하여 글에 이 내용을 추가할 계획이신가요? 이 연산자의 단축 평가 특징을 활용하면 복잡한 if문을 한 줄로 줄이는 로직을 설명하기 좋습니다.

728x90
반응형
상황 분류 구체적인 예시 평가 결과 실행 여부 비고
빈 값 (Empty) if ("") Falsy 미실행 빈 문자열은 데이터가 없는 것으로 간주
  if (null) Falsy 미실행 의도적으로 비어있는 상태
  if (undefined) Falsy 미실행 값이 할당되지 않은 상태
숫자 0 if (0) Falsy 미실행 숫자 0은 JavaScript에서 거짓으로 평가
  if (1) Truthy 실행 0이 아닌 모든 숫자는 참
문자열 if (" ") Truthy 실행 공백이 포함되면 빈 문자열이 아님
  if ("false") Truthy 실행 내용이 있는 문자열은 무조건 참
객체 및 배열 if ([]) Truthy 실행 주의! 빈 배열도 객체이므로 항상 참
  if ({}) Truthy 실행 주의! 빈 객체도 항상 참
특수 케이스 if (NaN) Falsy 미실행 계산 불가능한 숫자는 거짓

 

/**
 * 값이 비어있는지(공백 포함) 확인하는 함수
 */
const isEmpty = (str) => {
  return !str || str.trim().length === 0;
};

const value = "  ";

if (!isEmpty(value)) {
  console.log("의미 있는 텍스트가 있을 때만 실행");
} else {
  console.log("빈 값 또는 공백으로 판단됨"); // 현재 사례에서는 이쪽이 실행됨
}
728x90
반응형

Entity

package kr.go.dfe.tds.domain;

import java.time.LocalDateTime;

/**
 * 평가 운영 영속성 모델
 * - MyBatis 매핑 전용
 * - Getter/Setter만 제공 (비즈니스 로직 없음)
 * - 변환 로직은 MapStruct Mapper에서 처리
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
public class EvaluationOperationEntity {
    
    private Long id;
    private Long evaluationDesignId;
    private String commonCode;
    private String commonCodeDetail;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    private String notice;
    private boolean isConfirmed;

    // 기본 생성자 (MyBatis 필수)
    public EvaluationOperationEntity() {
    }

    // Getter/Setter (MyBatis 매핑용)
    public Long getId() { 
        return id; 
    }
    
    public void setId(Long id) { 
        this.id = id; 
    }

    public Long getEvaluationDesignId() { 
        return evaluationDesignId; 
    }
    
    public void setEvaluationDesignId(Long evaluationDesignId) { 
        this.evaluationDesignId = evaluationDesignId; 
    }

    public String getCommonCode() { 
        return commonCode; 
    }
    
    public void setCommonCode(String commonCode) { 
        this.commonCode = commonCode; 
    }

    public String getCommonCodeDetail() { 
        return commonCodeDetail; 
    }
    
    public void setCommonCodeDetail(String commonCodeDetail) { 
        this.commonCodeDetail = commonCodeDetail; 
    }

    public LocalDateTime getStartDate() { 
        return startDate; 
    }
    
    public void setStartDate(LocalDateTime startDate) { 
        this.startDate = startDate; 
    }

    public LocalDateTime getEndDate() { 
        return endDate; 
    }
    
    public void setEndDate(LocalDateTime endDate) { 
        this.endDate = endDate; 
    }

    public String getNotice() { 
        return notice; 
    }
    
    public void setNotice(String notice) { 
        this.notice = notice; 
    }

    public boolean isConfirmed() { 
        return isConfirmed; 
    }
    
    public void setConfirmed(boolean confirmed) { 
        isConfirmed = confirmed; 
    }
}

 

DTO (Inner Class)

package com.dfe.tds.evaluation_operation.domain.dto;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 평가 운영 DTO (Inner Class 패턴)
 * - Lombok 미사용, 순수 Java
 * - 변환 로직은 MapStruct Mapper에서 처리
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
public class EvaluationOperationDto {

    private EvaluationOperationDto() {} // 인스턴스화 방지

    /**
     * 평가 운영 영속성 모델
     */
    public static class Entity {

        private Long id;
        private Long evaluationDesignId;
        private String commonCode;
        private String commonCodeDetail;
        private LocalDateTime startDate;
        private LocalDateTime endDate;
        private String notice;
        private boolean isConfirmed;

        // 기본 생성자 (MyBatis 필수)
        public Entity() {
        }

        // Getter/Setter (MyBatis 매핑용)
        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Long getEvaluationDesignId() {
            return evaluationDesignId;
        }

        public void setEvaluationDesignId(Long evaluationDesignId) {
            this.evaluationDesignId = evaluationDesignId;
        }

        public String getCommonCode() {
            return commonCode;
        }

        public void setCommonCode(String commonCode) {
            this.commonCode = commonCode;
        }

        public String getCommonCodeDetail() {
            return commonCodeDetail;
        }

        public void setCommonCodeDetail(String commonCodeDetail) {
            this.commonCodeDetail = commonCodeDetail;
        }

        public LocalDateTime getStartDate() {
            return startDate;
        }

        public void setStartDate(LocalDateTime startDate) {
            this.startDate = startDate;
        }

        public LocalDateTime getEndDate() {
            return endDate;
        }

        public void setEndDate(LocalDateTime endDate) {
            this.endDate = endDate;
        }

        public String getNotice() {
            return notice;
        }

        public void setNotice(String notice) {
            this.notice = notice;
        }

        public boolean isConfirmed() {
            return isConfirmed;
        }

        public void setConfirmed(boolean confirmed) {
            isConfirmed = confirmed;
        }
    }

    /**
     * 목록 조회 요청
     */
    public static class ListRequest {
        private Long evaluationDesignId;
        private String commonCode;
        private int page = 1;
        private int size = 20;
        private String sort = "id,desc";

        // Getter/Setter
        public Long getEvaluationDesignId() { return evaluationDesignId; }
        public void setEvaluationDesignId(Long evaluationDesignId) { this.evaluationDesignId = evaluationDesignId; }

        public String getCommonCode() { return commonCode; }
        public void setCommonCode(String commonCode) { this.commonCode = commonCode; }

        public int getPage() { return page; }
        public void setPage(int page) { this.page = page; }

        public int getSize() { return size; }
        public void setSize(int size) { this.size = size; }

        public String getSort() { return sort; }
        public void setSort(String sort) { this.sort = sort; }
    }

    /**
     * 생성 요청
     */
    public static class CreateRequest {
        @NotNull(message = "평가 설계 ID는 필수입니다")
        private Long evaluationDesignId;

        @Size(max = 255, message = "공통 코드는 255자 이내여야 합니다")
        private String commonCode;

        @Size(max = 255, message = "공통 코드 상세는 255자 이내여야 합니다")
        private String commonCodeDetail;

        private LocalDateTime startDate;
        private LocalDateTime endDate;
        private String notice;

        // Getter/Setter
        public Long getEvaluationDesignId() { return evaluationDesignId; }
        public void setEvaluationDesignId(Long evaluationDesignId) { this.evaluationDesignId = evaluationDesignId; }

        public String getCommonCode() { return commonCode; }
        public void setCommonCode(String commonCode) { this.commonCode = commonCode; }

        public String getCommonCodeDetail() { return commonCodeDetail; }
        public void setCommonCodeDetail(String commonCodeDetail) { this.commonCodeDetail = commonCodeDetail; }

        public LocalDateTime getStartDate() { return startDate; }
        public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; }

        public LocalDateTime getEndDate() { return endDate; }
        public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; }

        public String getNotice() { return notice; }
        public void setNotice(String notice) { this.notice = notice; }
    }

    /**
     * 수정 요청
     */
    public static class UpdateRequest {
        @Size(max = 255, message = "공통 코드는 255자 이내여야 합니다")
        private String commonCode;

        @Size(max = 255, message = "공통 코드 상세는 255자 이내여야 합니다")
        private String commonCodeDetail;

        private LocalDateTime startDate;
        private LocalDateTime endDate;
        private String notice;

        // Getter/Setter
        public String getCommonCode() { return commonCode; }
        public void setCommonCode(String commonCode) { this.commonCode = commonCode; }

        public String getCommonCodeDetail() { return commonCodeDetail; }
        public void setCommonCodeDetail(String commonCodeDetail) { this.commonCodeDetail = commonCodeDetail; }

        public LocalDateTime getStartDate() { return startDate; }
        public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; }

        public LocalDateTime getEndDate() { return endDate; }
        public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; }

        public String getNotice() { return notice; }
        public void setNotice(String notice) { this.notice = notice; }
    }

    /**
     * 응답 (읽기 전용)
     */
    public static class Response {
        private final Long id;
        private final Long evaluationDesignId;
        private final String commonCode;
        private final String commonCodeDetail;
        private final LocalDateTime startDate;
        private final LocalDateTime endDate;
        private final String notice;
        private final boolean isConfirmed;

        public Response(Long id, Long evaluationDesignId, String commonCode, String commonCodeDetail,
                        LocalDateTime startDate, LocalDateTime endDate, String notice, boolean isConfirmed) {
            this.id = id;
            this.evaluationDesignId = evaluationDesignId;
            this.commonCode = commonCode;
            this.commonCodeDetail = commonCodeDetail;
            this.startDate = startDate;
            this.endDate = endDate;
            this.notice = notice;
            this.isConfirmed = isConfirmed;
        }

        // Getter만 제공 (Setter 없음 - 불변 객체)
        public Long getId() { return id; }
        public Long getEvaluationDesignId() { return evaluationDesignId; }
        public String getCommonCode() { return commonCode; }
        public String getCommonCodeDetail() { return commonCodeDetail; }
        public LocalDateTime getStartDate() { return startDate; }
        public LocalDateTime getEndDate() { return endDate; }
        public String getNotice() { return notice; }
        public boolean isConfirmed() { return isConfirmed; }
    }

    /**
     * 목록 응답 (페이징 정보 포함)
     */
    public static class ListResponse {
        private final List<Response> evaluationOperations;
        private final int totalCount;
        private final int totalPages;
        private final int currentPage;

        public ListResponse(List<Response> evaluationOperations, int totalCount,
                            int totalPages, int currentPage) {
            this.evaluationOperations = evaluationOperations;
            this.totalCount = totalCount;
            this.totalPages = totalPages;
            this.currentPage = currentPage;
        }

        // Getter만 제공 (Setter 없음 - 불변 객체)
        public List<Response> getEvaluationOperations() { return evaluationOperations; }
        public int getTotalCount() { return totalCount; }
        public int getTotalPages() { return totalPages; }
        public int getCurrentPage() { return currentPage; }
    }
}

 

Map Struct

package com.dfe.tds.evaluation_operation.domain;

import com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto;
import org.mapstruct.Mapper;

/**
 * 평가 운영 매퍼 (MapStruct)
 * - Entity ↔ Domain ↔ DTO 변환 담당
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           생성자 접근 제어자 이슈로 인한 default 메소드 수동 매핑 적용
 */
@Mapper(componentModel = "spring")
public interface EvaluationOperationMapStruct {

    /**
     * Entity → Domain 변환
     * (수동 매핑: 도메인 객체의 Setter가 없고 생성자가 Private이기 때문에 정적 팩토리 메소드 사용)
     */
    default EvaluationOperation entityToDomain(EvaluationOperationDto.Entity entity) {
        if (entity == null) {
            return null;
        }
        return EvaluationOperation.reconstitute(
                entity.getId(),
                entity.getEvaluationDesignId(),
                entity.getCommonCode(),
                entity.getCommonCodeDetail(),
                entity.getStartDate(),
                entity.getEndDate(),
                entity.getNotice(),
                entity.isConfirmed()
        );
    }

    /**
     * Domain → Entity 변환
     * (자동 매핑: Entity 객체는 기본 생성자와 Setter가 존재하므로 MapStruct가 자동 생성)
     */
    EvaluationOperationDto.Entity domainToEntity(EvaluationOperation domain);

    /**
     * DTO(CreateRequest) → Domain 변환
     * (수동 매핑: 클라이언트 요청 정보를 기반으로 신규 도메인 객체 생성)
     */
    default EvaluationOperation createRequestToDomain(EvaluationOperationDto.CreateRequest request) {
        if (request == null) {
            return null;
        }
        return EvaluationOperation.create(
                request.getEvaluationDesignId(),
                request.getCommonCode(),
                request.getCommonCodeDetail(),
                request.getStartDate(),
                request.getEndDate(),
                request.getNotice()
        );
    }

    /**
     * Domain → DTO(Response) 변환
     * (자동 매핑: Response 객체의 모든 필드를 받는 생성자를 통해 MapStruct가 자동 매핑 지원)
     */
    EvaluationOperationDto.Response domainToResponse(EvaluationOperation domain);
}

 

DAO

package com.dfe.tds.evaluation_operation.dao;

import com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto;
import org.egovframe.rte.psl.dataaccess.EgovAbstractMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 평가 운영 DAO
 * - 전자정부 프레임워크 표준(EgovAbstractMapper) 적용
 * - MyBatis XML 매퍼와 연결
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
@Repository("evaluationOperationDao")
public class EvaluationOperationDao extends EgovAbstractMapper {

    /**
     * 평가 운영 단건 조회
     *
     * @param id 평가 운영 ID
     * @return EvaluationOperationEntity 평가 운영 영속성 모델
     */
    public EvaluationOperationDto.Entity selectEvaluationOperation(Long id) {
        return selectOne("evaluationOperationMapper.selectEvaluationOperation", id);
    }

    /**
     * 평가 운영 목록 조회 (페이징 적용)
     *
     * @param request 목록 조회 요청 DTO (검색 조건 및 페이징 정보 포함)
     * @return List<EvaluationOperationEntity> 평가 운영 영속성 모델 목록
     */
    public List<EvaluationOperationDto.Entity> selectEvaluationOperationList(EvaluationOperationDto.ListRequest request) {
        return selectList("evaluationOperationMapper.selectEvaluationOperationList", request);
    }

    /**
     * 평가 운영 목록 총 건수 조회
     *
     * @param request 목록 조회 요청 DTO (검색 조건 포함)
     * @return int 총 건수
     */
    public int selectEvaluationOperationListTotCnt(EvaluationOperationDto.ListRequest request) {
        return selectOne("evaluationOperationMapper.selectEvaluationOperationListTotCnt", request);
    }

    /**
     * 평가 운영 등록
     *
     * @param entity 평가 운영 영속성 모델
     * @return int 등록된 건수
     */
    public int insertEvaluationOperation(EvaluationOperationDto.Entity entity) {
        return insert("evaluationOperationMapper.insertEvaluationOperation", entity);
    }

    /**
     * 평가 운영 수정
     *
     * @param entity 평가 운영 영속성 모델
     * @return int 수정된 건수
     */
    public int updateEvaluationOperation(EvaluationOperationDto.Entity entity) {
        return update("evaluationOperationMapper.updateEvaluationOperation", entity);
    }

    /**
     * 평가 운영 삭제
     *
     * @param id 평가 운영 ID
     * @return int 삭제된 건수
     */
    public int deleteEvaluationOperation(Long id) {
        return delete("evaluationOperationMapper.deleteEvaluationOperation", id);
    }
}

 

XML Mapper

<?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">

<!--
 * @description 평가_운영 MyBatis XML Mapper
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * 2026-02-25    Ayaan           최초 생성
-->
<mapper namespace="evaluationOperationMapper">

    <!-- ResultMap 정의 -->
    <resultMap id="evaluationOperationResult" type="com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto$Entity">
        <id property="id" column="evaluation_operation_id"/>
        <result property="evaluationDesignId" column="evaluation_design_id"/>
        <result property="commonCode" column="common_code"/>
        <result property="commonCodeDetail" column="common_code_detail"/>
        <result property="startDate" column="start_date"/>
        <result property="endDate" column="end_date"/>
        <result property="notice" column="notice"/>
        <result property="isConfirmed" column="is_confirmed"/>
    </resultMap>

    <!-- 공통 검색 조건 (Where 절) -->
    <sql id="evaluationOperationWhere">
        <where>
            <if test="evaluationDesignId != null">
                AND evaluation_design_id = #{evaluationDesignId}
            </if>
            <if test="commonCode != null and commonCode != ''">
                AND common_code = #{commonCode}
            </if>
        </where>
    </sql>

    <!-- 단건 조회 -->
    <select id="selectEvaluationOperation" resultMap="evaluationOperationResult">
        SELECT
            evaluation_operation_id,
            evaluation_design_id,
            common_code,
            common_code_detail,
            start_date,
            end_date,
            notice,
            is_confirmed
        FROM evaluation_operation
        WHERE evaluation_operation_id = #{id}
    </select>

    <!-- 다건 목록 조회 (페이징 적용) -->
    <select id="selectEvaluationOperationList" parameterType="com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto$ListRequest" resultMap="evaluationOperationResult">
        <!-- MyBatis에서 제공하는 bind를 사용하여 offset 계산 (page - 1) * size -->
        <bind name="offset" value="page > 0 ? (page - 1) * size : 0"/>

        SELECT
        evaluation_operation_id,
        evaluation_design_id,
        common_code,
        common_code_detail,
        start_date,
        end_date,
        notice,
        is_confirmed
        FROM evaluation_operation
        <include refid="evaluationOperationWhere"/>
        ORDER BY evaluation_operation_id DESC
        LIMIT #{size} OFFSET #{offset}
    </select>

    <!-- 목록 총 건수 조회 -->
    <select id="selectEvaluationOperationListTotCnt" parameterType="com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto$ListRequest" resultType="int">
        SELECT COUNT(evaluation_operation_id)
        FROM evaluation_operation
        <include refid="evaluationOperationWhere"/>
    </select>

    <!-- 등록 (PK useGeneratedKeys 적용) -->
    <insert id="insertEvaluationOperation" parameterType="com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto$Entity" useGeneratedKeys="true" keyProperty="id" keyColumn="evaluation_operation_id">
        INSERT INTO evaluation_operation (
            evaluation_design_id,
            common_code,
            common_code_detail,
            start_date,
            end_date,
            notice,
            is_confirmed
        ) VALUES (
                     #{evaluationDesignId},
                     #{commonCode},
                     #{commonCodeDetail},
                     #{startDate},
                     #{endDate},
                     #{notice},
                     #{isConfirmed}
                 )
    </insert>

    <!-- 수정 -->
    <update id="updateEvaluationOperation" parameterType="com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto$Entity">
        UPDATE evaluation_operation
        SET evaluation_design_id = #{evaluationDesignId},
            common_code = #{commonCode},
            common_code_detail = #{commonCodeDetail},
            start_date = #{startDate},
            end_date = #{endDate},
            notice = #{notice},
            is_confirmed = #{isConfirmed}
        WHERE evaluation_operation_id = #{id}
    </update>

    <!-- 삭제 -->
    <delete id="deleteEvaluationOperation" parameterType="long">
        DELETE FROM evaluation_operation
        WHERE evaluation_operation_id = #{id}
    </delete>

</mapper>

 

 

 

 

 

 

 

728x90
반응형
package com.dfe.tds.evaluation_operation.domain;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 평가 운영 도메인 모델
 * - Getter만 제공 (Setter 없음)
 * - 정적 팩토리 메소드로 생성
 * - 도메인 메소드로 상태 변경
 * - Lombok 미사용 (순수 Java)
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
public class EvaluationOperation {

    private final Long id;
    private final Long evaluationDesignId;
    private String commonCode;
    private String commonCodeDetail;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    private String notice;
    private boolean isConfirmed;

    // ========================================
    // Private 생성자 (외부 직접 생성 금지)
    // ========================================

    private EvaluationOperation(Long id, Long evaluationDesignId, String commonCode,
                                String commonCodeDetail, LocalDateTime startDate,
                                LocalDateTime endDate, String notice, boolean isConfirmed) {
        this.id = id;
        this.evaluationDesignId = Objects.requireNonNull(evaluationDesignId, "평가 설계 ID는 필수입니다");
        this.commonCode = commonCode;
        this.commonCodeDetail = commonCodeDetail;
        this.startDate = startDate;
        this.endDate = endDate;
        this.notice = notice;
        this.isConfirmed = isConfirmed;

        validatePeriod(this.startDate, this.endDate);
    }

    // ========================================
    // 정적 팩토리 메소드
    // ========================================

    /**
     * 신규 평가 운영 생성
     */
    public static EvaluationOperation create(Long evaluationDesignId, String commonCode,
                                             String commonCodeDetail, LocalDateTime startDate,
                                             LocalDateTime endDate, String notice) {
        return new EvaluationOperation(null, evaluationDesignId, commonCode, commonCodeDetail,
                startDate, endDate, notice, false);
    }

    /**
     * DB에서 조회한 데이터로 복원 (MapStruct 등에서 사용)
     */
    public static EvaluationOperation reconstitute(Long id, Long evaluationDesignId, String commonCode,
                                                   String commonCodeDetail, LocalDateTime startDate,
                                                   LocalDateTime endDate, String notice, boolean isConfirmed) {
        return new EvaluationOperation(id, evaluationDesignId, commonCode, commonCodeDetail,
                startDate, endDate, notice, isConfirmed);
    }

    // ========================================
    // Getter (상태 조회)
    // ========================================

    public Long getId() {
        return id;
    }

    public Long getEvaluationDesignId() {
        return evaluationDesignId;
    }

    public String getCommonCode() {
        return commonCode;
    }

    public String getCommonCodeDetail() {
        return commonCodeDetail;
    }

    public LocalDateTime getStartDate() {
        return startDate;
    }

    public LocalDateTime getEndDate() {
        return endDate;
    }

    public String getNotice() {
        return notice;
    }

    public boolean isConfirmed() {
        return isConfirmed;
    }

    // ========================================
    // 도메인 메소드 (비즈니스 로직)
    // ========================================

    /**
     * 공통 코드 정보 변경
     */
    public void updateCommonCode(String newCommonCode, String newCommonCodeDetail) {
        checkNotConfirmed();
        this.commonCode = newCommonCode;
        this.commonCodeDetail = newCommonCodeDetail;
    }

    /**
     * 운영 기간 변경
     */
    public void updatePeriod(LocalDateTime newStartDate, LocalDateTime newEndDate) {
        checkNotConfirmed();
        validatePeriod(newStartDate, newEndDate);
        this.startDate = newStartDate;
        this.endDate = newEndDate;
    }

    /**
     * 공지 사항 수정
     */
    public void updateNotice(String newNotice) {
        this.notice = newNotice;
    }

    /**
     * 평가 운영 확정 처리
     */
    public void confirm() {
        if (this.isConfirmed) {
            throw new IllegalStateException("이미 확정 처리된 평가 운영입니다.");
        }
        if (this.startDate == null || this.endDate == null) {
            throw new IllegalStateException("운영 기간이 설정되지 않아 확정할 수 없습니다.");
        }
        this.isConfirmed = true;
    }

    /**
     * 평가 운영 확정 취소
     */
    public void cancelConfirmation() {
        if (!this.isConfirmed) {
            throw new IllegalStateException("확정되지 않은 평가 운영입니다.");
        }
        this.isConfirmed = false;
    }

    // ========================================
    // Private 헬퍼 메소드
    // ========================================

    private void validatePeriod(LocalDateTime start, LocalDateTime end) {
        if (start != null && end != null && start.isAfter(end)) {
            throw new IllegalArgumentException("시작 일자가 종료 일자보다 늦을 수 없습니다.");
        }
    }

    private void checkNotConfirmed() {
        if (this.isConfirmed) {
            throw new IllegalStateException("확정된 평가 운영 정보는 수정할 수 없습니다.");
        }
    }
}
728x90
반응형
package com.dfe.tds.evaluation_operation.service;

import com.dfe.tds.evaluation_operation.dao.EvaluationOperationDao;
import com.dfe.tds.evaluation_operation.domain.EvaluationOperation;
import com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto;
import com.dfe.tds.evaluation_operation.domain.EvaluationOperationMapStruct;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 평가 운영 서비스
 * - DDD 구조: 비즈니스 로직은 도메인 모델에 위임하고 흐름만 제어
 * - Lombok 미사용 (명시적 생성자 주입)
 * - 메소드 레벨 @Transactional 적용
 * - 변환 로직은 MapStruct Mapper 사용
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
@Service
public class EvaluationOperationService {

    private final EvaluationOperationDao evaluationOperationDao;
    private final EvaluationOperationMapStruct evaluationOperationMapStruct;
    private final ApplicationEventPublisher eventPublisher;

    public EvaluationOperationService(EvaluationOperationDao evaluationOperationDao,
                                      EvaluationOperationMapStruct evaluationOperationMapStruct,
                                      ApplicationEventPublisher eventPublisher) {
        this.evaluationOperationDao = evaluationOperationDao;
        this.evaluationOperationMapStruct = evaluationOperationMapStruct;
        this.eventPublisher = eventPublisher;
    }

    /**
     * 평가 운영 등록
     *
     * @param request 평가 운영 생성 요청 DTO
     * @return Long 생성된 평가 운영 ID
     */
    @Transactional
    public Long createEvaluationOperation(EvaluationOperationDto.CreateRequest request) {
        // 1. DTO -> Domain 변환 (MapStruct)
        EvaluationOperation domain = evaluationOperationMapStruct.createRequestToDomain(request);

        // 2. Domain -> Entity 변환 및 저장
        EvaluationOperationDto.Entity entity = evaluationOperationMapStruct.domainToEntity(domain);
        evaluationOperationDao.insertEvaluationOperation(entity);

        return entity.getId();
    }

    /**
     * 평가 운영 정보 수정
     *
     * @param id 평가 운영 ID
     * @param request 평가 운영 수정 요청 DTO
     */
    public void updateEvaluationOperation(Long id, EvaluationOperationDto.UpdateRequest request) {
        // 1. Entity 조회 및 Domain 변환
        EvaluationOperationDto.Entity entity = evaluationOperationDao.selectEvaluationOperation(id);
        if (entity == null) {
            throw new IllegalArgumentException("존재하지 않는 평가 운영 정보입니다. ID: " + id);
        }
        EvaluationOperation domain = evaluationOperationMapStruct.entityToDomain(entity);

        // 2. 도메인 로직을 통한 상태 변경
        if (request.getCommonCode() != null || request.getCommonCodeDetail() != null) {
            domain.updateCommonCode(request.getCommonCode(), request.getCommonCodeDetail());
        }
        if (request.getStartDate() != null && request.getEndDate() != null) {
            domain.updatePeriod(request.getStartDate(), request.getEndDate());
        }
        if (request.getNotice() != null) {
            domain.updateNotice(request.getNotice());
        }

        // 3. Entity 변환 후 반영
        EvaluationOperationDto.Entity updatedEntity = evaluationOperationMapStruct.domainToEntity(domain);
        evaluationOperationDao.updateEvaluationOperation(updatedEntity);
    }

    /**
     * 평가 운영 확정 처리
     *
     * @param id 평가 운영 ID
     */
    public void confirmEvaluationOperation(Long id) {
        // 1. Entity 조회 및 Domain 변환
        EvaluationOperationDto.Entity entity = evaluationOperationDao.selectEvaluationOperation(id);
        if (entity == null) {
            throw new IllegalArgumentException("존재하지 않는 평가 운영 정보입니다. ID: " + id);
        }
        EvaluationOperation domain = evaluationOperationMapStruct.entityToDomain(entity);

        // 2. 비즈니스 로직 실행 (확정 처리)
        domain.confirm();

        // 3. 상태 변경 반영
        EvaluationOperationDto.Entity updatedEntity = evaluationOperationMapStruct.domainToEntity(domain);
        evaluationOperationDao.updateEvaluationOperation(updatedEntity);

        // 필요시 Event 발행 로직 추가
        // eventPublisher.publishEvent(new EvaluationOperationConfirmedEvent(id));
    }

    /**
     * 평가 운영 단건 조회
     *
     * @param id 평가 운영 ID
     * @return EvaluationOperationDto.Response 읽기 전용 DTO
     */
    @Transactional(readOnly = true)
    public EvaluationOperationDto.Response getEvaluationOperation(Long id) {
        EvaluationOperationDto.Entity entity = evaluationOperationDao.selectEvaluationOperation(id);
        if (entity == null) {
            throw new IllegalArgumentException("존재하지 않는 평가 운영 정보입니다. ID: " + id);
        }

        EvaluationOperation domain = evaluationOperationMapStruct.entityToDomain(entity);
        return evaluationOperationMapStruct.domainToResponse(domain);
    }

    /**
     * 평가 운영 목록 조회 (페이징 포함)
     *
     * @param request 목록 조회 요청 DTO
     * @return EvaluationOperationDto.ListResponse 페이징 처리된 목록 응답 DTO
     */
    @Transactional(readOnly = true)
    public EvaluationOperationDto.ListResponse getEvaluationOperationList(EvaluationOperationDto.ListRequest request) {
        // 1. 총 건수 조회 및 페이지 계산
        int totalCount = evaluationOperationDao.selectEvaluationOperationListTotCnt(request);
        int totalPages = totalCount > 0 ? (int) Math.ceil((double) totalCount / request.getSize()) : 0;

        // 2. 목록 조회
        List<EvaluationOperationDto.Entity> entities = evaluationOperationDao.selectEvaluationOperationList(request);

        // 3. Entity -> Domain -> Response DTO 변환
        List<EvaluationOperationDto.Response> responses = entities.stream()
                .map(evaluationOperationMapStruct::entityToDomain)
                .map(evaluationOperationMapStruct::domainToResponse)
                .collect(Collectors.toList());

        // 4. 응답 객체 반환
        return new EvaluationOperationDto.ListResponse(
                responses,
                totalCount,
                totalPages,
                request.getPage()
        );
    }

    /**
     * 평가 운영 삭제
     *
     * @param id 평가 운영 ID
     */
    public void deleteEvaluationOperation(Long id) {
        EvaluationOperationDto.Entity entity = evaluationOperationDao.selectEvaluationOperation(id);
        if (entity == null) {
            throw new IllegalArgumentException("존재하지 않는 평가 운영 정보입니다. ID: " + id);
        }

        // 삭제 전 도메인 검증이 필요할 경우 추가 가능 (예: 확정된 내역은 삭제 불가 등)
        EvaluationOperation domain = evaluationOperationMapStruct.entityToDomain(entity);
        if (domain.isConfirmed()) {
            throw new IllegalStateException("이미 확정된 평가 운영 정보는 삭제할 수 없습니다.");
        }

        evaluationOperationDao.deleteEvaluationOperation(id);
    }
}
728x90
반응형
package com.dfe.tds.evaluation_operation.controller;

import com.dfe.tds.evaluation_operation.domain.dto.EvaluationOperationDto;
import com.dfe.tds.evaluation_operation.service.EvaluationOperationService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * 평가 운영 컨트롤러
 * - RESTful API 제공
 * - Lombok 미사용 (명시적 생성자 주입)
 *
 * @author Ayaan
 * @version 1.0
 * @since 2026-02-25
 *
 * << 개정이력(Modification Information) >>
 *
 * 수정일          수정자          수정내용
 * -----------   --------------  ----------------------------
 * 2026-02-25    Ayaan           최초 생성
 */
@RestController
@RequestMapping("/api/v1/evaluation-operations")
public class EvaluationOperationController {

    private final EvaluationOperationService evaluationOperationService;

    public EvaluationOperationController(EvaluationOperationService evaluationOperationService) {
        this.evaluationOperationService = evaluationOperationService;
    }

    /**
     * 평가 운영 등록
     *
     * @param request 평가 운영 생성 요청 DTO
     * @return 생성된 평가 운영 ID
     */
    @PostMapping
    public ResponseEntity<Long> createEvaluationOperation(
            @Valid @RequestBody EvaluationOperationDto.CreateRequest request) {
        Long createdId = evaluationOperationService.createEvaluationOperation(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdId);
    }

    /**
     * 평가 운영 목록 조회 (페이징)
     *
     * @param request 목록 조회 요청 파라미터 DTO
     * @return 평가 운영 목록 응답 DTO
     */
    @GetMapping
    public ResponseEntity<EvaluationOperationDto.ListResponse> getEvaluationOperationList(
            @ModelAttribute @Valid EvaluationOperationDto.ListRequest request) {
        EvaluationOperationDto.ListResponse response = evaluationOperationService.getEvaluationOperationList(request);
        return ResponseEntity.ok(response);
    }

    /**
     * 평가 운영 단건 조회
     *
     * @param id 평가 운영 ID
     * @return 평가 운영 상세 응답 DTO
     */
    @GetMapping("/{id}")
    public ResponseEntity<EvaluationOperationDto.Response> getEvaluationOperation(
            @PathVariable Long id) {
        EvaluationOperationDto.Response response = evaluationOperationService.getEvaluationOperation(id);
        return ResponseEntity.ok(response);
    }

    /**
     * 평가 운영 정보 수정
     *
     * @param id 평가 운영 ID
     * @param request 평가 운영 수정 요청 DTO
     * @return 처리 결과 (상태 코드 200 OK)
     */
    @PutMapping("/{id}")
    public ResponseEntity<Void> updateEvaluationOperation(
            @PathVariable Long id,
            @Valid @RequestBody EvaluationOperationDto.UpdateRequest request) {
        evaluationOperationService.updateEvaluationOperation(id, request);
        return ResponseEntity.ok().build();
    }

    /**
     * 평가 운영 확정 처리
     *
     * @param id 평가 운영 ID
     * @return 처리 결과 (상태 코드 200 OK)
     */
    @PostMapping("/{id}/confirm")
    public ResponseEntity<Void> confirmEvaluationOperation(
            @PathVariable Long id) {
        evaluationOperationService.confirmEvaluationOperation(id);
        return ResponseEntity.ok().build();
    }

    /**
     * 평가 운영 삭제
     *
     * @param id 평가 운영 ID
     * @return 처리 결과 (상태 코드 204 No Content)
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteEvaluationOperation(
            @PathVariable Long id) {
        evaluationOperationService.deleteEvaluationOperation(id);
        return ResponseEntity.noContent().build();
    }
}
728x90
반응형

1. DDD란 무엇인가?

DDD(도메인 주도 설계)는 소프트웨어의 복잡성을 다루기 위해 비즈니스 도메인(핵심 업무 영역)을 중심으로 소프트웨어를 모델링하고 설계하는 접근 방식입니다. 에릭 에반스(Eric Evans)가 제안했으며, 기술적인 복잡성보다 '해결하고자 하는 비즈니스 문제'에 집중하는 것이 핵심입니다.

 

2. 레이어드 아키텍처 (Layered Architecture) 적용

DDD는 도메인 모델을 기술적 인프라로부터 격리하기 위해 보통 다음과 같은 계층 구조를 가집니다. 앞서 작성하신 템플릿이 이 구조를 완벽하게 따르고 있습니다.

Presentation Layer (Controller)
사용자의 요청을 해석하고 응답을 반환합니다. DTO를 사용하여 도메인 객체가 외부로 노출되는 것을 막습니다.

Application Layer (Service)
도메인 객체들을 지휘하여 비즈니스 유스케이스(흐름)를 구현합니다. 트랜잭션 관리와 권한 검사 등을 수행하며, 도메인 로직을 직접 포함하지 않습니다.

Domain Layer (Domain Model - POJO)
시스템의 심장입니다. 순수 Java 객체(POJO)로 작성되며, 프레임워크나 DB(MyBatis, JPA 등)에 의존하지 않고 핵심 비즈니스 규칙만 포함합니다. Lombok을 배제하고 불변성을 유지하는 것이 매우 좋은 접근입니다.

Infrastructure Layer (DAO, Entity, Mapper)
DB 접근(MyBatis _SQL.xml), 외부 API 호출 등을 담당합니다.

참고: 영속성 객체(Persistence Model, DB 테이블과 1:1)와 도메인 모델을 분리하고, MapStruct로 변환하는 것은 도메인의 순수성을 지키는 매우 훌륭한 패턴입니다.

 

3. 패키지 구조

kr.go.dfe.tds/
├── controller/
│   └── LessonController.java
├── service/
│   └── LessonService.java
├── domain/
│   ├── Lesson.java              # Domain Model
│   ├── LessonEntity.java        # Persistence Model
│   └── dto/
│       └── LessonDto.java       # DTO (inner classes)
├── dao/
│   └── LessonDao.java           # EgovAbstractMapper 상속
└── resources/
    └── mapper/
        └── tds/
            └── Lesson_SQL.xml

728x90
반응형
명령어 단축 명령어 설명
npm init - 현재 폴더에 package.json 설정 파일을 생성하여 새 프로젝트를 시작합니다.
npm install npm i package.json에 기록된 모든 의존성 라이브러리를 한 번에 설치합니다. (남의 코드를 받아 실행할 때 필수)
npm install [이름] npm i [이름] 특정 라이브러리(패키지)를 다운로드하여 프로젝트에 추가합니다.
npm start - 프로젝트를 **실행(구동)**합니다. (주로 개발 서버를 켤 때 사용)
npm run [명령어] - package.json의 scripts 항목에 정의된 커스텀 명령(예: build, test)을 실행합니다.
npm uninstall [이름] npm un [이름] 설치된 패키지를 삭제하고 설정 파일에서도 제거합니다.
npm update - 설치된 패키지들을 허용된 범위 내에서 최신 버전으로 업데이트합니다.

 

구분 명령어 / 문법 설명
프로젝트 생성 npx create-react-app [앱이름] 리액트 개발 환경을 자동으로 설정하고 프로젝트를 생성합니다. (터미널 명령어)
728x90
반응형
// dependencies
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
728x90
반응형
// dependencies
implementation 'org.springframework.boot:spring-boot-starter-websocket'

 

728x90
반응형

+ Recent posts