오늘은 개발시 프론트엔드(WEB) 프로젝트와 백엔드(WAS) 프로젝트로 분리되어 있을 시 LOCAL WAS 또는 배포되어진 WAS로 환경을 분리한 경험을 기록하고자 한다. 

현재 진행중인 프로젝트에서 HTML, CSS에 관한 부분을 타 회사 팀에서 맡게 되어 환경을 분리해야 하는 상황이 생겼다. 퍼블리싱을 담당한 회사는 우리 회사에서 만들고 있는 SpringBoot 프로젝트 환경이 없다 보니 이에 대한 부분을 제공해야 했기 때문이다. 퍼블리싱 이외의 javascript 관련 코드도 우리 팀이 맡은 상황이다 보니 ajax 코드가 추가될 시 퍼블리셔 업체에서 동작을 확인할 수 있도록 해주어야 했다. 

우리 팀은 WEB과 WAS 모두 개발하기 때문에 WAS 환경을 LOCAL 에서 실행하며 테스트 해야했고, 퍼블리셔를 위한 서버 WAS를 배포해주어야 했다. 이때 두 환경의 axios base url을 분리하여 이 부분을 해결하였다.

우리팀: Local Vuejs 개발 -> Local Was
퍼블리셔: Local Vuejs 개발 -> Dev Server Was

이러한 환경 분리를 위해 환경 변수를 이용하였으며 인텔리 제이에서 npm 실행 환경을 설정할 시 환경 변수를 주어 구분하였다. Edit congirutaion -> Environment 에서 간단히 가능하였다. 처음에 환경 변수를 설정하였는데도 불구하고 값이 제대로 읽혀지지 않아 이유를 찾아보니 명명법을 지켜야 제대로 읽을 수 있었다. 
Vue의 경우 "VUE_APP_" 이란 접두사를 붙여주어야 했다. React 의 경우 REACT_APP_ 이란 접두사를 붙여주어야 한다고 한다.

이를 통하여 axios 의 base url 을 분리하였고, 문제는 해결되어진 것으로 보았다. 그러나 그 이후 더 큰 문제가 발생하였다. 개발 WAS 에서 Session ID 가 요청마다 새로 생기는 문제였다. 우리의 로그인 방식이 세션 방식이었기에 이 문제는 크게 작용하였다. 

이것에 대한 내용은 다음 링크에 이어 쓰도록 하겠다.

 

다음글 - 개발을 위해 Local, Dev Server WAS 환경 분리하기 2 - SameSite란 :: 꿀잠 (tistory.com)

운영 서버에서 로그를 확인해 보아야 하는 일이 생겼다.
그런데 왜인지 로그 파일을 저장하는 경로가 보이지 않는 것이다. 운영 서버 리눅스 계정이 root 계정이 아니었기에 권한의 문제로 보이지 않는 것으로 처음엔 생각하였다.

고객사 개발자분께 해당 경로의 로그 파일 확인을 요청하고 DevOps에 빠삭하신 차장님께 상의 하였다. 운영 서버를 한동안 체크하였고, 애초에 로그가 저장되지 않았다는 사실을 깨달았다. 고객사 개발자 분도 파일이 아예 존재하지 않는다고 연락을 주셨다. 전임자 분이 담당할 때 부터 지금까지 운영 서버 이슈는 DB쪽 체크를 하는 정도에서 문제를 해결할 수 있었기에 로그가 저장되고 있지 않다는 사실을 몰랐던 것이다.

로그가 보이지 않았던 이유는 읽기 권한이 없는 것의 문제가 아니라 쓰기 권한의 문제였다. Spring 서비스가 당시 제공 받은 계정으로 실행되고 있었고, 쓰기 권한이 없던 계정의 서비스이다 보니 로그 라이브러리에서 로그 파일을 쓰지 못하였던 것이다. 로그 파일 경로를 해당 계정의 하위 경로로 바꾸어 문제를 해결하였다.

요약: Spring 로그가 남지 않는다면 로그 경로에 대한 서비스의 실행 계정의 쓰기 권한을 확인해야 한다.

오늘 포스트 할 코드는 이전에 올렸던 포스트의 코드의 리팩토링과 함께 프로그래머스의 케이스를 추가한 코드이다. 이전 포스트 내용은 아래와 같다. 

 

백준 문제 실행 테스트 환경(JAVA) 만들기 :: 꿀잠 (tistory.com)

 

백준 문제 실행 테스트 환경(JAVA) 만들기

코딩테스트, 알고리즘을 위한 사이트들이 있는데 그 중 내가 이용하는 것은 프로그래머스와 백준 사이트이다.프로그래머스의 경우 자체적으로 코드 실행을 할 수도 있고, 코드 실행중에 System.ou

ygs3004.tistory.com

 

코딩테스트의 대표 사이트인 백준과 프로그래머스의 문제에는 형식에 차이가 있다.
백준의 경우 테스트로 입력되는 파라미터가 System.in으로 입력되고, 정답의 경우 출력을 통하여 문제를 푼다면.
프로그래머스의 경우에는 solution 함수를 만들고, 해당 solution 함수의 파라미터로 테스트 케이스가 입력되고, 정답의 경우 return 값으로 처리 된다.

 

첫 줄에 남겨둔 링크인 이전 코드의 경우 백준을 고려하여 만들다보니 프로그래머스의 문제풀이 테스트 결과를 확인할 수 없는 문제가 있었다. 프로그래머스의 테스트 케이스를 문자열로 하여 적절하게 타입 변환 해주는 코드들을 작성할 수도 있었겠지만 Jackson 라이브러리 등을 붙이는 것이 아니면 과한 작업 소요라고 생각하였고, HashMap을 통하여 테스트 케이스 및 결과 케이스를 저장하는 형태로 테스트 환경을 구축하였다.


또한 실제 사이트에 제출 전 IDE에서 작성한 부분을 수정하는 작업을 최대한 적게 줄일 수 있도록 코드를 작성하였다.

백준과 프로그래머스의 두 케이스로 나누기 위해 전략패턴을 사용하고자 하였고, 우선 한 일은 문제에 대한 Interface 화이다.

interface Problem

import java.awt.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;

public interface Problem<P, R>{

    Problem<P, R> setAnswer(Object answer);
    HashMap<String, P> getInputCase();
    HashMap<String, R> getResultCase();
    R solve(P parameter) throws Exception;

    default void test() {

        try {
            // Result 저장
            Map<String, R> resultCase = getResultCase();
            boolean isSuccess = true;

            Map<String, P> inputCase = getInputCase();
            for (String caseKey : inputCase.keySet()) {
                P input = inputCase.get(caseKey);

                System.out.println("테스트 실행 Input");
                println(input);

                long startTime = System.nanoTime();
                R testResult = solve(input);
                long endTime = System.nanoTime();

                println("실행시간: " + (endTime-startTime)/1_000_000.00 +"ms");
                System.out.println();

                R expectResult = resultCase.get(caseKey);
                boolean isCaseSuccess = expectResult.equals(testResult);
                isSuccess = isSuccess && isCaseSuccess;

                println("정답");
                println(expectResult);
                System.out.println();

                println("결과");
                println(testResult);
                System.out.println();

                if(isCaseSuccess){
                    passPrintln("Case 통과");
                }else{
                    failPrintln("Case 실패");
                }

                System.out.println();
                println("=======================================================================================");
            }

            if(isSuccess){
                passPrintln("모든 테스트가 통과하였습니다.");
                System.out.println();
            }else{
                failPrintln("************** 실패한 테스트가 있습니다. 결과를 확인해주세요 **************");
                // 실패시 소리
                Toolkit.getDefaultToolkit().beep();
                System.out.println();
            }

        } catch (Exception e){
            e.printStackTrace();
        }
    };

    private void passPrintln(String str){
        // console 색상
        String reset = "\u001B[0m";
        String green = "\u001B[32m";
        println(green + str + reset);
    }

    private void failPrintln(String str){
        // console 색상
        String red = "\u001B[31m";
        String reset = "\u001B[0m";
        println(red + str + reset);
    }

    default void println(Object input){
        if(input instanceof File) {
            try{
                FileInputStream inputFileStream = new FileInputStream((File)input);
                BufferedReader br = new BufferedReader(new InputStreamReader(inputFileStream));
                StringBuilder fileString = new StringBuilder();
                String line = "";
                while ((line = br.readLine()) != null) {
                    fileString.append(line).append(System.lineSeparator());
                }
                System.out.print(fileString);
            }catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }else if (input.getClass().isArray()) {
            printArray(input);
        } else {
            System.out.print(input);
        }

        System.out.println();
    }

    default void printArray(Object input){
        int length = Array.getLength(input);
        Object[] array = new Object[length];
        IntStream.range(0, length).forEach(i ->
                array[i] = Array.get(input, i)
        );

        System.out.print("[");
        for(int i = 0; i < array.length; i++){
            Object value = array[i];
            if(value.getClass().isArray()){
                printArray(value);
            }else{
                System.out.print(value);
            }
            if(i != array.length - 1) System.out.print(", ");
        }

        System.out.print("]");
    }

}

백준과 프로그래머스에서 달라지는 케이스인 InputCase 와 ResultCase 를 구현 체에서 작성하게 하였고 test() 자체는 동일하게 구성하였다. 틀렸을 경우 확인하기 쉽도록 GPT의 도움을 받아 console 창에 색상과, 소리가 나는 코드를 추가하였다. 또한 케이스를 프린트 해볼 경우 Array 안의 다른 Object 가 있는 경우가 있어 print 관련하여서도 메서드를 추가하였다.
프로그래머스의 경우 input 과 return 의 타입이 변경될 수 있어서 제네릭을 이용하였다.

아래는 BaekJoon과 Programmers에서 Interface 메서드들을 구현한 내용들이다.

 

class BaekJoon

import java.io.*;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;

public class BaekJoon implements Problem<File, String>{

    Object answer;
    File[] testFiles;

    public Problem<File, String> setAnswer(Object answer) {
        this.answer = answer;
        URL classDir = answer.getClass().getResource("");
        try{
            testFiles = new File(classDir.toURI()).listFiles();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return this;
    }

    @Override
    public HashMap<String, File> getInputCase() {
        HashMap<String, File> testCase = new HashMap<>();
        Arrays.stream(testFiles)
                .filter((file) -> file.getName().startsWith("input"))
                .forEach(file -> {
                    String fileName = file.getName();
                    String testSeq = fileName.substring(fileName.indexOf("input") + "input".length(), fileName.lastIndexOf("."));
                    testCase.put(testSeq, file);
                });
        return testCase;
    }

    @Override
    public HashMap<String, String> getResultCase() {
        HashMap<String, String> result = new HashMap<>();
        try {
            Arrays.stream(testFiles)
                    .filter((file) -> file.getName().startsWith("result"))
                    .forEach(file -> {
                        String fileName = file.getName();
                        String testSeq = fileName.substring(fileName.indexOf("result") + "result".length(), fileName.lastIndexOf("."));
                        StringBuilder resultSb = new StringBuilder();
                        try {
                            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
                            String line = "";
                            while ((line = br.readLine()) != null) {
                                resultSb.append(line).append(System.lineSeparator());
                            }

                            result.put(testSeq, resultSb.toString().trim());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    @Override
    public String solve(File file) throws Exception {
        InputStream parameter = new FileInputStream(file);
        System.setIn(parameter);
        ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream();
        PrintStream resultSave = new PrintStream(resultOutputStream);
        PrintStream resultConsole = System.out;

        System.setOut(resultSave);

        // Solution class 변수 초기화를 위해 solve 마다 새로운 instance 생성
        Constructor constructor = answer.getClass().getConstructor();
        Object instance = constructor.newInstance();
        instance.getClass()
                .getDeclaredMethod("main", String[].class)
                .invoke(instance, (Object) null);
        System.out.flush();

        String testResult = resultOutputStream.toString().trim(); // System.out.println() 으로 정답 입력시 개행문자 제거
        resultOutputStream.close();
        parameter.close();
        System.setOut(resultConsole);
        return testResult;
    }

}

 

class Programmers

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;

public class Programmers<R> implements Problem<Object[], R>{

    Object answer;

    public Problem<Object[], R> setAnswer(Object answer) {
        this.answer = answer;
        return this;
    }

    @Override
    public HashMap<String, Object[]> getInputCase() {
        HashMap<String, Object[]> inputCase = null;

        try {
            String testClassName = answer.getClass().getPackage().toString().split(" ")[1] + ".TestCase";
            Class<?> inputClass = Class.forName(testClassName);

            Constructor<?> constructor = inputClass.getConstructor();
            Object instance = constructor.newInstance();

            inputCase = (HashMap<String, Object[]>) inputClass
                    .getMethod("getInput")
                    .invoke(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return inputCase;
    }

    @Override
    public HashMap<String, R> getResultCase() {

        HashMap<String, R> resultCase = null;

        try {
            String testClassName = answer.getClass().getPackage().toString().split(" ")[1] + ".TestCase";
            Class<?> testClass = Class.forName(testClassName);

            Constructor<?> constructor = testClass.getConstructor();
            Object instance = constructor.newInstance();

            resultCase = (HashMap<String, R>) testClass
                    .getMethod("getResult")
                    .invoke(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return resultCase;
    }

    @Override
    public R solve(Object[] parameter) throws Exception {
        Method[] methods = answer.getClass().getDeclaredMethods();

        // Solution class 변수 초기화를 위해 solve 마다 새로운 instance 생성
        Constructor constructor = answer.getClass().getConstructor();
        Object instance = constructor.newInstance();

        R result = null;
        for(Method method : methods){
            if(method.getName().equals("solution")){
                result = (R) method.invoke(instance, parameter);
            }
        }
        return result;
    }

}

 

input 되는 파라미터가 여러 개인 경우도 있기 때문에 Object 배열을 사용하였다.

아래는 테스트 실행 방법 예시이다.

 

class TestMain

import programmers.LV2_12899.Solution;

public class TestMain {

    public static void main(String[] args) throws Exception {

        BaekJoon baekJoon = new BaekJoon();
        Programmers<String> programmersReturnString = new Programmers<>();
        Programmers<Integer> programmersReturnInt = new Programmers<>();

        // 소수 찾기 (Level 2)
        // https://school.programmers.co.kr/learn/courses/30/lessons/42839
        programmersReturnInt.setAnswer(new programmers.LV2_42839.Solution()).test();

        // 어린왕자 (Silver 3)
        // https://www.acmicpc.net/problem/1004
        baekJoon.setAnswer(new baekjoon.S3_2606.Main()).test();

        // 바이러스 (Level 3)
        // https://www.acmicpc.net/problem/2606
        baekJoon.setAnswer(new baekjoon.S3_1004.Main()).test();
    }

}

 

TestCase - 프로그래머스(class로 작성)

package programmers.LV2_42839;

import java.util.HashMap;

public class TestCase {

    public HashMap<String, Object[]> getInput(){
        HashMap<String, Object[]> testCase = new HashMap<>();
        String numbers1 = "17";
        testCase.put("case1", new Object[]{numbers1});

        String numbers2 = "011";
        testCase.put("case2", new Object[]{numbers2});

        String numbers3 = "143";
        testCase.put("case3", new Object[]{numbers3});
        return testCase;
    }

    public HashMap<String, Object> getResult(){
        HashMap<String, Object> resultCase = new HashMap<>();
        resultCase.put("case1", 3);
        resultCase.put("case2", 2);
        resultCase.put("case3", 6);
        return resultCase;
    }

}

 

TestCase - 백준

input: (input + 케이스 숫자.txt 파일에 케이스 복사 및 붙여넣기)
result: (result + 케이스 숫자.txt 파일에 케이스 복사 및 붙여넣기)

 

input1.txt

2
-5 1 12 1
7
1 1 8
-3 -1 1
2 2 2
5 5 1
-4 5 1
12 1 1
12 1 2
-5 1 5 1
1
0 0 2

 

result1.txt

 

3
0

 

정답 class의 경우 실제 제출할 때와 똑같이 작성하면 된다. 단 IDE에서 작성할 경우 현재 패키지가 import 된 구문이 있으므로, 문제를 해결한 이 후 패키지 명만 제거한 후 제출하면 된다.

 

Algorithm-GroupStudy(github.com)
에서 실제로 본인이 사용한 코드를 확인 가능하다.

'Code' 카테고리의 다른 글

알고리즘 코드 Java로 GPT 생성  (0) 2024.10.04
백준 문제 실행 테스트 환경(JAVA) 만들기  (0) 2024.05.15

실행중인 연결

netstat -a -o

 

 

죽이기

taskkill /f /pid "원하는PID"

 

ArrayList 의 contains 함수는 해당 값이 현재 List 에 있는지 확인하는 함수이다.
그런데 알고리즘 문제를 푸는 중 int[] 타입을 Arrays.asList를 이용하여 변환 후 ArrayList의 contains 함수를 이용하여 int 타입의 값이 확인이 안되는 것이었다. 확인해본 결과 contains 내부에서 값을 체크할 시 equals 함수를 사용하고 있었다.

 

public boolean contains(Object o) {  
    return indexOf(o) >= 0;  
}

public int indexOf(Object o) {  
    return indexOfRange(o, 0, size);  
}  

int indexOfRange(Object o, int start, int end) {  
    Object[] es = elementData;  
    if (o == null) {  
        for (int i = start; i < end; i++) {  
            if (es[i] == null) {  
                return i;  
            }  
        }    } else {  
        for (int i = start; i < end; i++) {  
            if (o.equals(es[i])) {   // 바로 이곳
                return i;  
            }  
        }    }    return -1;  
}

 

LinkedList 의 경우 Node 의 next 노드와 equals 함수를 이용해서 contains 상태를 확인하고 있었다. Arrays.asList 함수의 경우 int[] 타입을 변환 시킨 것이다 보니 값들이 원시타입이어서 equals 함수를 이용할 수 없어 false 가 리턴된 것으로 예상된다.

 

// int[] arr 일 경우
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());

 

원시타입이 아닌 Integer 타입으로 위와 같이 변환하여 사용하는 것으로 해결하였다.

이 글을 포스트 한 이후 프로그래머스 문제를 실행 테스트 하기 위하여 코드를 리팩토링 하였다. 코딩 테스트를 준비하는 사람일 경우 프로그래머스 또한 연습해야 하는 사이트임에 분명하므로, 이 글이 아닌 링크 걸린 글을 확인하길 바란다.

[백준, 프로그래머스 문제 실행 테스트환경 (JAVA) 만들기 :: 꿀잠 (tistory.com)](https://ygs3004.tistory.com/17)

 


 

코딩테스트, 알고리즘을 위한 사이트들이 있는데 그 중 내가 이용하는 것은 프로그래머스와 백준 사이트이다.

프로그래머스의 경우 자체적으로 코드 실행을 할 수도 있고, 코드 실행중에 System.out.println 을 이용하여 디버깅도 나름 가능하다.

 

하지만 백준의 경우 디버깅을 하기가 힘들다. 또한 문제를 풀다가 왜 틀렸는지 알 수 없어 질문하기 쪽을 보다 보니 input 데이터에 공백이 들어가 있는 것 같다는 대답을 들은 적도 있다. 실제로 공백을 처리하는 로직을 추가하였더니 통과된 경험도 있다.

 

그래서 공백까지 들어가있는 테스트 데이터를 만들거나, InputStream 으로 input 데이터가 들어오는 문제를 실행 및 테스트를 할 수 있는 코드를 만들었고 이를 공유하고자 이 글을 포스트한다. 해당 코드는 아래와 같다.

 

import java.io.*;  
import java.lang.reflect.InvocationTargetException;  
import java.net.URL;  
import java.util.Arrays;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.concurrent.atomic.AtomicBoolean;  

public class Main {  

    public static void main(String[] args) throws Exception {  
        test(new Form());  
    }  

    private static void test(Object problem) throws Exception {  
        URL classDir = problem.getClass().getResource("");  
        File[] files = new File(classDir.toURI()).listFiles();  

        // Result 저장  
        Map<String, String> result = new HashMap();  
        Arrays.stream(files).filter((file) -> file.getName().startsWith("result")).forEach(file -> {  
            String fileName = file.getName();  
            String testSeq = fileName.substring(fileName.indexOf("result") + "result".length(), fileName.lastIndexOf("."));  
            StringBuilder resultSb = new StringBuilder();  
            try {  
                BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));  
                String line = "";  
                while((line = br.readLine()) != null){  
                    resultSb.append(line).append(System.lineSeparator());  
                }  

                result.put(testSeq, resultSb.toString().trim());  
            } catch (FileNotFoundException e) {  
                throw new RuntimeException(e);  
            } catch (IOException e) {  
                throw new RuntimeException(e);  
            }  
        });  

        AtomicBoolean isSuccess = new AtomicBoolean();  
        isSuccess.set(true);  

        // Input 실행  
        Arrays.stream(files).forEach(file -> {  
            String fileName = file.getName();  
            boolean isInput = fileName.startsWith("input");  
            if(isInput){  
                String testSeq = fileName.substring(fileName.indexOf("input") + "input".length(), fileName.lastIndexOf("."));  
                System.out.println("** " + testSeq + "번 테스트 실행 **");  
                try {  
                    long startTime = System.nanoTime();  
                    String testResult  
                            = (String) problem.getClass()  
                            .getDeclaredMethod("solution", InputStream.class)  
                            .invoke(problem, new FileInputStream(file));  
                    long endTime = System.nanoTime();  
                    System.out.println("실행시간: " + (endTime-startTime)/1_000_000.00 +"ms");  
                    System.out.println();  

                    String expectResult = result.get(testSeq);  
                    System.out.println("정답");  
                    System.out.println(expectResult);  
                    System.out.println();  

                    System.out.println("결과");  
                    System.out.println(testResult);  
                    System.out.println();  

                    if(testResult.equals(expectResult)){  
                        System.out.println(testSeq + "번 테스트 성공");  
                        isSuccess.set(isSuccess.get());  
                    }else{  
                        System.out.println(testSeq + "번 테스트 실패");  
                        isSuccess.set(false);  
                    }  

                } catch (IllegalAccessException | FileNotFoundException e) {  
                    throw new RuntimeException(e);  
                } catch (NoSuchMethodException e) {  
                    System.out.println("solution 메서드가 없습니다");  
                    throw new RuntimeException(e);  
                } catch (InvocationTargetException e) {  
                    e.printStackTrace();  
                    throw new RuntimeException(e);  
                }  
                System.out.println("=======================================================================================");  
            }  
        });  

        if(isSuccess.get()){  
            System.out.println("모든 테스트가 통과하였습니다.");  
        }else{  
            System.out.println("************** 실패한 테스트가 있습니다. 결과를 확인해주세요 **************");  
        }  
    }  

}


 

 

해당 코드를 활용하기 위해서는 우선 문제 풀이를 작성할 Form 클래스(클래스 이름은 문제마다 또는 원하는 형태)를 하나 만든다.

 

import java.io.BufferedReader;  
import java.io.InputStream;  
import java.io.InputStreamReader;  

public class Form {  

    public String solution(InputStream systemIn) throws Exception{  
        BufferedReader br = new BufferedReader(new InputStreamReader(systemIn));  
        StringBuilder result = new StringBuilder();  

        // 문제풀이코드

        System.out.println(result);  
        return result.toString();  
    }  

}

 

문제 풀이 코드는 위와 같은 형태로 작성한다. 테스트 데이터는 input1.txt, result1.txt 형태로(input/result + 케이스 번호) 텍스트 파일을 만들어 문제 풀이 클래스와 동일한 디렉토리에 저장한다. 아래와 같은 형태면 된다.

프로젝트 루트
├── package1
│ ├── 문제1 Class
│ └── input1.txt
│ └── input2.txt
│ └── input3.txt
│ └── result1.txt
│ └── result2.txt
│ └── result33.txt
├── package2
│ ├── 문제2 Class
│ └── input1.txt
│ └── result1.txt
└── Main

 

        if(isSuccess.get()){  
            System.out.println("모든 테스트가 통과하였습니다.");  
        }else{  
            System.out.println("************** 실패한 테스트가 있습니다. 결과를 확인해주세요 **************");  
        }  

 

테스트 코드의 성공 여부에 따라 위와같은 멘트가 콘솔창에 출력된다.
폴더 구조 및 출력멘트는 Main 클래스에서 입맛에 맞게 변경하면 될 것이다.

백준에 제출하기 전에 내 PC 에서 실행하던 클래스를 적절히 변경 후 제출하면 된다.

 

// 실행환경 패키지 이름 제거

import java.io.BufferedReader;  
import java.io.InputStream;  
import java.io.InputStreamReader;  

// public class Form {  임의의 클래스명 -> Main
public class Main {  

    // public String solution(InputStream systemIn) throws Exception{ 
    public static void main(String[] args) throws Exception{  

        // systemIn => System.in
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
        StringBuilder result = new StringBuilder();  

        // 문제풀이코드

        System.out.println(result);  
        // return result.toString();  
    }  

}

 

  1. package 이름 제거
  2. 클래스 이름 Main 으로 변경,
  3. main 메서드 이름 변경
  4. return 제거 후 정답 코드 출력(System.out.println 이 아니라 BufferWriter 등을 사용해도 된다.)

이후 런타임 오류가 없는데 문제가 틀린다면 문제를 다시 풀어보도록 하면된다.
열공하는 사람 모두 화이팅!

프로젝트에서 인터페이스 관련한 부분을 유지보수 할 때 알게된 내용이다.
정해진 데이터를 순서대로 테이블에 INSERT 해야 하는 상황이었다.

그런데 자꾸 하나의 트랜잭션에서 for문 돌면서 순서대로 insert 하는 데이터가 순서대로 들어가지 않는 것이었다.

A B C D
1 1 2 2
2 1 1 3
3 1 1 1

 

대략 위와 같은 데이터를 INSERT 하는 상황이었다고 가정하고 설명하겠다.
분명히 순서대로 데이터를 INSERT 문을 실행하였고, Spring 로그에 찍힌 ISNERT 문도 A 데이터 기준으로 1,2,3 순서대로 되었는데 말이다.

 

당시 입력되는 순서를 보고 테스트해주시던 분이 다른 데이터 기준으로 입력되는 것 같다고 말씀해주셨다. A 데이터 기준으로 말하자면 3->1->2 순서로 입력되고 있었다.

도대체 어찌된 일인지 테이블의 구성을 살펴보던 중 예상되는 이유를 발견하였고,
해당 부분을 수정하고 테스트한 결과 원하는 순서대로 INSERT 할 수 있었다.


그 이유는 바로 테이블 키에 있었다. 당시 해당 테이블엔 별도의 PK 는 없었고, 유니크 키만 B,C,D의 복합키로 걸려있었다.

데이터가 실제로 입력되던 3->1->2(A 기준) 이 B -> C -> D 의 키 순으로 정렬되어 INSERT 되고 있다면 딱 설명이 되는 상황이었던 것이다.

 

실제로 해당 테이블의 키 설정을 A 컬럼을 PK로 하여 다시 설정한 후에는 원하는 순서대로 INSERT 할 수 있게 되었다.

해당 상황은 인덱스와 관련이 있지 않을까 생각된다. 인덱스 관련하여 학습 했을 때 데이터베이스는 인덱스를 기준으로 정렬한 테이블을 저장된다는 내용을 본 적이있다. 트랜잭션에서 데이터들을 해당 테이블에 INSERT 할 때 PK 값이 없다보니 유니크키에 걸린 인덱스를 기준으로 정렬해서 INSERT 된것으로 예상된다.

PK는 설정시 인덱스를 기본적으로 생성한다. 따라서 테이블에 PK를 설정한 이후에는 PK 인덱스를 기준으로 순서대로 INSERT 된 것으로 판단된다.


얼마전에 DBeaver 툴을 이용해서 데이터를 수정하는데 자꾸 다른 row가 수정되는 것이었다.
분명히 나는 한 row를 수정했는데 테이블 전체의 데이터가 자꾸 변경되는 것이었다.

그랬던 이유를 결국 찾게 되었는데 바로 키때문이었다. 당시 테이블에 키 설정을 새로 하려고 기존에 있던 키를 제거한 상황이었다. 테이블 전체에 아무런 키 설정이 되어있지 않다보니 DBeaver 툴에서 Update를 실행할 때 내가 수정한 row가 어느 row 인지 인식을 못하는 것이었다. 새로운 키를 다시 생성하자 정상적으로 Update가 되었다.

DBeaver 최초 설치시 JDBC 드라이버를 설정해주는 걸 생각해보면, 결국 DBeaver 툴이 데이터를 수정할때는 DB 자체를 수정하는 것이 아니라 JDBC를 통해 DB를 조작하는 것으로 예상된다. 

내부적으로 where 1=1 + key를 이용한 조건을 쓰는걸까? ㄷㄷ..
DBeaver 툴 무료 버젼으로도 상당히 편하게 잘 쓸 수 있지만 키 식별이 안되어 update row를 확인할 수 없다면 update가 안되어야 하는 것이 아닌가 생각이 든다.

좋은 툴이지만 조심해서 써야겠다.

+ Recent posts