본문 바로가기

Programming Language/Java

Java group key class로 stream groupingBy 사용하기

백엔드를 개발할때 DB 에서 GroupBy 를 할때가 있는데 간단한 GroupBy연산의 경우 Java 단에서 하면 DB부담을 줄여주고 효율적인 개발을 할 수 있다. java단에서 간단한 로직으로 grouping 을 할수 있으나 key가 여럿일 경우에는 다중 loop가 발생하게 된다.

 

java 1.8의 stream + groupingBy기능을 활용하면 좀더 편하게 할수 있고 GroupingKey를 나타내는 class를 하나 사용하면 좀더 간결하게 groupingBy를 수행할 수 있다.

 

 

Student 클래스

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Builder
@Getter
@Setter
public class Student {

    int no;

    String name;

    String ageCode;

    String sexCode;

    String subject;

    GroupKey groupKey;
}

 

Grouping 에 활용될 GroupKey 클래스

@Builder
@Getter
@Setter
public class GroupKey {

    String ageCode;

    String sexCode;

    String subject;

    @Override
    public int hashCode() {
        return this.ageCode.length() + this.sexCode.length() + this.subject.length();
    }

    @Override
    public boolean equals(Object obj) {
        GroupKey other = (GroupKey) obj;

        if (other.getSubject().equals(this.subject)
                && other.getSexCode().equals(this.sexCode)
                && other.getAgeCode().equals(this.ageCode)) {
            return true;
        }

        return false;
    }
}

위 GroupKey클래스에서 염두해야 할 것은 Stream groupingBy에서 활용할 equals, hashCode 함수를  override해주어야 한다. equals에 groupKey가 모두 같을때 비교 true 임을 명시해준다. equals를 override했으면  hashCode도 반드시 override해주어야 한다. 만일 없으면 groupingBy가 동작하지 않는다.

 

원래는 hashCode는 객체 비교를 위해 유일값이 나오도록 구현해야 하나 본 예제에서는 객체간 비교는 없기 때문에 간단한 코드로 중복고려 없이 리턴하도록 하였다.

 

 

테스트 코드

import static java.util.stream.Collectors.groupingBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class TestStreamGrouping {

    @Test
    public void name() {
        List<Student> students = Lists.newArrayList(
                Student.builder().no(1).name("StudentName1").ageCode("001").sexCode("1").subject("A01").build(),
                Student.builder().no(2).name("StudentName2").ageCode("001").sexCode("1").subject("A01").build(),
                Student.builder().no(3).name("StudentName3").ageCode("002").sexCode("1").subject("A02").build(),
                Student.builder().no(4).name("StudentName4").ageCode("002").sexCode("2").subject("A03").build(),
                Student.builder().no(5).name("StudentName5").ageCode("003").sexCode("1").subject("A04").build()
        );

        List<Student> studentsWithGroupsKey = Optional.ofNullable(students).map(list -> list.stream()
                .peek(student -> student.setGroupKey(GroupKey.builder()
                        .ageCode(student.getAgeCode())
                        .sexCode(student.getSexCode())
                        .subject(student.getSubject()).build()))
                .collect(Collectors.toList())).orElse(Lists.newArrayList());

        Map<GroupKey, List<Student>> collectResult = studentsWithGroupsKey.stream().collect(groupingBy(Student::getGroupKey));

        assertThat(collectResult).isNotNull();
    }
}

 

위 테스트 케이스 결과를 찍어보면 아래와 같이 결과가 제대로 나옴을 볼 수 있다.

ageCode = "001", sexCode = "1", subject = "A01" 에 대해서 count  2 개로 그룹핑이되서 결과가 나온것을 볼 수 있다.

collectResult = {HashMap@1058}  size = 4
 {GroupKey@1079}  -> {ArrayList@1080}  size = 1
  key = {GroupKey@1079} 
   ageCode = "003"
   sexCode = "1"
   subject = "A04"
  value = {ArrayList@1080}  size = 1
 {GroupKey@1081}  -> {ArrayList@1082}  size = 1
  key = {GroupKey@1081} 
   ageCode = "002"
   sexCode = "2"
   subject = "A03"
  value = {ArrayList@1082}  size = 1
 {GroupKey@1083}  -> {ArrayList@1084}  size = 1
  key = {GroupKey@1083} 
   ageCode = "002"
   sexCode = "1"
   subject = "A02"
  value = {ArrayList@1084}  size = 1
 {GroupKey@1085}  -> {ArrayList@1086}  size = 2
  key = {GroupKey@1085} 
   ageCode = "001"
   sexCode = "1"
   subject = "A01"
  value = {ArrayList@1086}  size = 2

 

-- The end --