Scott 과 함께 - Outer Join 을 Java 로 구현하기.

Outer Join

Outer Join with Java

 이번 Post는 Outer Join을 Java로 구현한다. 이전 Inner Join Post와 동일하게 부서의 Job 별 평균급여를 구하지만, 이번에는 직원이 없는 부서도 같이 출력해야 한다.

SQL

부서/Job 평균 급여에 Operations 부서도 추가.

 Operations 부서는 직원이 없는 부서라서, Job과 평균급여는 Null이다. Operations의 Job은 Null이고 평균급여는 0으로 Java에서 표현한다.

select d.dname,
       e.job,
       avg(e.sal)
  from dept d
       left outer join emp e
       on d.deptno = e.deptno
 where 1=1
 group by d.dname,
          e.job;

Java

부서 Stream + 직원 Stream

 처음 생각했던 방법은 부서 Stream의 map()에서 직원 Stream으로 Job별 평균급여를 가져오려고 했지만 실패했다. Operations의 직원이 없어 직원 Stream의 결과가 Null이면, Operation이 부서 Stream에서 사라졌다.

depts.stream()
    .map(department -> employees.stream()
          .filter(employee -> employee.getDepartment() == department)
          .collect(Collectors.groupingBy(...))
    );

부서 List + 부서/Job 평균급여 Map

Outer Join
Outer Join 전략


 다음으로 생각한 방법은 DB의 Outer Join 방법이다. 먼저 Driving Table을 만들기 위해서 전체 부서 List를 만들었다. Driving Table은 조인을 주도하는 Table이다.

@Test
public void outerJoin_Stream() {
  String sql;
  sql = "select d from Department d";
  List<Department> depts = entityManager.createQuery(sql, Department.class)
      .getResultList();
  ...
}

 다음은 Driven Table인 부서/Job 평균급여 Map을 만든다. Driven Table은 Driving Table의 조건으로 Scan하는 Table이다.

@Test
public void outerJoin_Stream() {
  ...
  sql = "select e from Employee e";
  List<Employee> employees = entityManager.createQuery(sql, Employee.class)
      .getResultList();

  Map<Department, Map<EmployeeJob, Double>> results = employees.stream()
      .collect(Collectors.groupingBy(Employee::getDepartment,
          Collectors.groupingBy(Employee::getJob, Collectors.averagingDouble(Employee::getSalary))));
  ...
}

 마지막으로 부서 List로 부서/Job 평균급여 Map을 조회한다. 여기서 문제점이 하나 있다. Map에는 Operation이 존재하지 않기 때문에, get() 호출 시 NullPointException이 발생한다. 그래서 Null처리를 위해 ifPresentOrElse()을 사용했다. ifPresentOrElse()은 Null 여부에 따른 처리를 다르게 할 수 있다.

@Test
public void outerJoin_Stream() {
  ...
  for (Department dept : depts) {
    Optional.ofNullable(results.get(dept))
        .ifPresentOrElse((employeeJobDoubleMap) -> {
              // ....
            },
            () -> {
              System.out.printf("%10s | %10s : ", dept.getName(),
                  "Null");
            });
  }
}

결과


마무리

 이번 Post는 Outer Join을 Java로 구현해봤다. Java로 DB와 다르게 구현할 수 있다고 생각했지만, 결국 똑같이 만들었다. 뭔가 추가적인 방법이 있다면 Update하겠다. 소스는 Github에 공유했다.

참조

댓글

이 블로그의 인기 게시물

JPA 와 함께 - 느낀점

Scott 과 함께 - Recursive Query 구현하기