TIL - 0816
Spring-data-JPA
ORM을 쓰면
- 도메인 모델 개발
- 쿼리를 직접 생성하지않음(물론 아예 하지않는 것은 아님 - 필요에 의해 제공 기능으로 쿼리 생성까지 할 수 있음) : SQL 방언까지 신경쓰지않아도 됨(java-ims에서 prod/dev 환경에 따라 다른 RDBMS를 사용했지만 손쉽게 전환이 가능했던 이유)
- UserDao 혹은 UserRepository와 같이 데이터베이스 접근하는 역할을 만들어줌 : Connection 자원 할당, 해제 등 관리를 개발자가 하지않음
/* 도메인 모델 없이 개발하기 */
String url = "jdbc:postgresql://localhost:5432/springdata";
String username = "jinbro";
String password = "pass";
try (Connection connection = DriverManager.getConnection(url, username, password)) {
System.out.println("Connections created : " + connection);
String sql = "CREATE TABLE ACCOUNT (id int, username varchar(255), password varchar(255), primary key (id))";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.execute();
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
/* 도메인 모델 개발 */
Account account = new Account("jinbro", 27);
accountRepository.save(account);
- 중간 계층이 생김
- 캐시 할 수 있게 됨(같은 트랜잭션) : 실제로 쿼리를 여러번 보내는게 아니라 객체를 저장해뒀다가 해당 객체가 변경되었을 경우 감지하고 변경까지 적용해둠, 최종적으로 최초 상태와 다를 때 쿼리를 날림, 변경이 없고 써치만 했을 경우 최초 1회 쿼리만 날리고 이후부터는 똑같은 객체만 리턴해주면 됨(데이터베이스 커넥션 수를 줄임)
- lazy loading 가능 : 객체의 필드로 다른 객체가 있을 수 있음 - 해당 부분도 쿼리를 날려 찾아와야하는데 처음부터 찾아오는게 아니라 실제로 사용할 때 찾아오도록 lazy 설정이 가능함, lazy 설정을 했을 때 발생할 수 있는 문제가 있음(N+1 쿼리 문제 - 1개마다 N번의 순회를 하는 문제, 해당 문제가 왜 발생하는지 어떻게 해결하는지 차차 찾아보기)
- 패러다임 불일치 해결 : OO와 Relational의 패러다임 차이
- 꼭 알아야하는 것 : JPA만 알아서 개발할 수 있는게 아님 - RDBMS와 쿼리에 대해 알지 못하면 JPA를 제대로 사용할 수 없음, JPA를 어렴풋이 알면 오히려 성능을 망칠 수도 있으니 제대로된 공부가 필요하다…!
OO와 R의 패러다임 차이점과 맵핑
- 차이
- 식별의 차이 : 주 키(pk)로 식별과 equals() + hashCode() 식별
- 데이터 타입의 차이 : VARCHAR, INT 등 정해진 타입과 다양한 객체 타입(클래스 - 기본 제공, 개발자 정의, enum)
- 상속관계와 연관관계 등 관계 표현 : 테이블 생성 및 JOIN과 extends, implements, 의존(필드)
- 차이 극복을 위해 ORM 사용 전에는 개발자가 직접 작업을 해줘야했으나 ORM이 패러다임 차이 극복을 위한 작업을 함
- 생산성 향상
- 비즈니스 로직 개발에 더 집중할 수 있게 됨
OOP
객체 추상화하기 - 도메인 분석부터
- 본인이 가진 배경지식을 가지고 추상화를 진행하면 안된다
- 예를 들어 커피전문점이라는 도메인을 설계할 때 바리스타와 손님 객체가 필요하다, 클래스로 추상화해야하는데 이때 실세계 배경지식으로 사람이라는 클래스를 추상화해버리면 코드 복잡도만 증가할 뿐 문제 해결에 전혀 도움되지않는다. 스태프와 손님으로 추상화한 뒤 스태프는 점장과 점원 더 나아가 바리스타와 계산원으로 세분화를 시키는 것이 더 낫다.
- 생각의 결과 : 도메인 분석부터 하고 필요한 객체를 먼저 도출한다 -> 객체를 그대로 추상화하면 됨
- 경험의 결과 : 도메인 분석 후 필요한 객체를 먼저 만들고 시작하기보다 해당 도메인에 대한 경험이 부족하다면 기능을 먼저 생각하고 기능을 만들기위해 테스트 케이스를 만든 뒤 기능을 만들면서 객체를 나누는 것이 좋았음
JAVA
String, StringBuilder, StringBuffer
- String은 불변 객체 : 값이 바뀌지않음, 대신 바뀐 값을 가지는 객체를 생성할 뿐
- 잦은 값 변경이 있을 경우 Builder나 Buffer를 고려해야함 : String 객체가 불필요하게 메모리에 쌓이게 되는 것을 방지(메소드 내에서(스택 메모리 내에서) 선언되는 로컬 변수라면 크게 상관없지만) - String 객체에 += 연산을 하면 엄청 느림(매번 새로운 객체)
- 같은 값을 가지는 String 객체라면 같은 객체 : 두 참조 변수가 같은 객체 주소값을 가짐
String str = new String(new char[]{'a', 'b', 'c'});
String str2 = "abc";
System.out.println(str.hashCode() + ", " + str2.hashCode()); // 같은 값
-
StringBuilder와 StringBuffer의 차이 : 멀티쓰레드 환경에서 문자열에 대한 처리를 하는 객체를 만들었다 그것도 전역변수(공유 변수)로… 그렇다면 Buffer를 사용하는 것이 Thread-Safe함 - Buffer의 메소드(변경, 조회 메소드 - 모든 메소드)들은 동기화 처리가 되어있음(Lock - 하나의 쓰레드가 점유하면 다른 쓰레드는 대기 해야함)
-
문자열끼리 ‘+’연산자를 통해서 concat 할 경우 1.5부터는 컴파일 단계에서 StringBuilder로 컴파일 됨
- javap로 역어셈블 해보면 append로 컴파일 된 것을 볼 수 있었음
/* 컴파일 대상 */
public static String concat(String str1, String str2) {
return str1 + str2;
}
/* 역어셈블 결과 */
public static java.lang.String concat(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: new #6 // class java/lang/StringBuilder
3: dup
4: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
7: aload_0
8: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_1
12: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
18: areturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 str1 Ljava/lang/String;
0 19 1 str2 Ljava/lang/String;
}
Stream API
- Stream API는 최종연산을 하고 난 뒤에는 재사용할 수 없음
public static void main(String[] args) {
Stream<Person> personStream = Arrays.asList(
new Person(21), new Person(30), new Person(16), new Person(20)
)
.stream()
.skip(2);
System.out.println(personStream.anyMatch(Person::isAdult));
// 예외 발생함
personStream.forEach(System.out::println);
}
reduce
- 스트림의 모든 원소를 하나의 결과로 만들어주는 함수 : 감소연산
첫번째 reduce
- 어떤 기준에 의해 하나의 원소만 선택함 : 원소 중 가장 큰/작은 원소 찾기
- Optional
reduce(BinaryOperator accumulator);
- Optional
- reduce의 인자(함수) : reduce(BinaryOperator) - minBy, maxBy
public static void main(String[] args) {
Arrays.asList(new Item("바나나우유", 3), new Item("물", 6), new Item("소세지", 10), new Item("불닭볶음면", 6))
.stream()
.reduce((item1, item2) -> item1.haveMore(item2) ? item1 : item2)
.ifPresent(item -> System.out.println(item.getName()));
}
두번째 reduce
- 초기값을 주고, 하나로 압축시킴
- 위와 동일한데 초기값을 주는게 다름, 리턴 타입도 옵셔널이 아님 : T reduce(T identity, BinaryOperator
<T>
accumulator);
- 위와 동일한데 초기값을 주는게 다름, 리턴 타입도 옵셔널이 아님 : T reduce(T identity, BinaryOperator
세번째 reduce
- (초기값을 주고, 특정 타입으로 변환한 후, 변환한 타입인 원소를 하나로 모은다)
- 시그니쳐 :
<U>
reduce(U identity, BiFunction<U, ? super T, U> accumulator, BiOperator<U>
combiner)
- 시그니쳐 :
int total = items.stream().reduce(0, (sum, item) -> item.totalQuantity(sum), (sum1, sum2) -> sum1 + sum2);
System.out.println(total);