package your.package.name;

import java.io.File;
import java.io.FileOutputStream;
import java.util.*;

<생략>

@BxmBean
@Scope("ZC001007C01")
@BxmCategory(type = "Bean", logicalName = "고객정보 엑셀생성", description = "고객 보증정보를 엑셀로 추출")
public class ZC001007C01 implements
        ItemReader<ZC001001C0307010>,
        ItemProcessor<ZC001001C0307010, ZC001001C0307010>,
        ItemWriter<ZC001001C0307010>,
        ItemStream {

    final Logger logger = LoggerFactory.getLogger(this.getClass());

    private DACIT10001 dacCIT10001;
    private Iterator<ZC001001C0307010> iterator;

    private int fetchCnt = 0;
    private int writeCnt = 0;
    private int failCnt = 0;

    private Workbook workbook;
    private Sheet sheet;
    private int rowNum = 0;

    private static final String SAVE_DIR = "/Attach/epower/batch/BR/ZBK14910/";

    @BeforeStep
    public void beforeStep(StepExecution stepExecution) {
        workbook = new SXSSFWorkbook(100);
        sheet = workbook.createSheet("고객정보");

        Row header = sheet.createRow(rowNum++);
        header.createCell(0).setCellValue("계약번호");
        header.createCell(1).setCellValue("보증시작일자");
        header.createCell(2).setCellValue("보증종료일자");
        header.createCell(3).setCellValue("보증상태코드");
        header.createCell(4).setCellValue("최근연체종류코드");
        header.createCell(5).setCellValue("보증사유코드");
        header.createCell(6).setCellValue("보증설정금액");
        header.createCell(7).setCellValue("고객이름");
        header.createCell(8).setCellValue("지난2년간연체건수");
        header.createCell(9).setCellValue("총공사비");
        header.createCell(10).setCellValue("설계비");
        header.createCell(11).setCellValue("차액");
    }

    @Override
    public void open(ExecutionContext executionContext) throws ItemStreamException {
        try {
            dacCIT10001 = (DACIT10001) BatchApplicationContext.getBean(DACIT10001.class);

            String includeBDATA2 = BatchApplicationContext.getJobParameters().getString("includeBDATA2");

            List<ZC001001C0307010In> inputList;
            if ("Y".equalsIgnoreCase(includeBDATA2)) {
                inputList = dacCIT10001.selectCustomerNumbs_12();
            } else {
                inputList = dacCIT10001.selectCustomerNumbs_1();
            }

            if (inputList == null || inputList.isEmpty()) {
                throw new ItemStreamException("BATH007 테이블에서 고객번호를 찾을 수 없습니다.");
            }

            List<ZC001001C0307010> flatList = new ArrayList<>();
            for (ZC001001C0307010In input : inputList) {
                try {
                    List<ZC001001C0307010> customerList = dacCIT10001.selectCustomerDetailList(input.getCtnrNo());
                    if (customerList != null && !customerList.isEmpty()) {
                        flatList.addAll(customerList);
                        fetchCnt += customerList.size();
                    } else {
                        failCnt++;
                    }
                } catch (Exception e) {
                    failCnt++;
                    logger.error("고객 상세조회 실패: {}", input.getCtnrNo(), e);
                }
            }
            iterator = flatList.iterator();
        } catch (Exception e) {
            throw new ItemStreamException("고객번호 조회 중 에러: " + e.getMessage(), e);
        }
    }

    @Override
    public ZC001001C0307010 read() {
        return (iterator != null && iterator.hasNext()) ? iterator.next() : null;
    }

    @Override
    public ZC001001C0307010 process(ZC001001C0307010 input) {
        return input;
    }

    @Override
    public void write(List<? extends ZC001001C0307010> items) {
        for (ZC001001C0307010 item : items) {
            if (item == null) continue;

            Row row = sheet.createRow(rowNum++);

            row.createCell(0).setCellValue(defaultString(item.getCntNo()));
            row.createCell(1).setCellValue(defaultString(item.getWrntStYmd()));
            row.createCell(2).setCellValue(defaultString(item.getWrntEndYmd()));
            row.createCell(3).setCellValue(defaultString(item.getWrntStatCd()));
            row.createCell(4).setCellValue(defaultString(item.getLstDlqKndCd()));
            row.createCell(5).setCellValue(defaultString(item.getWrntRsnCd()));

            // 6번: 보증설정금액 BigDecimal -> double
            row.createCell(6).setCellValue(item.getSetWrntAmt().doubleValue());

            row.createCell(7).setCellValue(defaultString(item.getCusName()));

            // 8번: 지난2년간연체건수 BigDecimal -> double
            row.createCell(8).setCellValue(item.getDlqCnt2Year().doubleValue());

            // 총공사비, 설계비, 차액 조회 및 셀 추가
            double totalConstruction = 0d;
            double designCost = 0d;
            try {
                String totalConstructionStr = dacCIT10001.selectTotalConstructionCost(item.getCntNo());
                String designCostStr = dacCIT10001.selectDesignCost(item.getCntNo());
                totalConstruction = Double.parseDouble(totalConstructionStr);
                designCost = Double.parseDouble(designCostStr);
            } catch (Exception e) {
                logger.error("총공사비/설계비 조회 실패 - 계약번호:{}: {}", item.getCntNo(), e.getMessage());
            }
            row.createCell(9).setCellValue(totalConstruction);
            row.createCell(10).setCellValue(designCost);
            row.createCell(11).setCellValue(totalConstruction - designCost);

            writeCnt++;
        }
    }

    @Override
    public void update(ExecutionContext executionContext) {}

    @Override
    public void close() throws ItemStreamException {
        try {
            String timestamp = BDateUtil.getNow("yyyyMMddHHmmss");
            String fileName = "abc_" + timestamp + ".xlsx";
            String filePath = SAVE_DIR + fileName;

            File dir = new File(SAVE_DIR);
            if (!dir.exists()) {
                boolean created = dir.mkdirs();
                if (!created) {
                    throw new ItemStreamException("저장 디렉토리를 생성할 수 없습니다: " + SAVE_DIR);
                }
            }

            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                workbook.write(fos);
            }

            if (workbook instanceof SXSSFWorkbook) {
                ((SXSSFWorkbook) workbook).dispose();
            }

            String endTime = BDateUtil.getNow("yyyy-MM-dd HH:mm:ss");
            logger.info("############################################");
            logger.info("## PGM = ZC001007C01 END ##");
            logger.info("## JOB PROCESS COUNT ##");
            logger.info("############################################");
            logger.info("## END TIME [{}]", endTime);
            logger.info("## FETCH COUNT [{}]", fetchCnt);
            logger.info("## WRITE COUNT [{}]", writeCnt);
            logger.info("## FAIL COUNT [{}]", failCnt);
            logger.info("############################################");

        } catch (Exception e) {
            throw new ItemStreamException("엑셀 저장 중 오류 발생: " + e.getMessage(), e);
        }
    }

    private String defaultString(String str) {
        return (str == null) ? "" : str;
    }
}

 

1. 핵심 개념 및 프레임워크 이해

이 코드에서 사용된 핵심 기술들에 대한 깊은 이해가 필요해요.

  • Java 언어 숙달: 기본적인 문법을 넘어, 객체 지향 프로그래밍(OOP) 개념(상속, 다형성, 캡슐화 등), 컬렉션 프레임워크(List, Set, Map 등), 예외 처리, 스트림(Java 8+) 등에 능숙해야 합니다.
  • Spring/Spring Boot 프레임워크: 이 코드는 Spring Batch를 사용하고 있어요. Spring의 핵심인 의존성 주입(DI), 제어의 역전(IoC) 컨테이너 개념을 확실히 이해해야 합니다. Spring Batch의 경우, ItemReader, ItemProcessor, ItemWriter, Step, Job, ExecutionContext 등의 역할과 생명주기를 파악해야 하고요.
  • 배치 처리 개념: 대량의 데이터를 효율적으로 처리하는 배치 시스템의 특성(내구성, 재시작 가능성, 트랜잭션 관리 등)에 대한 이해가 중요합니다.
  • 데이터베이스 연동 (DAO/ORM): MyBatis나 JPA와 같은 DAO(Data Access Object) 프레임워크를 이해하고, SQL 쿼리 작성 및 최적화 능력이 필요합니다.
  • Apache POI (Excel 처리): 엑셀 파일을 생성하고 데이터를 쓰는 방법을 알아야 합니다. 특히 대용량 파일 처리를 위한 SXSSFWorkbook과 같은 스트리밍 API 사용법을 익히는 것이 좋아요.
  • 로깅 (SLF4J/Logback): logger를 이용한 효과적인 로깅 방법을 알아야 합니다.

2. 문제 해결 및 설계 능력

코드를 작성하는 능력만큼이나 문제를 정의하고 해결하는 능력이 중요해요.

  • 요구사항 분석: "고객 정보를 읽어서 엑셀 파일로 만든다"는 요구사항을 세부적으로 분석하고, 어떤 데이터가 필요하고 어떤 형태로 출력되어야 하는지 명확히 이해해야 합니다.
  • 시스템 흐름 이해 (가장 중요): 데이터가 어디서 오고(DB), 어떤 과정을 거쳐(읽기-처리-쓰기), 어디로 가는지(엑셀 파일) 전체적인 데이터 흐름을 머릿속에 그릴 수 있어야 합니다. 각 단계에서 어떤 로직이 필요한지, 어떤 예외가 발생할 수 있는지 예상하는 능력이 중요해요.
  • 설계 능력: 프로그램을 여러 모듈(Reader, Processor, Writer, DAO 등)로 나누어 설계하는 방법을 알아야 합니다. 각 모듈의 역할과 책임, 그리고 모듈 간의 인터페이스를 어떻게 정의할지 고민해야 해요.
  • 오류 처리 및 복구 전략: 에러 발생 시 프로그램이 어떻게 동작해야 하는지(로깅, 재시도, 실패 처리) 미리 계획하고, 이에 맞춰 코드를 작성할 수 있어야 합니다.

'Sql' 카테고리의 다른 글

공사비 산정  (0) 2025.07.25
ROWNUM = 1은 정렬  (0) 2025.07.21
SQL 프로그램없이 웹으로 실습하기  (1) 2025.07.07
실무에서 SQL 쿼리를 초보자가 쉽게 이해하고 익히려면  (0) 2025.07.07
IN,BETWEEN,EXISTS  (0) 2025.07.07

SQL 쿼리로 확인

SELECT * FROM v$version;
 
  • 이 쿼리를 실행하면 오라클의 버전, 운영체제 버전 등 다양한 정보가 출력되며, 가장 처음에 Database 버전이 표시됩니다

 

racle의 ROWNUM은 정렬 전에 필터링되므로, 단순히 조건 없이 ROWNUM = 1만 쓰면 "최근" 데이터가 아닌, 불특정한 한 줄이 먼저 뽑힐 수 있습니다.

예:

SELECT *
FROM YOUR_TABLE
WHERE ROWNUM = 1
ORDER BY CREATED_DATE DESC;  -- 이 정렬은 먼저 필터 된 1건에만 적용됨
 

따라서 위 쿼리는 제대로 동작하지 않고, 최근 날짜가 아닌 데이터가 나올 수 있습니다.

 FETCH FIRST 1 ROWS ONLY는 ORDER BY 이후 적용됨

예:

 
SELECT *
FROM YOUR_TABLE
ORDER BY CREATED_DATE DESC
FETCH FIRST 1 ROWS ONLY;
  • 이 방식은 먼저 전체 데이터를 날짜 기준으로 정렬하고,
  • 정렬된 결과에서 상위 1개만 가져옵니다.

→ 그래서 **정확히 '최근 1건'**을 가져옵니다.

🔁 또는, Oracle 11g 이하에서는 이렇게 써야...

Oracle 11g 이하에서는 FETCH FIRST 문법이 없기 때문에 다음과 같이 서브쿼리 + ROWNUM 방식으로 쓸 수 있습니다:

 
SELECT *
FROM (
    SELECT *
    FROM YOUR_TABLE
    ORDER BY CREATED_DATE DESC
)
WHERE ROWNUM = 1;
  • 이 방식은 먼저 서브쿼리에서 정렬된 데이터 생성 후
  • 그 결과에서 ROWNUM = 1을 적용
    → 의도대로 동작합니다.

📝 결론

조건추천 방식
Oracle 12c 이상 ORDER BY 날짜 DESC FETCH FIRST 1 ROWS ONLY;
Oracle 11g 이하 SELECT * FROM (SELECT * ORDER BY 날짜 DESC) WHERE ROWNUM = 1;
단순 테스트용 or 임의의 한 줄만 필요 ROWNUM = 1 가능 (정확한 "최신"은 아님)
 

 

SQL 프로그램없이 웹으로 실습하기

 

SQL을 실습하기 위해서는 SQL프로그램을 설치하고

테이블을 생성하고 데이터를 삽입해야하는 번거로운 과정을 거쳐야한다.

 

하지만 본인이 이제 SQL을 공부하기 시작했고

간단히 공부한 내용들을 직접 실습해보고 싶은것이라면 이러한 과정들은

복잡하고 귀찮아질뿐이다...

 

그래서, 프로그램 설치와 같은 복잡한 과정없이 웹에서 실습할 수 있는

사이트 3가지를 추천하고자 한다.

 

 

아래의 표를 통해 간단한 차이점을 알아보자!!!

 

  w3schools hackerrank programmers
난이도 상, 중, 하 모두 존재 상, 중, 하 모두 존재
사용가능언어 MySQL DB2, MySQL,
Oracle, MS SQL Server
MySQL, Oracle
문제유무 X O O

 

1. w3schools

MySQL을 실습할 수 있는 사이트로 SELECT와 같은

기본적인 조회문을 연습하기 좋다.

 

 

MySQL Tryit Editor v1.0

WebSQL stores a Database locally, on the user's computer. Each user gets their own Database object. WebSQL is supported in Chrome, Safari, and Opera. If you use another browser you will still be able to use our Try SQL Editor, but a different version, usin

www.w3schools.com

 

해당 웹 페이지 실제 화면

 

w3schools

오른쪽에 보면 테이블의 종류가 나와있으며

SQL문을 작성하여 'Run SQL'버튼을 클릭하면 아래에 결과가 출력된다.

 

 

2. hackerrank

DB2, MySQL, Oracle, MS SQL Server과 같은 다양한 SQL문을 이용하여

제시된 문제를 해결하면서 연습할 수 있는 곳이다.

영어로 되어 있어 어렵지만 공부할 수 있는 좋은 기회라고도 생각된다.

 

 

Solve SQL Code Challenges

A special-purpose language designed for managing data held in a relational database.

www.hackerrank.com

 

해당 웹 페이지 실제 화면

 

hackerrank

오른쪽을 보면 현재 자신의 SQL 실력에 따른 문제 난이도를 조절하여

자신의 실력에 맞는 문제들만 출력할 수 있다.

 

hackerrank

문제를 선택하여 들어오면 왼쪽에는 문제에 대한 설명,

오른쪽에는 SQL문을 입력할 수 있는 화면이 있고 자신에게 맞는 언어를 선택하여

문제를 풀고 실행해보면 된다.

 

 

3. programmers

MySQL, Oracle 2 가지의 SQL문을 이용하여

제시된 문제를 해결하면서 연습할 수 있는 곳이다.

한국에서 운영하는 사이트로 한국어로 되어있어 보기 편하고 쉽게 이해할 수 있다.

 

 

코딩테스트 연습 | 프로그래머스 스쿨

개발자 취업의 필수 관문 코딩테스트를 철저하게 연습하고 대비할 수 있는 문제를 총망라! 프로그래머스에서 선발한 문제로 유형을 파악하고 실력을 업그레이드해 보세요!

school.programmers.co.kr

 

해당 웹 페이지 실제 화면

 

programmers

난이도, 언어, 특정 대회 기출문제를 선택하여

간편하게 자신의 실력에 맞는 문제들만 출력할 수 있다.

 

programmers

문제를 선택하여 들어오면 왼쪽에는 문제에 대한 설명,

오른쪽에는 SQL문을 입력할 수 있는 화면이 있고 자신에게 맞는 언어를 선택하여

문제를 풀고 실행해보면 된다.

 

왼쪽 가장 위를 보면 어떤 문장을 연습하기 위한 문제인지를 확인할 수 있다.

위 이미지는 SELECT를 연습하기 위한 문제이다.

 

 

이제 위 사이트들을 통해 SQL을 연습하여 실습을 진행하면 된다.

실무에서 SQL 쿼리를 초보자가 쉽게 이해하고 익히려면 다음과 같은 단계별 접근이 가장 효과적입니다.

1. 기본 문법부터 익히기

  • SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY 등 핵심 구문의 역할과 순서를 먼저 이해하세요
  • 각 구문이 쿼리에서 어떤 역할을 하는지, 실제 데이터가 어떻게 추려지는지 예제를 통해 확인하는 것이 중요합니다248.

2. 실제 데이터로 연습하기

  • 이론만 공부하지 말고, 실제 테이블과 데이터를 직접 다뤄보면서 쿼리를 작성해보세요239.
  • Excel이나 CSV 파일을 DB에 올려서 직접 데이터를 추출해보면 실무 감각이 빨리 생깁니다2.

3. 기존 쿼리 분석하기

  • 조직이나 팀에서 이미 사용 중인 쿼리문을 받아서, 각 구문이 무슨 역할을 하는지 하나씩 해석해보세요35.
  • 쿼리의 일부분을 바꿔 실행해보고, 결과가 어떻게 변하는지 직접 확인하면 이해가 빠릅니다3.

4. 데이터와 테이블 구조 이해

  • 쿼리 문법만큼 중요한 것이 테이블 구조와 데이터의 의미를 파악하는 것입니다5.
  • 어떤 데이터가 어떤 목적으로 쌓였는지, 컬럼별로 어떤 값이 들어가는지 먼저 확인하세요5.

5. 쉬운 예제부터, 점차 복잡한 쿼리로 확장

  • 도서관, 쇼핑몰 등 일상 예제로 시작해 기본 쿼리를 연습하고, 점차 GROUP BY, HAVING, 서브쿼리 등으로 확장하세요48.

6. 궁금한 부분은 검색·질문

  • 모르는 구문이나 함수는 바로 검색하거나, 동료에게 질문하는 습관을 들이세요35.

추천 학습 순서 예시

단계학습 내용실습 예시
1 SELECT, FROM, WHERE 특정 조건의 데이터 추출
2 GROUP BY, 집계함수 부서별 인원수, 상품별 매출 합계 구하기
3 HAVING, ORDER BY 집계 결과에서 조건 필터, 정렬
4 BETWEEN, IN 날짜/범위, 여러 값 조건 필터링
5 실제 쿼리 분석 기존 쿼리 해석, 일부분 수정해보기
6 JOIN, 서브쿼리 여러 테이블 연결, 쿼리 안에 쿼리 넣어보기
 

초보자에게 가장 중요한 포인트

  • 기초 문법부터 차근차근 익히고, 실제 데이터로 반복 실습하는 것이 가장 빠른 성장 방법입니다239.
  • 쿼리를 한 줄씩 해석해보고, 결과를 예측한 뒤 직접 실행해보는 연습이 이해에 큰 도움이 됩니다35.
  • 테이블과 데이터의 구조를 먼저 파악하는 습관을 들이세요5.

이런 단계로 접근하면 실무에서 마주치는 복잡한 쿼리도 점점 쉽게 이해할 수 있습니다.

'Sql' 카테고리의 다른 글

ROWNUM = 1은 정렬  (0) 2025.07.21
SQL 프로그램없이 웹으로 실습하기  (1) 2025.07.07
IN,BETWEEN,EXISTS  (0) 2025.07.07
GROUP BY 와 HAVING  (0) 2025.07.07
BxM 프레임워크의 일반적인 계층 이해(1)  (1) 2025.07.02

✅ 1. IN – 리스트에 포함되는 값만 추출

📌 뜻

WHERE 컬럼 IN (값1, 값2, 값3)
→ 컬럼 값이 주어진 값 리스트 중 하나라도 맞으면 통과


✅ 예제: 특정 부서의 직원만 보기

sql
복사편집
SELECT emp_name, dept_id FROM employees WHERE dept_id IN (10, 20);

설명:
→ dept_id가 10 또는 20인 직원만 조회

emp_namedept_id
홍길동 10
이순신 10
강감찬 20
 

✅ 실무 팁: 서브쿼리 안에서도 자주 씀

sql
복사편집
SELECT emp_name FROM employees WHERE dept_id IN ( SELECT dept_id FROM departments WHERE location = '서울' );

→ 서울에 있는 부서 소속 직원만 조회


✅ 2. BETWEEN – 범위 조건

📌 뜻

WHERE 컬럼 BETWEEN A AND B
→ 컬럼 값이 A 이상, B 이하일 때 참
(포함됨)


✅ 예제: 2500 ~ 4000 사이 급여 직원

sql
복사편집
SELECT emp_name, salary FROM employees WHERE salary BETWEEN 2500 AND 4000;
emp_namesalary
홍길동 2800
이순신 3200
유관순 3000
강감찬 4000
 

✅ BETWEEN은 날짜에도 많이 씀

sql
복사편집
WHERE hire_date BETWEEN TO_DATE('2024-01-01', 'YYYY-MM-DD') AND TO_DATE('2024-12-31', 'YYYY-MM-DD')

✅ 3. EXISTS – "값이 있냐?"를 물어봄 (존재 여부 확인)

📌 뜻

WHERE EXISTS (서브쿼리)
→ 서브쿼리 결과가 1건이라도 있으면 TRUE


✅ 예제: 급여 4000 이상인 기록이 있는 부서만 출력

sql
복사편집
SELECT dept_id FROM departments D WHERE EXISTS ( SELECT 1 FROM employees E WHERE E.dept_id = D.dept_id AND E.salary >= 4000 );

설명:
→ employees 테이블에 해당 부서에서 4000 이상 급여가 한 명이라도 있으면 통과


✅ EXISTS vs IN 차이

구분INEXISTS
사용 대상 값 목록 서브쿼리 존재 여부
결과 조건 값이 리스트에 포함되면 참 서브쿼리 결과가 있으면 참
특징 서브쿼리 결과를 비교 서브쿼리가 실행되고 결과 유무만 체크
 

✅ EXISTS 실무 예: 학생이 성적을 가진 경우만 조회

sql
복사편집
SELECT student_id, student_name FROM Students S WHERE EXISTS ( SELECT 1 FROM Grades G WHERE G.student_id = S.student_id );

→ 성적(Grades) 테이블에 한 건이라도 존재하면 조회


✅ 세 가지 요약

문법의미예시
IN 리스트 포함 여부 dept_id IN (10, 20)
BETWEEN 범위 조건 (포함) salary BETWEEN 3000 AND 4000
EXISTS 서브쿼리 결과 존재 여부 WHERE EXISTS (SELECT ... )

✅ 1. GROUP BY 와 HAVING

📌 개념 차이

항목설명
GROUP BY 데이터를 기준값으로 묶어서 집계할 때 사용 (예: 부서별 평균)
HAVING GROUP BY로 묶은 결과에 조건을 걸 때 사용
WHERE GROUP BY 하기 전에 데이터를 필터링
 

✅ 예제 1: 부서별 평균 급여 구하기 (GROUP BY)

sql
복사편집
SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id;

▶ dept_id 별로 묶어서 평균 salary 계산


✅ 예제 2: 평균 급여가 3000 이상인 부서만 보기 (HAVING)

sql
복사편집
SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id HAVING AVG(salary) >= 3000;

▶ GROUP BY로 부서별 평균을 구한 뒤
▶ HAVING으로 평균 3000 이상인 부서만 추출


✅ 예제용 테이블 구조

sql
복사편집
CREATE TABLE employees ( emp_id NUMBER, emp_name VARCHAR2(50), dept_id NUMBER, salary NUMBER ); INSERT INTO employees VALUES (1, '홍길동', 10, 2800); INSERT INTO employees VALUES (2, '이순신', 10, 3200); INSERT INTO employees VALUES (3, '강감찬', 20, 4000); INSERT INTO employees VALUES (4, '유관순', 20, 3000);

→ dept_id 10 평균 = 3000
→ dept_id 20 평균 = 3500


✅ 2. ORDER BY

📌 개념

  • 결과를 정렬하는 키워드
  • 기본은 오름차순 (ASC), 내림차순은 DESC

✅ 예제 1: 급여 순으로 직원 정렬

sql
복사편집
SELECT emp_name, salary FROM employees ORDER BY salary DESC;

▶ 급여 높은 순으로 정렬


✅ 예제 2: 부서 오름차순, 같은 부서 내에서는 급여 높은 순

sql
복사편집
SELECT emp_name, dept_id, salary FROM employees ORDER BY dept_id ASC, salary DESC;

✅ 3. 자주 쓰는 SQL 명령어 요약표

명령어설명
SELECT 데이터 조회
FROM 대상 테이블 지정
WHERE 조건 필터링
GROUP BY 그룹별 집계
HAVING 그룹 집계 조건
ORDER BY 정렬
JOIN 여러 테이블 연결 (INNER, LEFT, RIGHT 등)
IN 리스트 내 포함 조건
BETWEEN 범위 조건 (예: BETWEEN 1000 AND 2000)
EXISTS 서브쿼리 존재 여부 체크
CASE WHEN 조건 분기 (if처럼 사용)
DISTINCT 중복 제거
LIMIT / FETCH FIRST 결과 개수 제한
RANK() / ROW_NUMBER() 순위 계산용 함수
 

✅ 추가 예제: 종합 문제

부서별로 급여 총합을 구하고, 5000 이상인 부서만 표시하되,
급여 총합이 높은 순으로 정렬

sql
복사편집
SELECT dept_id, SUM(salary) AS total_salary FROM employees GROUP BY dept_id HAVING SUM(salary) >= 5000 ORDER BY total_salary DESC;

✅ 초보자 분석 순서 요약

  1. FROM → 어떤 테이블?
  2. WHERE → 사전 필터링?
  3. GROUP BY → 묶는 기준?
  4. HAVING → 그룹 조건?
  5. ORDER BY → 정렬 기준?
  6. SELECT → 최종으로 보여줄 컬럼?

✅ 추가로 배우면 좋은 것들

영역추천 주제
JOIN INNER JOIN, LEFT JOIN
집계 함수 SUM(), AVG(), COUNT(), MAX()
서브쿼리 SELECT절, WHERE절, HAVING절에 사용
윈도우 함수 RANK(), ROW_NUMBER(), OVER()
조건 분기 CASE WHEN THEN END
INSERT/UPDATE/DELETE DML 기본 조작

BxM 프레임워크의 일반적인 계층 및 파일 매핑을 실제 파일명과 함께 예를 들어 설명해 드릴게요. 가상의 "고객 정보 조회 및 수정" 기능을 중심으로 설명하겠습니다.


예시: 고객 정보 조회 및 수정 기능

1. WebSquare 화면 (클라이언트 계층)

  • 역할: 사용자가 고객 정보를 조회하고 수정할 수 있는 UI를 제공합니다. 서버에 조회/수정 요청을 보내고, 받은 결과를 화면에 표시합니다.
  • 주요 파일:
    • CM0101.xml: 고객 정보 조회/수정 화면의 UI(레이아웃, 입력 필드, 버튼, 그리드 등)를 정의합니다.
      • 예시 내용: 고객ID 입력란, 조회 버튼, 고객 목록을 표시할 그리드, 상세 정보 입력란, 저장 버튼 등
    • CM0101.js: CM0101.xml 화면의 클라이언트 측 로직을 정의합니다. (버튼 클릭 시 서버 호출, 조회 결과 받아서 그리드에 표시, 입력값 유효성 검사 등)
      • 예시 내용:
        • fn_searchCustomer() 함수: 조회 버튼 클릭 시 SC_CustomerService.getCustomerList 서비스 호출
        • fn_saveCustomer() 함수: 저장 버튼 클릭 시 SC_CustomerService.saveCustomer 서비스 호출

2. 서버 측 진입점 (SC: Service Controller / Service Component)

  • 역할: WebSquare 화면에서 넘어온 요청을 처음으로 받아서 처리할 자바 메서드를 찾아 매핑하고, 비즈니스 로직을 담당하는 BC 계층을 호출합니다. 트랜잭션의 시작과 종료를 관리합니다.
  • 주요 파일:
    • SC_CustomerService.java: 고객 서비스와 관련된 요청을 처리하는 컨트롤러 역할을 합니다.
      • 예시 메서드:
      • Java
         
        @BxMServiceOperation("getCustomerList") // WebSquare에서 "getCustomerList" 오퍼레이션 호출 시 이 메서드 실행
        public BxMServiceResponse getCustomerList(BxMServiceRequest request) {
            // BC_Customer.java의 getCustomerListBySearchCondition 메서드 호출
            // 검색 조건을 담은 DTO를 BC에 전달하고, BC로부터 결과 DTO를 받음
            CustomerSearchConditionInDto inDto = request.getParam(CustomerSearchConditionInDto.class);
            CustomerListOutDto outDto = bc_Customer.getCustomerListBySearchCondition(inDto);
            return BxMServiceResponse.create().addParam(outDto);
        }
        
        @BxMServiceOperation("saveCustomer") // WebSquare에서 "saveCustomer" 오퍼레이션 호출 시 이 메서드 실행
        public BxMServiceResponse saveCustomer(BxMServiceRequest request) {
            // BC_Customer.java의 saveCustomerInfo 메서드 호출
            CustomerSaveInDto inDto = request.getParam(CustomerSaveInDto.class);
            bc_Customer.saveCustomerInfo(inDto); // 저장 로직 수행
            return BxMServiceResponse.create().addResult("Success", true);
        }
        

3. 비즈니스 로직 계층 (BC: Business Component)

  • 역할: SC로부터 호출되어 실제 핵심 비즈니스 로직을 수행합니다. 데이터 가공, 복잡한 업무 규칙 적용, 여러 DBIO 호출 조합 등이 이루어집니다.
  • 주요 파일:
    • BC_Customer.java: 고객 정보와 관련된 구체적인 비즈니스 규칙을 구현합니다.
      • 예시 메서드:
      • Java
         
        // 고객 목록 조회 비즈니스 로직
        public CustomerListOutDto getCustomerListBySearchCondition(CustomerSearchConditionInDto inDto) {
            // 1. 입력값 유효성 검사 (예: 검색 조건 필수 여부)
            // 2. DBIO 호출하여 고객 목록 조회 (dbio_Customer.xml의 selectCustomerList 쿼리 호출)
            List<CustomerDto> customerList = dbio_Customer.selectCustomerList(inDto); // DBIO 호출 예시
            // 3. 조회된 데이터를 Business 규칙에 따라 가공 (예: 상태 코드 변환)
            // 4. 결과 DTO에 담아 반환
            CustomerListOutDto outDto = new CustomerListOutDto();
            outDto.setCustomerList(customerList);
            return outDto;
        }
        
        // 고객 정보 저장 비즈니스 로직
        public void saveCustomerInfo(CustomerSaveInDto inDto) {
            // 1. 입력값 유효성 검사 (예: 필수 필드 누락 여부)
            // 2. 기존 고객인지 신규 고객인지 판단
            if (inDto.isNewCustomer()) {
                dbio_Customer.insertCustomer(inDto); // 신규 고객 등록 (DBIO 호출)
            } else {
                dbio_Customer.updateCustomer(inDto); // 기존 고객 수정 (DBIO 호출)
            }
            // 3. 추가적인 관련 데이터 처리 (예: 이력 저장 등, 다른 DBIO 호출 가능)
        }
        

4. 데이터 접근 계층 (DBIO: Data Base Input/Output)

  • 역할: 데이터베이스와의 연동을 전담합니다. SQL 쿼리를 정의하고 실행하며, 데이터베이스 결과를 자바 객체로 매핑합니다. (DAO 역할)
  • 주요 파일:
    • dbio_Customer.dbio: 고객 테이블에 대한 데이터베이스 CRUD 쿼리를 정의합니다.
      • 예시 내용 (.dbio 파일은 XML 기반입니다):
      • XML
         
        <?xml version="1.0" encoding="UTF-8"?>
        <dbio namespace="dbio_Customer"> <select id="selectCustomerList" resultClass="com.example.dto.CustomerDto">
                SELECT
                    CUST_ID, CUST_NM, CUST_TEL_NO, CUST_ADDR
                FROM
                    TB_CUSTOMER
                WHERE
                    1=1
                <if test="searchCustNm != null and searchCustNm != ''">
                    AND CUST_NM LIKE '%' || #{searchCustNm} || '%'
                </if>
                ORDER BY CUST_ID
            </select>
        
            <insert id="insertCustomer" parameterClass="com.example.dto.CustomerSaveInDto">
                INSERT INTO TB_CUSTOMER (
                    CUST_ID, CUST_NM, CUST_TEL_NO, CUST_ADDR
                ) VALUES (
                    #{custId}, #{custNm}, #{custTelNo}, #{custAddr}
                )
            </insert>
        
            <update id="updateCustomer" parameterClass="com.example.dto.CustomerSaveInDto">
                UPDATE TB_CUSTOMER
                SET
                    CUST_NM = #{custNm},
                    CUST_TEL_NO = #{custTelNo},
                    CUST_ADDR = #{custAddr}
                WHERE
                    CUST_ID = #{custId}
            </update>
        
        </dbio>
        

5. 데이터 전송 객체 (DTO: Data Transfer Object / OMM: Object Meta Model)

  • 역할: 각 계층 간 데이터를 주고받을 때 사용되는 구조를 정의합니다. .omm 파일을 통해 DTO 자바 클래스가 자동으로 생성됩니다.
  • 주요 파일:
    • CustomerSearchConditionIn.omm: 고객 목록 조회 시 입력받는 검색 조건 DTO를 정의합니다.
      • 자동 생성: CustomerSearchConditionInDto.java
      • Java
         
        // 자동 생성된 DTO 예시
        public class CustomerSearchConditionInDto {
            private String searchCustNm;
            // Getter, Setter
        }
        
    • CustomerListOut.omm: 고객 목록 조회 결과로 반환되는 DTO를 정의합니다.
      • 자동 생성: CustomerListOutDto.java
      • Java
         
        // 자동 생성된 DTO 예시
        public class CustomerListOutDto {
            private List<CustomerDto> customerList; // CustomerDto는 별도 OMM 또는 DBIO resultClass로 정의됨
            // Getter, Setter
        }
        
    • CustomerSaveIn.omm: 고객 정보 저장 시 입력받는 DTO를 정의합니다.
      • 자동 생성: CustomerSaveInDto.java
      • Java
         
        // 자동 생성된 DTO 예시
        public class CustomerSaveInDto {
            private String custId;
            private String custNm;
            private String custTelNo;
            private String custAddr;
            // Getter, Setter, isNewCustomer() 등의 로직 추가 가능
        }
        
    • Customer.omm (또는 DBIO의 resultClass에서 직접 지정): 단일 고객 정보를 담는 DTO를 정의합니다. (DBIO에서 resultClass로 사용될 수 있음)
      • 자동 생성: CustomerDto.java
      • Java
         
        // 자동 생성된 DTO 예시
        public class CustomerDto {
            private String custId;
            private String custNm;
            private String custTelNo;
            private String custAddr;
            // Getter, Setter
        }
        

전체적인 호출 흐름:

  1. CM0101.js (WebSquare)에서 사용자가 조회 버튼 클릭 시, 서버의 SC_CustomerService의 getCustomerList 오퍼레이션 호출.
    • 이때 CustomerSearchConditionInDto에 검색 조건을 담아 전달.
  2. **SC_CustomerService.java**의 getCustomerList 메서드가 호출됨.
  3. SC_CustomerService는 BC_Customer.java의 getCustomerListBySearchCondition 메서드 호출.
  4. **BC_Customer.java**의 getCustomerListBySearchCondition 메서드에서 비즈니스 로직 수행 후, dbio_Customer.dbio 파일의 selectCustomerList 쿼리를 호출.
  5. dbio_Customer.dbio에서 데이터베이스로부터 고객 목록을 조회하여 CustomerDto 객체 리스트로 반환.
  6. BC_Customer는 이 리스트를 받아 CustomerListOutDto에 담아 SC_CustomerService로 반환.
  7. SC_CustomerService는 CustomerListOutDto를 최종 응답으로 WebSquare 화면에 반환.
  8. **CM0101.js**는 받은 CustomerListOutDto 데이터를 파싱하여 CM0101.xml 화면의 고객 목록 그리드에 표시.

이 예시를 통해 BxM 프레임워크의 각 계층이 어떤 파일로 구성되고 어떻게 상호작용하는지 이해하시는 데 도움이 되셨기를 바랍니다.

확실하게 다시 한번 BxM의 일반적인 흐름을 아주 쉽게 설명해 드릴게요. 비유를 들어볼까요?

BxM 시스템은 마치 레스토랑과 같아요.


1. 손님 (WebSquare 화면 - xml, js)

  • 역할: 음식을 주문하는 고객입니다. 메뉴판(UI)을 보고, 원하는 음식을 선택(버튼 클릭, 데이터 입력)한 후, 주문(서버 요청)을 합니다.
  • 파일:
    • OOO.xml: 메뉴판 (화면의 디자인과 버튼, 입력창 등)
    • OOO.js: 고객이 주문을 누르면 주문서를 작성하고 주방으로 보내는 행동

2. 웨이터 (SC: Service Controller - SC_OOO.java)

  • 역할: 손님(WebSquare)의 주문(요청)을 가장 먼저 받습니다. 주문 내용을 확인하고, 어떤 요리사에게 어떤 주문을 전달해야 할지 판단합니다.
  • 하는 일:
    • "주문 들어왔습니다!" (WebSquare 요청 수신)
    • "손님은 김치찌개를 시켰네. 김치찌개 전문 요리사에게 전달해야지." (요청에 맞는 BC 호출)
    • 주문이 제대로 처리되는지 전체 과정을 지켜봅니다. (트랜잭션 관리)
  • 파일: SC_OOO.java (자바 클래스)

3. 요리사 (BC: Business Component - BC_OOO.java)

  • 역할: 웨이터(SC)에게서 주문을 받아, 실제 음식을 만드는 핵심적인 비즈니스 로직을 수행합니다. 여러 재료(데이터)를 가져와 요리(가공, 계산)합니다.
  • 하는 일:
    • "김치찌개 주문이요? 알겠습니다." (SC로부터 요청 받음)
    • "재료가 있나? 김치, 돼지고기, 두부... 창고(데이터베이스)에 있는지 식자재 담당에게 물어봐야겠다." (DBIO 호출)
    • 재료를 받아와서 레시피대로 요리합니다. (복잡한 비즈니스 로직 처리)
    • 완성된 요리를 웨이터에게 전달합니다. (SC로 결과 반환)
  • 파일: BC_OOO.java (자바 클래스)

4. 식자재 담당 (DBIO: Data Base Input/Output - OOO.dbio)

  • 역할: 요리사(BC)의 요청을 받아 창고(데이터베이스)에 가서 필요한 식자재(데이터)를 가져오거나, 새로운 식자재를 넣거나, 기존 식자재를 정리하는 일을 전담합니다. 실제 창고 담당자와 대화하는 전문가입니다.
  • 하는 일:
    • "요리사님, 김치 있어요? 돼지고기 있어요?" (BC로부터 데이터 요청)
    • "네, 창고에 있습니다. 가져다 드릴게요." (데이터베이스에서 조회/저장/수정/삭제)
    • 여기서 OOO.dbio 파일은 식자재 담당자가 창고에 가서 무엇을 어떻게 가져올지 적어둔 '식자재 목록표 + 가져오는 방법'이 적힌 문서입니다. (SQL 쿼리 정의)
  • 파일: OOO.dbio (XML 형태의 파일)

흐름 요약:

  1. **손님(WebSquare)**이 **웨이터(SC)**에게 주문합니다.
  2. **웨이터(SC)**는 주문을 **요리사(BC)**에게 전달합니다.
  3. **요리사(BC)**는 요리를 만들기 위해 **식자재 담당(DBIO)**에게 재료를 요청합니다.
  4. **식자재 담당(DBIO)**은 창고(데이터베이스)에서 재료를 가져오거나 정리하여 **요리사(BC)**에게 다시 줍니다.
  5. **요리사(BC)**는 재료로 요리를 완성하여 **웨이터(SC)**에게 줍니다.
  6. **웨이터(SC)**는 완성된 요리를 **손님(WebSquare)**에게 가져다줍니다.
  7. **손님(WebSquare)**은 받은 요리를 화면에 봅니다.

이렇게 각 역할이 나뉘어 있어서 시스템이 더 깔끔하고, 나중에 요리 방법이 바뀌거나, 창고를 옮겨도 다른 부분에 영향을 덜 주게 되는 장점이 있습니다.

+ Recent posts