TIL - 0814

Stream API 하나씩 사용해보기

flatMap

  • map과 flatMap 차이 : map은 1:1로 변환하는 함수라면, flatMap은 N:1 변환하는 함수(배열 -> 스트림으로 변환할 때 사용)
    • stream에 진행 함수에 들어가는 값(input)이 배열인 것을 하나하나 떼어놓을 때
Stream.of(
    new String[]{"a", "b", "c", "d", "e"},
    new String[]{"f", "g", "h", "i", "j"}
    )
    .flatMap(Arrays::stream)
    .map(str -> str.charAt(0))
    .filter(ch -> ch > 'a')
    .collect(toList())
    .forEach(ch -> System.out.print(ch + " "));

sorted

  • 감싸져있는 콜렉션 원소들을 정렬함
    • primitive type의 경우 대소 관계를 비교(정수)할 수 있음, ref type의 경우에는 특정 인터페이스를 구현해야함(기준을 정해줘야함)
List<Integer> nums = IntStream.rangeClosed(0, 10)
                .boxed()
                .collect(toList());

// 섞음
Collections.shuffle(nums);

nums.stream()
    .sorted()
    .forEach(System.out::print);

Object 정렬하기

  • Obj의 경우에는 어떤 기준으로 정렬할 지 모르기때문에 지정해줘야함 : Comparable(기본 정렬 기준), Comparator(기본 정렬 기준 이외 정렬 기준을 정해서 정렬할 때 사용)

  • 오름차순, 내림차순 설정하기 : Student의 age 속성을 가지고 정렬 기준 만들기

    • age - otherAge > 0 보다 클 때 1을 리턴하도록 함 : 오름차순 (거꾸로 0보다 크지만 -1을 리턴함으로써 내림차순으로 만들 수 있음)
class Student implements Comparable<Student> {
    private String name;
    private int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Override
    public int compareTo(Student otherStudent) {
        int otherAge = otherStudent.age;

        if (age - otherAge > 0) {
            return 1;
        } else if (age - otherAge == 0) {
            return 0;
        }
        return -1;
    }
}

Student student = new Student("jinbro", 6);
Student student2 = new Student("imjinbro", 3);
Student student3 = new Student("colin", 4);

Stream.of(student, student2, student3)
      .sorted()
      .map(Student::getName)
      .forEach(System.out::println);

Spring AOP - Transaction

트랜잭션 처리

  • 트랜잭션 : 복수 쿼리를 하나의 작업으로 묶어서 처리함, 하나가 실패했을 때 모두 실패(롤백 - 데이터베이스 영구 반영 안됨), 모두 성공했을 때 반영(커밋)
  • 데이터베이스는 한 순간에 하나의 트랜잭션만 처리하는 것이 아니라 동시적으로 여러 트랜잭션을 처리함 : 하나만 처리한다면 성능상으로 쓸 수 없는 DB
    • 격리레벨 : 복수의 트랜잭션을 처리할 때 성능을 위해서 트랜잭션 간 간섭을 어느정도까지 허용할 것인지 레벨을 설정하는 것 - 잠금(lock) 레벨 설정, 데이터베이스에 따라 지원하는 레벨이 다르지만 보통 4개로 배움
  • 격리레벨에 따라 복수 트랜잭션 처리에서 발생할 수 있는 문제
    • Dirty Read : 커밋되지않은 데이터 변경까지도 읽을 수 있기때문에 T1에서 읽고 변경을 했는데 T2가 해당 데이터를 읽은 상태에서 T1이 커밋되지않고 롤백을 했을 때 T2는 그 사실을 모르고 데이터베이스에 반영시켜버리면 예상치 못한 결과가 저장될 수 있음
    • Non-Repeatable Read : T1에서 어떤 데이터를 읽고, T2가 중간에 동일 데이터에 대해 변경 후 커밋한 뒤 T1이 읽으면 같은 트랜잭션이지만 다른 데이터를 읽게됨(커밋된 읽기를 하더라도 해당 데이터에 대해 Lock이 걸리는게 아니기떄문에 변경이 가능함)
    • Phantom Read : T1이 특정 범위에 대해 읽은 후 T2가 중간에 그 범위에 해당하는 데이터를 삽입 또는 삭제를 해서 T1이 이후 읽었을 때 처음 읽은 갯수와는 다른 문제
  • 격리레벨 : 데이터 성격에 따라서 Lock 정책(간섭을 어느정도 허용할 것인지)을 정하면 됨
    1. Read Uncommitted : 반영되지않은(커밋되지않은) 데이터 변경까지도 읽을 수 있음, 쓰루풋(처리량)에서는 월등히 뛰어남, 데이터 무결성에 비교적 상관없는 데이터라면 성능을 지키기위해 사용
    2. Read Committed : 커밋된 데이터만 읽을 수 있음
    3. Repeatable-Read : 한 트랜잭션이 SELECT 한 데이터에 대해서는 변경 Lock(Update, Delete 할 수 없음)이 걸림(SELECT는 공유락), 이외 범위에 추가, 삭제는 가능함(Phantom Read는 여전히 발생할 수 있음), 보통 기본적으로 설정되어있는 레벨
    4. Serializable : 직렬 처리, 성능 상으로는 굉장히 좋지못함, 하지만 한 트랜잭션이 종료될 때까지 간섭하지못하도록 완전하게 막아야하는 상황이라면 설정할 필요가 있음

어플리케이션에서 트랜잭션 처리

  • 트랜잭션을 지원하는 데이터베이스는 있는데, 결국 어플리케이션에서 데이터베이스에 명령을 내려줘야함 : 트랜잭션을 어디에서부터 어디까지 묶을 것이며, 어떤 상황에 커밋하고 롤백할 것인지에 대해 알려줘야함 - 스프링 AOP 이전의 코드와 이후의 코드에는 상당한 차이가 있음
  • 스프링 AOP 이전의 트랜잭션 처리
    • Dao 처리를 하나의 트랜잭션으로 묶기위해 커넥션을 공유 : 매우 지저분한 상태(서비스 단에 Dao가 가져야할 데이터베이스 관련 코드가 있게 됨)
public class QnaService {
    
    public void insert(Answer answer) throws SQLException {
        Connection conn = null;
        
        try {
            
            answerDao.insert(conn, answer);
            questionDao.updateCountOfAnswer(conn, answer.get....);
            conn.commit();            
        } catch(SQLException e) {
        	  conn.rollback();
        }
    }
}
  • 스프링 AOP 코드 : @Transactional
    • 인프라 로직을 거둬낼 수 있게됨
    • 중복 제거 : 트랜잭션 경계 설정과 롤백 / 커밋 설정 등등 코드를 중복적으로 코딩해줘야했었는데 전 후 처리를 하게 함으로써 중복을 제거함
    • 클래스에 달아두면 해당 클래스의 메소드들은 모두 트랜잭션 처리, 메소드에 달아두면 개별적으로 트랜잭션 설정을 할 수 있음(클래스에 달고, 메소드에도 달아주면 클래스에 설정된 값이 전역이라면 메소드에 설정된 값이 로컬이라 로컬 우선 적용 - 메소드 별로 상황에 맞게 설정해야할 것들이 있으면 따로 설정도 하면 됨)
  • @Transactional 설정사항
    • Isolation Level : 위에서 살펴본 격리레벨, 사용하는 데이터베이스의 기본 격리 레벨이 무엇인지 파악부터하기
    • Propagation Behavior : 트랜잭션이 설정된 메소드 내에서 굳이 트랜잭션으로 처리하지않아도 되는(실패하더라도 커밋/롤백에 영향을 미치지않도록) 코드를 예전에는 commit() 메소드 다음에 코딩해주었는데 그럴 필요없이 해당 설정사항만 알고 설정하면 트랜잭션 내에서 새로운 트랜잭션을 만들어서 해당 코드가 실패하더라도 원래 트랜잭션에는 영향을 미치지 않도록 할 수 있는 설정사항(기본적으로는 트랜잭션에 포함되도록 되어있음 - 현업에서 해당 메소드에 들어갈 것이 무엇인지 보고 트랜잭션 커밋/롤백에 영향을 미치면 안되겠다는 코드는 해당 부분을 설정해줘야함)
    • Exception handle : 런타임 익셉션(extends RuntimeException)이 발생하면 자동으로 롤백 처리가 되는데, 컴파일 익셉션(extends Exception)의 경우 해당 익셉션이 발생했을 때 롤백을 처리하도록 설정해줘야함(모르면 왜 이 예외는 롤백 처리를 하지않을까 라고 생각하게 됨)