[오류 해결] 스프링 싱글톤 방식 설계 시 공유 필드로 인한 에러

서규범·2022년 11월 5일
0

유튜브 댓글 분석 서비스 프로젝트를 진행하던 중, 싱글톤 방식의 설계가 잘못되어 분석을 계속해서 실행할 때마다 이전 분석 값이 누적되어 출력되는 현상이 발생하였다.

Flask 서버에서 분석된 결과를 JSON 형태로 받아와 통계를 추출하여 Front 서버에 전달하는 형식인데 Service 단에서 문제가 발생한 것 같았다.

ResponseJsonController.java

public class ResponseJsonController {
	private final CommentService commentService;
    ...
    
    public KeywordDTO getKeyword(@PathVariable String url, Model model) {
        String KeywordBaseUrl = "http://localhost:5000/keywords?url=" + url;
        RestTemplate KeywordRestTemplate = new RestTemplate();
        ResponseEntity<KeywordDTO> KeywordResponse = KeywordRestTemplate.getForEntity(KeywordBaseUrl, KeywordDTO.class);
        ...
        
        HashMap<String, List> commentMap = commentService.classifyComment(comments);
        HashMap<String, Double> positiveNegativePercentMap = commentService.positiveNegativePercent();
        HashMap<String, Double> sentimentPercentMap = commentService.sentimentPercent();
        ...
}
  • Controller에서 CommentService를 수행해, HashMap 형태로 데이터를 반환 받는 과정이다.



CommentService.java

@Service
public class CommentService {

    private List<CommentDTO> positiveComments = new ArrayList<>(); // json 구분 인덱스 : 1
    private List<CommentDTO> negativeComments = new ArrayList<>(); // json 구분 인덱스 : 0
    private List<CommentDTO> fearComments = new ArrayList<>(); // json 구분 인덱스 : 2
    private List<CommentDTO> surprisedComments = new ArrayList<>(); // json 구분 인덱스 : 3
    private List<CommentDTO> angerComments = new ArrayList<>(); // json 구분 인덱스 : 4
    private List<CommentDTO> sadnessComments = new ArrayList<>(); // json 구분 인덱스 : 5
    private List<CommentDTO> neutralComments = new ArrayList<>(); // json 구분 인덱스 : 6
    private List<CommentDTO> happyComments = new ArrayList<>(); // json 구분 인덱스 : 7
    private List<CommentDTO> disgustComments = new ArrayList<>(); // json 구분 인덱스 : 8
    
    
    public HashMap<String, List> classifyComment(CommentDTO[] comments){
    	HashMap<String, List> commentMap = new HashMap<>();
        for(int i = 0; i< comments.length; i++){    //인덱스 번호를 통해서 긍정, 부정 , 감정 댓글 분류
            String index = comments[i].getIndex();
            switch (index){
                case "0" :
                    negativeComments.add(comments[i]);
                    break;
                case "1" :
                    positiveComments.add(comments[i]);
                    break;
                case "2" :
                    fearComments.add(comments[i]);
                    break;
                case "3" :
                    surprisedComments.add(comments[i]);
                    break;
                case "4" :
                    angerComments.add(comments[i]);
                    break;
                case "5" :
                    sadnessComments.add(comments[i]);
                    break;
                case "6" :
                    neutralComments.add(comments[i]);
                    break;
                case "7" :
                    happyComments.add(comments[i]);
                    break;
                case "8" :
                    disgustComments.add(comments[i]);
                    break;
            }
        commentMap.put("positiveComments",positiveComments);
        commentMap.put("negativeComments",negativeComments);
        commentMap.put("fearComments",fearComments);
        commentMap.put("surprisedComments",surprisedComments);
        commentMap.put("angerComments",angerComments);
        commentMap.put("sadnessComments",sadnessComments);
        commentMap.put("neutralComments",neutralComments);
        commentMap.put("happyComments",happyComments);
        commentMap.put("disgustComments",disgustComments);
        return commentMap;

    }
    
    public HashMap<String, Double> positiveNegativePercent(){
    
    	HashMap<String, Double> positiveNegativePercentMap = new HashMap<>();

        List positiveComments = map.get("positiveComments");
        List negativeComments = map.get("negativeComments");

        double positivePercent = ((double)positiveComments.size() / ((double)positiveComments.size()+(double)negativeComments.size()))*100;
        double negativePercent = ((double)negativeComments.size() / ((double)positiveComments.size()+(double)negativeComments.size()))*100;
    }
    
    public HashMap<String, Double> sentimentPercent(){
    	
        HashMap<String, Double> sentimentPercentMap = new HashMap<>();
        
        double emTotalSize = fearComments.size()+ surprisedComments.size()+ angerComments.size()+ sadnessComments.size()+ neutralComments.size()+ happyComments.size()+ disgustComments.size();
        double fearPercent = (((double)fearComments.size()/emTotalSize)*100);
        double surprisedPercent = ((double)surprisedComments.size()/emTotalSize)*100;
    }
    ...
  • 초기에는 Service 계층에서 필드를 공유하는 방식으로 구현하였다. classifyComment() 를 수행하여 분류된 댓글들을, 긍정 부정 분류 및 감정 분류를 실행해 데이터를 Controller 계층에 반환하는 형식이었다.
    하지만 이 경우, Service 계층에 요청이 여러번 들어올 경우 분석 결과가 누적이 되는 치명적인 에러가 발생했다.



====================수정 후 코드=================

ResponseJsonController.java

public class ResponseJsonController {
	private final CommentService commentService;
    ...
    
    public KeywordDTO getKeyword(@PathVariable String url, Model model) {
        String KeywordBaseUrl = "http://localhost:5000/keywords?url=" + url;
        RestTemplate KeywordRestTemplate = new RestTemplate();
        ResponseEntity<KeywordDTO> KeywordResponse = KeywordRestTemplate.getForEntity(KeywordBaseUrl, KeywordDTO.class);
        ...
        
        HashMap<String, List> commentMap = commentService.classifyComment(comments);
        HashMap<String, Double> positiveNegativePercentMap = commentService.positiveNegativePercent(commentMap);
        HashMap<String, Double> sentimentPercentMap = commentService.sentimentPercent(commentMap);
        ...
}
  • Controller에서 commentService를 호출할 때, 분류된 댓글 데이터를 넘겨주는 방식으로 구현하였다.


CommentService.java

@Service
public class CommentService {
 
    public HashMap<String, List> classifyComment(CommentDTO[] comments){
    
    	HashMap<String, List> commentMap = new HashMap<>();
        
        List<CommentDTO> positiveComments = new ArrayList<>(); // json 구분 인덱스 : 1
        List<CommentDTO> negativeComments = new ArrayList<>(); // json 구분 인덱스 : 0
        List<CommentDTO> fearComments = new ArrayList<>(); // json 구분 인덱스 : 2
        List<CommentDTO> surprisedComments = new ArrayList<>(); // json 구분 인덱스 : 3
        List<CommentDTO> angerComments = new ArrayList<>(); // json 구분 인덱스 : 4
        List<CommentDTO> sadnessComments = new ArrayList<>(); // json 구분 인덱스 : 5
        List<CommentDTO> neutralComments = new ArrayList<>(); // json 구분 인덱스 : 6
        List<CommentDTO> happyComments = new ArrayList<>(); // json 구분 인덱스 : 7
        List<CommentDTO> disgustComments = new ArrayList<>(); // json 구분 인덱스 : 8
        
        for(int i = 0; i< comments.length; i++){    //인덱스 번호를 통해서 긍정, 부정 , 감정 댓글 분류
            String index = comments[i].getIndex();
            switch (index){
                case "0" :
                    negativeComments.add(comments[i]);
                    break;
                case "1" :
                    positiveComments.add(comments[i]);
                    break;
                case "2" :
                    fearComments.add(comments[i]);
                    break;
                case "3" :
                    surprisedComments.add(comments[i]);
                    break;
                case "4" :
                    angerComments.add(comments[i]);
                    break;
                case "5" :
                    sadnessComments.add(comments[i]);
                    break;
                case "6" :
                    neutralComments.add(comments[i]);
                    break;
                case "7" :
                    happyComments.add(comments[i]);
                    break;
                case "8" :
                    disgustComments.add(comments[i]);
                    break;
            }
        commentMap.put("positiveComments",positiveComments);
        commentMap.put("negativeComments",negativeComments);
        commentMap.put("fearComments",fearComments);
        commentMap.put("surprisedComments",surprisedComments);
        commentMap.put("angerComments",angerComments);
        commentMap.put("sadnessComments",sadnessComments);
        commentMap.put("neutralComments",neutralComments);
        commentMap.put("happyComments",happyComments);
        commentMap.put("disgustComments",disgustComments);
        return commentMap;

    }
    
    public HashMap<String, Double> positiveNegativePercent(HashMap<String, List> map){
    
    	HashMap<String, Double> positiveNegativePercentMap = new HashMap<>();

        List positiveComments = map.get("positiveComments");
        List negativeComments = map.get("negativeComments");

        double positivePercent = ((double)positiveComments.size() / ((double)positiveComments.size()+(double)negativeComments.size()))*100;
        double negativePercent = ((double)negativeComments.size() / ((double)positiveComments.size()+(double)negativeComments.size()))*100;
    }
    
     public HashMap<String, Double> sentimentPercent(HashMap<String, List> map){
    	
        HashMap<String, Double> sentimentPercentMap = new HashMap<>();
        
        double emTotalSize = fearComments.size()+ surprisedComments.size()+ angerComments.size()+ sadnessComments.size()+ neutralComments.size()+ happyComments.size()+ disgustComments.size();
        double fearPercent = (((double)fearComments.size()/emTotalSize)*100);
        double surprisedPercent = ((double)surprisedComments.size()/emTotalSize)*100;
    }
    ...
  • 기존 Service 계층 내에서 공유되는 필드들을 모두 삭제하고, classifyComment() 함수 내에서 지역 변수로 활용하도록 구현하였다. 이후에 긍,부정 분류 및 감정 분류 함수의 경우 외부에서 댓글 데이터를 주입받아 데이터를 반환 해주는식으로 구현하여, 필드 상태에 영향을 받지 않는 형식으로 개발하였다.

결론

  • 공유 필드를 사용함으로써, 싱글톤 방식 설계에 치명적인 오류가 발생
  • 스프링 빈에 공유 필드를 사용해선 안됨
  • 외부에서 데이터를 주입받는 방식으로, Service 계층은 단순히 해당 로직만 수행해야 함
profile
하려 하자

0개의 댓글