Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

코딩블로그

[Spring] Reflection 사용하여 중복 코드 줄여보기 본문

카테고리 없음

[Spring] Reflection 사용하여 중복 코드 줄여보기

_hanbxx_ 2024. 5. 15. 16:29
728x90

에휴..보이시나요 이 중복되고 드러운 코드 구조를?

    public void incrementPositiveCineMaster(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveCineMaster(popcorn);
    }

    public void incrementPositiveGreatFilming(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveGreatFilming(popcorn);
    }

    public void incrementPositivePom(Popcorn popcorn) {
        popcornQueryRepository.incrementPositivePom(popcorn);
    }

    public void incrementPositiveAnimationIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveAnimationIsGood(popcorn);
    }

    public void incrementPositiveArtIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveArtIsGood(popcorn);
    }

    public void incrementPositiveCustom(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveCustom(popcorn);
    }

    public void incrementPositiveMusic(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveMusic(popcorn);
    }

    public void incrementPositiveTopicIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveTopicIsGood(popcorn);
    }

    public void incrementPositiveLinesAreGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveLinesAreGood(popcorn);
    }

    public void incrementPositiveEndingIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveEndingIsGood(popcorn);
    }

    public void incrementPositiveCastingIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveCastingIsGood(popcorn);
    }

    public void incrementPositiveActingIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveActingIsGood(popcorn);
    }

    public void incrementPositiveChemistryIsGood(Popcorn popcorn) {
        popcornQueryRepository.incrementPositiveChemistryIsGood(popcorn);
    }

    public void incrementNegativeIffy(Popcorn popcorn) {
        popcornQueryRepository.incrementNegativeIffy(popcorn);
    }

    public void incrementNegativeBadEditing(Popcorn popcorn) {
        popcornQueryRepository.incrementNegativeBadEditing(popcorn);
    }

    @Transactional
    public void incrementNegativeBadAngle(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badAngle,
                        qpopcorn.popcornNegativeCount.badAngle.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadDetail(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badDetail,
                        qpopcorn.popcornNegativeCount.badDetail.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadColor(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badColor,
                        qpopcorn.popcornNegativeCount.badColor.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadCustom(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badCustom,
                        qpopcorn.popcornNegativeCount.badCustom.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadMusic(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badMusic,
                        qpopcorn.popcornNegativeCount.badMusic.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadSound(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badSound,
                        qpopcorn.popcornNegativeCount.badSound.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadEnding(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badEnding,
                        qpopcorn.popcornNegativeCount.badEnding.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeEndingLoose(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.endingLoose,
                        qpopcorn.popcornNegativeCount.endingLoose.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeNoDetail(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.noDetail,
                        qpopcorn.popcornNegativeCount.noDetail.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadTopic(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badTopic,
                        qpopcorn.popcornNegativeCount.badTopic.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadActing(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badActing,
                        qpopcorn.popcornNegativeCount.badActing.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementNegativeBadCasting(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornNegativeCount.badCasting,
                        qpopcorn.popcornNegativeCount.badCasting.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementPositiveSetIsArt(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornPositiveCount.setIsArt,
                        qpopcorn.popcornPositiveCount.setIsArt.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementPositiveOst(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(qpopcorn.popcornPositiveCount.ost, qpopcorn.popcornPositiveCount.ost.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

    @Transactional
    public void incrementPositiveWrittenByGod(Popcorn popcorn) {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        JPAUpdateClause updateClause = jpaQueryFactory.update(qpopcorn);

        updateClause
                .set(
                        qpopcorn.popcornPositiveCount.writtenByGod,
                        qpopcorn.popcornPositiveCount.writtenByGod.add(1))
                .where(qpopcorn.id.eq(popcorn.getId()))
                .execute();
    }

 

 

팝콘메이트에서 영화의 "상영지수"라는 평가 지표를 계산해야 할 때, 사용자가 영화에 대한 리뷰를 작성할 때 "키워드 리뷰"를 함께 제공하며 이를 토대로 상영지수를 업데이트해야 하는 복잡한 로직이 필요하다.

사용자와 영화 간의 중간 엔티티에는 사용자의 키워드 리뷰 선택 여부를 나타내는 키워드 필드가 있으며, 특정 영화 엔티티에는 키워드 개수를 나타내는 필드가 별도로 존재한다.

이런 구조 때문에 사용자의 키워드 리뷰가 도착할 때, 모든 필드의 선택 여부를 if문으로 판단하고 true인 필드와 이름이 같은 키워드 개수 필드를 업데이트하는 로직이 필요했다.

 

이러한 중복 로직을 없애기 위해 자바의 Reflection을 사용해보았다

 

Reflection을 사용하면, 저 위에 코드가 단 두가지 메소드로도 구현이 가능해 진다.

  public void incrementNegative(PopcornNegative negative, Popcorn popcorn) throws NoSuchFieldException, IllegalAccessException {
        popcornQueryRepository.incrementNegative(negative, popcorn);
    }

    public void incrementPositive(PopcornPositive positive, Popcorn popcorn) throws NoSuchFieldException, IllegalAccessException {
        popcornQueryRepository.incrementPositive(positive,popcorn);
    }

 

Repository의 사용된 increment함수를 더 살펴 보면

    public void incrementPositive(PopcornPositive positive, Popcorn popcorn) throws NoSuchFieldException, IllegalAccessException {
        QPopcorn qpopcorn = QPopcorn.popcorn;
        List<String> chosen = getTrueFields(positive);

        for (String fieldName : chosen) {
            try {
                // fieldName에 해당하는 필드를 가져옵니다.
                Field popcornField = qpopcorn.popcornPositiveCount.getClass().getDeclaredField(fieldName);
                popcornField.setAccessible(true); // 필드에 대한 접근을 허용합니다.

                // 현재 필드의 값을 가져옵니다.
                NumberPath<Integer> fieldValue = (NumberPath<Integer>) popcornField.get(qpopcorn.popcornPositiveCount);

                // 값을 1 증가시킵니다.
                jpaQueryFactory.update(qpopcorn)
                        .set(fieldValue, fieldValue.add(1))
                        .where(qpopcorn.id.eq(popcorn.getId()))
                        .execute();
            } catch (NoSuchFieldException | IllegalAccessException e) {
                // 필드를 찾을 수 없거나 접근할 수 없는 경우 예외 처리
                e.printStackTrace();
            }
        }
    }
  • 리플렉션은 클래스 메서드 필드 등의 정보를 동적으로 가져오고 조작할 수 있다
  • getDeclaredField(fieldName) 메서드 : 이를 사용하여 클래스의 필드를 가져온다. 이 메서드는 클래스의 모든 필드를 고려한다.
  • setAccessible(true) 메서드 : 호출하여 필드에 대한 접근을 허용한다. public말고 protected나 private필드를 사용했을 때 꼭 필요한 메서드이다.
  •  NumberPath<Integer>: 이 부분은 가져온 필드의 값을 캐스팅하는 것이다. `popcornField`가 실제로 정수 값을 가지므로 이를 적절한 타입으로 변환하여 사용한다.
  •  fieldValue: 가져온 필드의 값을 저장하는 변수이다. 이 변수는 `NumberPath<Integer>` 타입으로 선언되었으며, 필드의 값이 정수형이라고 가정한다.
    public List<String> getTrueFields(Object keywords) {
        List<String> trueFields = new ArrayList<>();

        // PopcornNegative/Positive 클래스의 모든 필드를 가져와서 순회합니다.
        for (java.lang.reflect.Field field : keywords.getClass().getDeclaredFields()) {
            // 필드의 이름을 가져옵니다.
            String fieldName = field.getName();
            // 필드의 값을 가져옵니다.
            boolean fieldValue = false;
            try {
                field.setAccessible(true);
                fieldValue = field.getBoolean(keywords);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            // 필드의 값이 true이면 리스트에 추가합니다.
            if (fieldValue) {
                trueFields.add(fieldName);
            }
        }

        return trueFields;
    }
  • 사용자가 선택한 키워드 필드이름을 "getTrueFields"라는 메소드를 통해 String List에 저장한다.

 

리플렉션을 남발해서 쓰는 것이 바람직하지는 않지만 이런식으로 중복된 코드가 남발할 때는 적절하게 사용하여 코드의 중복성을 줄이는 데에 리플렉션이 유용한것 같다

 

앞으로 해야할 점

  • 예외 처리를 단순히 간접적으로 사용하는 메소드에서는 add Exception하지 않는 방법이 존재하는지에 대해 생각해보기
  • incrementPositiveCount, incrementNegativeCount의 중복도 줄일 수 있는 방법이 있는지에 대한 방안을 더 생각해보기
  • 예전의 중복된 코드의 쿼리 성능과 현재 바뀐 쿼리 코드의 성능 차이가 얼마나 있는지에 대한 테스트해보기
  • 리플렉션의 단점을 고려하여 인터페이스나 더 좋은 방식이 존재하는지 고려해보기

 

 

728x90