스택

  • 한 쪽 끝에서만 자료를 넣고, 뺄 수 있는 자료구조다.
  • Push로 데이터를 집어넣고, Pop으로 데이터를 빼낸다.
  • 그림 상으로는 스택의 각 인덱스에 있는 데이터를 알 수 있지만, 사실상 제일 위에 있는 데이터만 알 수 있는 자료구조다. 보통 제일 위에 있는 데이터를 top라고 한다.

스택의 구현

  • 일차원 배열 하나로 구현이 가능하다.
public class Stack<T> {
    private T[] data;
    private int size;

    @SuppressWarnings("unchecked")
    public Stack(int capacity) {
        data = (T[])new Object[capacity];
        size = 0;
    }

    public Stack() {
        this(10);
    }

    public void push(T data) {
        this.data[size++] = data;
    }

    public T pop() {
        return this.data[--size];
    }

    public int getSize() {
        return size;
    }
}
  • 1차원 배열 data와, stack의 크기를 알려주는 size를 선언했다.
  • 사용자는 스택의 최대 크기를 지정할 수 있다. 만약 최대 크기를 정해주지 않는다면 최대크기는 default 10이다
  • 앞서 말한 것처럼 push로 데이터를 저장할 수 있다.
    • 예를 들어 처음 push(10) 을 한다면 data[0]10이 저장된다.
    • 후위연산자로 식을 수행한 뒤 size + 1이 된다
  • 앞서 말한 것처럼 pop으로 데이터를 빼낼 수 있다.
    • 예를 들어 배열에 [3,1,6] 이와 같은 값이 저장돼있다면, pop()을 수행할 시 맨 마지막 값인 6을 반환한다.
    • 현재 size는 3이기 때문에 data[3]은 null이다. 따라서 전위연산자로 식을 수행하기 전 size - 1을 한 뒤 값을 빼낸다

아래 내용은 책 모던 자바 인 액션(http://www.yes24.com/Product/Goods/77125987?scode=032&OzSrank=2) 을 정리한것임을 밝힙니다.

코드의 점진적인 발진으로 동적파라미터를 이해해보자

첫번째 요구사항.

사과를 재배하는 농부 후안의 요구사항

  • "제가 수확한 사과 중에 색깔이 "GREEN"인 사과만 수집해주세요
  • FirstStep.class
      public static List<Apple> filterGreenApples(List<Apple> inventory) {
          List<Apple> result = new ArrayList<>();
          for (Apple apple : inventory) {
              if (GREEN.equals(apple.getColor())) {
                  result.add(apple);
              }
          }
          return result;
      }

두번째 요구사항

후안이 또다른 요구를 해왔다.

  • "제가 수확한 사과 중에 무게가 xxx 이상인 것들만 수집해주세요"
  • SecondStep.class
       public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
           List<Apple> result = new ArrayList<>();
           for (Apple apple : inventory) {
               if (apple.getWeight() > weight) {
                   result.add(apple);
               }
           }
           return result;
       }

세번째 요구사항

  • "수확한 사과 중에 색깔이 xxx고, 무게가 yyy 이상인 사과만 수집해주세요"
  • ThirdStep.class
      public static List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag) {
          List<Apple> result = new ArrayList<>();
          for (Apple apple : inventory) {
              if ((flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight)) {
                  result.add(apple);
              }
          }
          return result;
      }

각 요구사항마다 중복 코드가 너무 많다. 해결책?

  • 변화하는 요구사항에 좀 더 유연하게 대응할 수 있는 방법이 있다. Apple의 속성에 기초해 boolean 값을 반환하는 방법이 있다.

    • Predicate 인터페이스를 정의하자.
    • 그 다음 Predicate의 구현체인 AppleGreenColorPredicate, AppleHeavyWeightPredicate 을 만들자.
    • 클라이언트 코드(Main.class)에서 인자값으로 구현체를 사용하면 된다.
      • 전략패턴 찾아볼 것
  • ApplePredicate.class

      public interface ApplePredicate {
        boolean test(Apple apple);
      }
  • AppleGreenColorPredicate.class

      public class AppleGreenColorPredicate implements ApplePredicate{
        @Override
        public boolean test(Apple apple) {
            return GREEN.equals(apple.getColor());
        }
      }
  • AppleHeavyWeightPredicate.class

      public class AppleHeavyWeightPredicate implements ApplePredicate {
        @Override
        public boolean test(Apple apple) {
            return apple.getWeight() > 150;
        }
      }
  • FourthStep.class

      public class FourthStep {
        public static List<Apple> filterApples(List<Apple> apples, ApplePredicate predicate) {
            List<Apple> result = new ArrayList<>();
            for (Apple apple : apples) {
                if (predicate.test(apple)) {
                    result.add(apple);
                }
            }
            return result;
        }
      }
  • Main.class

      public class Main {
        public static void main(String[] args) {
            List<Apple> inventory = Arrays.asList(
                    new Apple(80, Color.GREEN),
                    new Apple(155, Color.GREEN),
                    new Apple(120, Color.RED)
            );
    
            List<Apple> heavyApples = FourthStep.filterApples(inventory, new AppleHeavyWeightPredicate());
            heavyApples.forEach(System.out::println);
    
            List<Apple> greenApples = FourthStep.filterApples(inventory, new AppleGreenColorPredicate());
            greenApples.forEach(System.out::println);
        }
      }

YAML 파일 변수에 매핑하기. (@Value, @ConfigurationProperties)

@Value

application.yml

property:
  test:
    name: property depth test
propertyTest: test
propertyTestList: a,b,c

ValueTest.class

@SpringBootTest
public class AutoConfigurationApplicationTests {

    @Value("${property.test.name}")
    private String propertyTestName;

    @Value("${propertyTest}")
    private String propertyTest;

    @Value("${noKey:default value}")
    private String defaultValue;

    @Value("${propertyTestList}")
    private String[] propertyTestArray;

    @Value("#{'${propertyTestList}'.split(',')}")
    private List<String> propertyTestList;

    @Test
    void testValue() {
        assertEquals("property depth test", propertyTestName);
        assertEquals("test", propertyTest);
        assertEquals("default value", defaultValue);
        assertArrayEquals(new String[]{"a","b","c"}, propertyTestArray);
        assertEquals(List.of("a","b","c"), propertyTestList);
    }
}

@ConfigurationProperties

application.yml

car:
  list:
    - name: Porsche911
      color: red
    - name: K5
      color: black

Car.class

public class Car {

    private String name;
    private String color;

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

CarProperty.class

@Component
@ConfigurationProperties("car")
public class CarProperty {

    private List<Car> list;

    public List<Car> getList() {
        return list;
    }

    public void setList(List<Car> list) {
        this.list = list;
    }
}
  • Car.class, CarProperty.classSetter메서드 또는 Constructor가 필요하다.
  • 바인딩할 변수의 이름과 yaml 파일에 있는 이름이 매치해야한다. (케밥표기법, 소문자 가능)
  • @Value와는 다르게 SpEl은 사용이 불가하다.
  • @Component로 스프링이 관리하게 해야한다

@EntityGraph

fetch join

  • fetch가 LAZY로 되어있어도 연관된 객체를 즉시 조회한다.


  • 위와 같이 Member -> Team 관계를 맺고 있을 때 fetch 옵션이 LAZY라면 Member 엔티티를 조회할 때, 연관된 Team 엔티티는 프록시 객체로 조회된다.

    • 이때 셀렉트 쿼리는 Member만을 찾는 셀렉트쿼리 한번이 나가고, Member.getTeam().getName(); 같이 프록시 객체의 실제값을 호출할 때 그제서야 Team에 대한 쿼리도 나간다.

    • 만약 Team이 두 개라면 N + 1문제가 터진다. 1은 Member, N(2)은 Team.

    • 다음과 같이 fetch 조인으로 Member와 Team을 조인시켜 한방쿼리로 해결한다.

      @Query("select m from Member as m left join fetch m.team")
      List<Member> findMemberFetchJoin();
      
      @EntityGraph(attributePaths = {"team"})
      List<Member> findEntityGraphByUsername(@Param("username") String username);

순수 JPA레포지토리의 페이징과 스프링 데이터 JPA레포지토리의 페이징.

순수 JPA레포지토리

MemberJpaRepository.class

public List<Member> findByPage(int age, int offset, int limit) {
    return em.createQuery("select m from Member as m" +
                " where m.age = :age" +
                " order by m.username asc", Member.class)
            .setParameter("age", age)
            .setFirstResult(offset)
            .setMaxResults(limit)
            .getResultList();
}

MemberJpaRepositoryTest.class

@Transactional
@Test
void paging() {
    memberJpaRepository.save(new Member("member1", 10));
    memberJpaRepository.save(new Member("member2", 10));
    memberJpaRepository.save(new Member("member3", 10));
    memberJpaRepository.save(new Member("member4", 10));
    memberJpaRepository.save(new Member("member5", 10));
    memberJpaRepository.save(new Member("member6", 10));

    int age = 10;
    int offset = 0;
    int limit = 3;

    List<Member> members = memberJpaRepository.findByPage(age, offset, limit); //member1, member2, member3
    for (Member member : members) {
        System.out.println("member = " + member);
    }
    long totalCount = memberJpaRepository.totalCountByAge(age);

    assertEquals(3, members.size());
    assertEquals(6, totalCount);
}

스프링 데이터 JPA 레포지토리

MemberRepository.class

Page<Member> findByAge(int age, Pageable pageable);

MemberRepositoryTest.class

  • Sorting과 Pageable로 페이징을 추상화했다.
@Transactional
@Test
void paging() {
    memberRepository.save(new Member("member1", 10));
    memberRepository.save(new Member("member2", 10));
    memberRepository.save(new Member("member3", 10));
    memberRepository.save(new Member("member4", 10));
    memberRepository.save(new Member("member5", 10));
    memberRepository.save(new Member("member6", 10));

    int age = 10;
    int offset = 0;
    int limit = 3;

    PageRequest pageRequest = PageRequest.of(offset, limit, Sort.by(Sort.Direction.ASC, "username"));

    Page<Member> members = memberRepository.findByAge(age, pageRequest); //member1, member2, member3
    Page<MemberDto> map = members.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
    for (MemberDto memberDto : map) {
        System.out.println("memberDto = " + memberDto);
    }

    List<Member> content = members.getContent();
    long totalElements = members.getTotalElements();

    assertEquals(6, totalElements);
    assertEquals("member1", content.get(0).getUsername());
}

Count 같은 통계쿼리 쓸 때 주의할 점

  • Page<T>타입의 경우 count 쿼리 또한 날린다.

  • 연관관계 있다면 Left Outer Join 해서 count 쿼리 날리기 떄문에 무겁다 떄문에 countQuery를 커스텀해주자.

    @Query(value = "select m from Member m left join fetch m.team as t",
          countQuery = "select count(m.username) from Member as m")
    Page<Member> findByAge(int age, Pageable pageable);

'JAVA > JPA' 카테고리의 다른 글

[JPA]open-session-in-view 를 알아보자  (1) 2020.07.01
@EntityGraph  (0) 2020.02.11
연관관계의 주인  (0) 2020.01.27
준영속 상태  (0) 2020.01.16
플러시  (0) 2020.01.16

+ Recent posts