반응형

스트림(Stream)

스트림(Stream)은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

 

스트림은 자바8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다. Iterator와 비슷한 역할을 하지만 람다식으로 요소 처리 코드를 제공하여 코드가 좀 더 간결하게 할 수 있다는 점과 내부 반복자를 사용하므로 병렬처리가 쉽다는 점에서 차이점이 있습니다. 

 

스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

  1. 생성하기 : 스트림 인스턴스 생성.
  2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
  3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).

1. 생성하기

배열 스트림

 

String[] arr = new String[]{"Bumblebee", "b", "c"};
Stream<String> stream = Arrays.stream(arr); // return Bumblebee, b, c
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3); // return b, c

 

컬렉션 스트림

 

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> colStream = list.stream(); // return a, b, c
Stream<String> parallelStream = list.parallelStream();  // 병렬 처리 스트림

 

Stream.builder()

 

Stream<String> builderStream =
Stream.<String>builder()
                        .add("kakao").add("naver").add("google")
                        .build(); // return kakao, naver, google

 

Stream.generate()

생성되는 스트림은 크기가 정해져있지 않고 무한(infinite)하기 때문에 특정 사이즈로 최대 크기를 제한해야 합니다.

 

 

Stream<String> generatedStream =
Stream.generate(() -> "Bumblebee").limit(3); // return Bumblebee, Bumblebee, Bumblebee

 

Stream.iterate()

초기값 설정 후 해당 값을 람다를 통해서 스트림에 들어갈 요소를 만들 수 있다.

 

Stream<Integer> iteratedStream = Stream.iterate(10, n -> n + 2).limit(3); // return 10, 12, 14

 

기본 타입형 스트림

리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있습니다.

 

IntStream intStream = IntStream.range(1, 5); // return 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1, 5); // return 1,2,3,4,5

 

Java 8 의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream, LongStream, DoubleStream)을 만들어낼 수 있습니다.

DoubleStream doubles = new Random().doubles(3);

 

병렬 스트림 Parallel Stream

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.

내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework 를 사용합니다.

각 작업을 쓰레드를 이용해 병렬 처리됩니다.

 

Stream<Product> parallelStream = productList.parallelStream(); // 병렬 스트림 생성
boolean isParallel = parallelStream.isParallel(); // 병렬 여부 확인

 

다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메소드를 사용합니다.

 

parallelStream.sequential(); // sequential
boolean isParallel = parallelStream .isParallel();

 

스트림 연결하기

 

Stream<String> stream1 = Stream.of("kakao", "naver");
Stream<String> stream2 = Stream.of("google");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(a -> System.out.print(a+", ")); // kakao, naver, google

 

2. 가공하기

전체 요소 중에서 다음과 같은 API 를 이용해서 내가 원하는 것만 뽑아낼 수 있습니다.

이러한 가공 단계를 중간 작업(intermediate operations)이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서(chaining) 작성할 수 있습니다.

 

Filtering

필터(filter)은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업입니다. 

 

List<String> itCompany = Arrays.asList("kakao", "naver", "google"); // 테스트 dataset

Stream<String> filterStream = itCompany.stream().filter(company -> company.contains("a")); // return kakao, naver

 

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다. 이 때 값을 변환하기 위한 람다를 인자로 받습니다.

 

Stream<String> mapStream = itCompany.stream().map(String::toUpperCase); // return KAKAO, NAVER, GOOGLE

 

Sorting

 

List<String> sortStream = itCompany.stream()
                                            .sorted()
                                            .collect(Collectors.toList()); //return google, kakao, naver,


List<String> sortReverseStream = itCompany.stream()
                                                  .sorted(Comparator.reverseOrder()) //역순
                                                  .collect(Collectors.toList()); // return naver, kakao, google

 

Comparator 의 compare 메소드는 두 인자를 비교해서 값을 리턴합니다.

 

List<String> compareSortStream = itCompany.stream()
                                                   .sorted(Comparator.comparingInt(String::length))
                                                   .collect(Collectors.toList()); // return kakao, naver, google


List<String> collect = itCompany.stream()
                                        .sorted((s1, s2) -> s2.length() - s1.length())
                                        .collect(Collectors.toList()); // reutrn google, kakao, naver,

 

peek

확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer 를 인자로 받습니다.

 

IntStream.of(1, 3, 5, 7, 9)
                            .peek(System.out::println)
                            .sum(); //return 1, 3, 5, 7, 9

 

 

3. 결과 만들기

가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계입니다. 따라서 스트림을 끝내는 최종 작업(terminal operations)입니다.

 

Calculating

스트림 API 는 다양한 종료 작업을 제공합니다. 최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들어낼 수 있습니다.

 

long count = IntStream.of(1, 2, 3, 4, 5).count(); //return 5
long sum = LongStream.of(1, 2, 3, 4, 5).sum();  //return 15

 

만약 스트림이 비어 있는 경우 count  sum 은 0을 출력하면 됩니다. 하지만 평균, 최소, 최대의 경우에는 표현할 수가 없기 때문에  Optional을 이용해 리턴합니다.

 

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); //return OptionalInt[1]
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max(); //return OptionalInt[9]

 

Reduction

스트림은 reduce라는 메소드를 이용해서 결과를 만들어냅니다.

 

reduce 메소드는 총 세 가지의 파라미터를 받을 수 있습니다.

  1. accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  2. identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  3. combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.

1. accumulator 

 

OptionalInt reduced =
IntStream.range(1, 4) //[1, 2, 3]
.reduce((a, b) -> {
    return Integer.sum(a, b);
});  // return OptionalInt[6]

 

2. identity

10은 초기값이고, 스트림 내 값을 더해서 결과는 16이 됩니다.

여기서 람다는 메소드 참조(method reference)를 이용해서 넘길 수 있습니다.

 

int reducedTwoParams =
IntStream.range(1, 4) // [1, 2, 3]
.reduce(10, Integer::sum); // return 16

 

3.combiner

Combiner 는 병렬 처리 시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계입니다.

따라서 병렬 스트림에서만 동작합니다.

 

Integer reducedParams = Stream.of(1, 2, 3)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> {
   System.out.println("combiner was called"); // 병렬 스트림이 아니어서 호출 안됨
   return a + b;
}); // return 16

 

결과는 다음과 같이 36이 나옵니다. 먼저 accumulator 는 총 세 번 동작합니다. 초기값 10에 각 스트림 값을 더한 세 개의 값(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 계산합니다. Combiner 는 identity 와 accumulator 를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할입니다. 12 + 13 = 25, 25 + 11 = 36 이렇게 두 번 호출됩니다.

 

Integer reducedParallel = Arrays.asList(1, 2, 3)
.parallelStream() // 병렬 스트림
.reduce(10,
Integer::sum,
(a, b) -> {
   System.out.println("combiner was called"); // 두번 호출 됨
   return a + b;
}); // return 36

 

Collecting

- Product.java

public class Product {
int count;
String name;

public Product(int count, String name) {
this.count = count;
this.name = name;
}

public String getName() {
return this.name;
}

public int getAmount() {
return this.count;
}
}

// 테스트 dataset

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                                                      new Product(14, "orange"),
                                                      new Product(13, "lemon"),
                                                      new Product(23, "bread"),
                                                      new Product(13, "sugar"));

 

Collectors.toList()

 

List<String> collectorCollection = productList.stream()
                                                      .map(Product::getName)
                                                   	.collect(Collectors.toList());

// return potatoes, orange, lemon, bread, sugar

 

Collectors.joining()

 

String listToString = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining()); // reutrn potatoesorangelemonbreadsugar

 

 

  • delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
  • prefix : 결과 맨 앞에 붙는 문자
  • suffix : 결과 맨 뒤에 붙는 문자
String listToString2 = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining(", ", "<", ">"));

// reutrn <potatoes, orange, lemon, bread, sugar>

 

Collectors.averageingInt()

 

Double averageAmount = productList.stream()
                                         .collect(Collectors.averagingInt(Product::getAmount)); // return 17.2

 

Collectors.summingInt()

 

Integer summingAmount = productList.stream()
                                          .collect(Collectors.summingInt(Product::getAmount)); // return 86

 

IntStream 으로 바꿔주는 mapToInt 메소드를 사용해서 좀 더 간단하게 표현할 수 있습니다.

 

Integer summingAmount = productList.stream()
                                           .mapToInt(Product::getAmount)
                                           .sum(); // return 86

 

Collectors.summarizingInt()

 

IntSummaryStatistics statistics =productList.stream()
                                                   .collect(Collectors.summarizingInt(Product::getAmount));

//return IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}

 

 

참고

- Java 스트림 Stream (1) 총정리(https://futurecreator.github.io/2018/08/26/java-8-streams/)

- [Java] 자바 스트림(Stream) 사용법 & 예제(https://coding-factory.tistory.com/574)

728x90
반응형

'JAVA' 카테고리의 다른 글

JAVA int, Integer 비교  (0) 2023.03.16
반응형

Intellij 2020.3 버전에서 node.js 프로젝트 생성하는 방법

1. Node.js 다운로드 (https://nodejs.org/ko/download/)

2. Intellij > File > New > project > JavaScript > Express

 

 

3. Options > View Engine : EJS

 

 

4. 프로젝트 실행(port - 3000)

 

728x90
반응형
반응형

목표

  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑

목차

  • 연관관계가 필요한 이유
  • 단방향 연관관계
  • 양방향 연관관계와 연관관계의 주인

연관관계가 필요한 이유

'객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.'   - 조영호(객체지향의 사실과 오해)

 

예제 시나리오

  1. 회원과 팀이 있다.
  2. 회원은 하나의 팀에만 소속될 수 있다.
  3. 회원과 팀은 다대일 관계다.

- 객체를 테이블에 맞추어 모델링(연관관계가 없는 객체)

- 객체를 테이블에 맞추어 모델링(참조 대신에 외래 키를 그대로 사용)

Member.java
Team.java

외래 키 식별자를 직접 다룰 수밖에 없음

객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

- 객체 지향 모델링(객체 연관관계 사용)

Member.java

@ManyToOne - 한팀에 여러 맴버가 있으므로 Member 입장에서 N:1이므로 ManyToOne이다.

@JoinColumn - 이름 그대로 조인하는 칼럼 이름 명시

 

JpaMain.java

전에는 teamId로 연관관계가 매핑되어 있어 teamId로 team 테이블에서 find 해서 가져와야 했다면 객체 연관관계로 매핑하면 getTeam으로 바로 꺼내올 수 있다. 

 

양방향 연관관계와 연관관계의 주인

테이블은 같은 경우는 TEAM_ID(FK) 하나로 Member, Team에서 서로 매핑되는 정보를 알 수 있었다. 하지만 객체에서는 단반향 일 때는 Member에서 Team 객체를 추가해서 알 수 있었지만 Team에서는 알 수 있는 방법이 없다.

 

Team.java

이제는 반대 방향일 때의 사용자의 팀을 가져올 수 있다. 

 

객체와 테이블이 관계를 맺는 차이

객체 연관관계 = 2개

회원 -> 팀 연관관계 1개(단방향)

팀 -> 회원 연관관계 1개(단방향)

 

테이블 연관관계 = 1개

회원 <-> 팀의 연관관계 1개(양방향)

 

연관관계 주인(Owner)

양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

누구를 주인으로?

외래 키가 있는 곳을 주인으로 정해라

 

양방향 매핑시 가장 많이 하는 실수.. (연관관계의 주인에 값을 입력하지 않음)

JpaMain.java
H2 DB

TEAM_ID에 NULL값이 들어간다.

원인 : mappedBy는 읽기 전용 변경할 때는 아예 보지를 않는다.

Team.java
JpaMain.java

양방향 매핑시 연관관계 주인에 값을 입력해야 한다. (순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다.)

 

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함 

 

 

참고 - 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한)

728x90
반응형

'JPA' 카테고리의 다른 글

JPA 순환 참조 원인 및 해결 방법  (0) 2021.08.26
JPA 엔티티 매핑  (0) 2021.06.08
JPA 영속성 관리  (0) 2021.06.06
JPA 소개  (0) 2021.06.04
반응형

목차

  1. 객체와 테이블 매핑
  2. 데이터베이스 스키마 자동 생성
  3. 필드와 칼럼 매핑
  4. 기본 키 매핑

엔티티 매핑 소개

1. 객체와 테이블 매핑 : @Entity, @Table

 - @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.

 - JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수

주의 

 - 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)

 - final 클래스, enum, interface, inner 클래스 사용x

 - 저장할 필드에 final 사용x 

 

@Entity

- JPA에서 사용할 엔티티 이름을 지정한다.

- 기본값 : 클래스 이름을 그대로 사용

- 가급적 기본값 사용 권장

 

@Table 

- 엔티티와 매핑할 데이블 지정

name - 매핑할 테이블 이름

@Table(name = "MBR")을 사용해서 insert 쿼리가 MBR 테이블로 생성된다.

 

2. 데이터베이스 스키마 자동 생성

- DDL을 애플리케이션 실행 시점에 자동 생성

- 테이블 중심 -> 객체 중심

- 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성

- 이렇게 생성된 DDL은 개발 장비에서만 사용

 

옵션 설명
create 기존테이블 삭제 후 다시 생성(DROP + CREATE)
create-drop create와 같으나 종료시점에 테이블 DROP
update 변경분만 반영(운영 DB에는 사용하면 안됨)
validate 엔티티와 테이블이 정상 매핑되었는지만 확인
none 사용하지 않음

 

persistence.xml

DB 테이블을 DROP하고 CREATE 한다.

 

주의!

운영 장비에는 절대 create, create-drop, update 사용하면 안된다.

개발 초기 단계는 create 또는 update

테스트 서버는 update 또는 validate

스테이징과 운영 서버는 validate 또는 none

 

DDL 생성 기능

- DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

크게 애플리케이션에 영향을 주지 않음 길이나 ALTER DDL 쿼리만 추가된다. 

3. 필드와 컬럼 매핑 : @Column

어노테이션 설명
@Column 컬럼 매핑
@Temporal 날짜 타입 매핑
@Enumerated enum 타입 매핑
@Lob BLOB, CLOB 매핑
@Transient 특정 필드를 칼럼에 매핑하지 않음(매핑 무시)

@Lob - 문자열 타입이면 기본으로 clob으로 생성된다.

@Enumerated(EnumType.STRING) - EnumType.STRING 사용O ORDINAL 사용X

 

참고 - LocalDate, LocalDateTime을 사용할 때는 @Temporal(TemporalType.TIMESTAMP) 생략 가능(최신 하이버네이트 지원)

4. 기본 키 매핑 : @Id

기본 키 매핑 방법

직접 할당 : @Id만 사용

자동 생성(@GeneratedValue)

 - IDENTITY : 데이터베이스에 위임, MYSQL

 - SEQUENCE : 데이터베이스 시쿼스 오브젝트 사용, ORACLE, @SequenceGenerator 필요

 - TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용, @TableGenerator 필요

 - AUTO : 방언에 따라 자동 지정, 기본 값

 

IDENTITY 전략 - 특징

  • 기본 키 생성을 데이터베이스 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행 한 후 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 entityManager.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회

SEQUENCE 전략 - 특징

  • 데이터베이스 시쿼스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
  • 속성 allocationSize - 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨, 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 값을 반드시 1로 설정해야 한다.)

TABLE 전략 - 특징

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 성능

 

직접 할당

Member.java
JpaMain.java

자동 할당 - IDENTITY 전략

Member.java
JpaMain.java

자동 할당 - SEQUENCE 전략

시퀀스 DROP 이후 생성

 

자동 할당 - TABLE 전략

키 값을 채번하는 테이블 생성

 

참고 - 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한)

728x90
반응형

'JPA' 카테고리의 다른 글

JPA 순환 참조 원인 및 해결 방법  (0) 2021.08.26
JPA 연관관계 매핑 기초  (0) 2021.06.17
JPA 영속성 관리  (0) 2021.06.06
JPA 소개  (0) 2021.06.04
반응형

들어가기전에 JPA 환경 세팅

  • idea - Intellij 
  • maven 프로젝트

디렉토리 구조

 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jpa-basic</groupId>
<artifactId>ex1-hello-jpa</artifactId>
<version>1.0.0</version>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<dependencies>
<!-- JPA 하이버네이트 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.10.Final</version>
</dependency>
<!-- H2 데이터베이스 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
</project>

 

JPA 설정 파일 (/META-INF/persisatence.xml 위치)

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!--데이터베이스 방언-->

<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>

 

JPA는 특정 데이터베이스에 종속X

각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다름
방언: SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능

<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!--데이터베이스 방언-->

사용하는 데이터베이스에 따라서 dialect를 다르게 설정한다. 이번 실습은 H2 데이터베이스 사용

 

JpaMain.java

EntityManagerFactory - 하나만 생성해서 애플리케이션 전체에서 공유

EntityManager - 쓰레드간에 공유X (사용하고 버려야 한다.)

JPA의 모든 데이터 변경은 트랜잭션 안에서 실행

JPQL - 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리

 

JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)
  • 영속성 컨텍스트

엔티티 매니저 팩토리와 엔티티 매니저

출처 - inflean 자바 ORM 표준 JPA 프로그래밍 - 기본편 수업자료, 김영한

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • "엔티티를 영구 저장하는 환경"이라는 뜻
  • EntityManager.persist(entity);

EntityManager가 영속성 컨테스트라고 생각하면 쉽다.

 

엔티티의 생명주기

비영속(new/transient) - 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

영속(managed) - 영속성 컨텍스트 관리되는 상태

준영속(detached) - 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제(removed) - 삭제된 상태

출처 - inflean 자바 ORM 표준 JPA 프로그래밍 - 기본편 수업자료, 김영한

비영속 상태 - 객체를 생성한 상태

영속 상태 - 객체를 저장한 상태

준영속, 삭제 상태

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

1차 캐시에서 조회

entityManager.persist(member) - 영속성 컨텍스트에서 관리되기 때문에 db select 쿼리 필요 없이 1차 캐시에서 바로 해당 member name 출력 가능

id가 2인 member를 찾기위해서 db select 쿼리 후 member name 출력

 

영속 엔티티의 동일성 보장

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

 

트랜잭션을 지원하는 쓰기 지연

EntityTransaction transaction = entityManager.getTransaction();
transaction.begin(); - 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.

entityManager.persist(member2); - 여기까지 INSERT 쿼리를 데이터베이스에 보내지 않는다.

커밋하는 순간 데이터 베이스에 INSERT 쿼리를 보낸다.

쓰기 지연 SQL 저장소에 쿼리를 저장해놓다가 commit 하는 순간 데이터베이스에 쿼리를 날린다.

 

변경 감지(Dirty Checking)

영속성 컨텍스트에서 관리하고 있다가 엔티티와 스냅샷을 비교하여 변경이 감지되면 commit 할 때 update 쿼리를 데이터베이스에 날린다.

 

엔티티 삭제

entityManager.remove(member1) - 엔티티 삭제

플러시

영속성 컨텍스트의 변경내용을 데이터베이스에 반영

 

플러시 발생

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정,  삭제 쿼리)

영속성 컨텍스트를 플러시하는 방법

  • entityManager.flush() - 직접 호출
  • 트랜잭션 커밋 - 플러시 자동 호출
  • JPQL 쿼리 실행 - 플러시 자동 호출

JPQL 쿼리 실행 시 플러시가 자동으로  호출되는 이유 - 영속성 컨텍스트에서만 관리되면 해당 테이블 모두 조회하는 쿼리가 실행 시 데이터베이스가 동기화되지 않아서 문제 발생 소지가 있기 때문에

EX)

플러시는 

  • 영속성 컨텍스트를 비우지 않음
  • 연속성 컨텍스트의 변경내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨

준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

준영속 상태로 만드는 방법

entityManager.detach(entyty) - 특정 엔티티만 준영속 상태로 전환

entityManager.clear() - 영속성 컨텍스트를 완전히 초기화

entityManager.close() - 영속성 컨텍스트를 종료

 

 

참고 - 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한)

728x90
반응형

'JPA' 카테고리의 다른 글

JPA 순환 참조 원인 및 해결 방법  (0) 2021.08.26
JPA 연관관계 매핑 기초  (0) 2021.06.17
JPA 엔티티 매핑  (0) 2021.06.08
JPA 소개  (0) 2021.06.04
반응형

1. GitHub NEW repository 생성

 

2. HTTPS 주소 복사

 

3. Terminal, git bash 접속 후 해당 프로젝트 폴더로 이동

 

4. git init

5. git remote add origin HTTPS 주소 붙여넣기

5. git remote -v (연결된 주소 확인)

* git remote 삭제

git remote remove origin

 

연결 끝!

6. git add .

7. git commit -m "First Project"

8. git push origin master

9. 원격 레포지토리 확인

 

 

* gitignore 파일에 적용 안될 때 git 캐시 삭제 후 다시 commit -> push

git rm -r --cached .
728x90
반응형

'Git' 카테고리의 다른 글

Spring git .gitignore 적용 안될 때  (0) 2022.10.06
[Git] Mac SSH키 생성  (0) 2022.03.12
[Git] push 했는데 잔디가 안 심어질 때..  (0) 2021.05.09
[Git] SSH키 설정  (0) 2020.12.12
반응형
  • SQL 중심적인 개발의 문제점
  • JPA 소개

SQL 중심적인 개발의 문제점

-  무한 반복

- 지루한 코드

 

반복되는 CRUD 작업

INSERT INTO...

UPDATE...

SELECT...

DELETE...

자바 객체를 SQL로 ...

SQL을 자바 객체로 ...

 

public class Member {

   private String memberId;

   pirvate String name;

   ...

}

 

INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES

SELECT MEMBER_ID, NAME FROM MEMBER M

UPDATE MEMBER SET ...

 

어느날 갑자기 요청사항으로 전화번호 칼럼 추가 요청이 오면 관련 테이블에 모두 수정 필요 

 

public class Member {

   private String memberId;

   pirvate String name;

   private String tel;  // 추가

   ...

}

 

INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES // TEL 추가

SELECT MEMBER_ID, NAME, TEL FROM MEMBER M // TEL 추가

UPDATE MEMBER SET ... TEL = ? // TEL 추가

 

SQL에 의존적인 개발을 피하기 어렵다.

 

패러다임의 불일치! (객체 VS 관계형 데이터베이스)

객체와 관계형 데이터베이스의 차이

1. 상속

2. 연관관계

3. 데이터 타입

4. 데이터 식별 방법

 

1) 객체다운 모델링으로 개발

 

class Member {

   String id;

   Team team;  // 참조로 연관관계 맺는다.

   String username;



   Team getTeam() {

          return team;

   }

}



class Team {

   Long id;

   String name;

}

 

2) 객체 모델링 저장

member.getTeam().getId(); 를 이용해서 TEAM_ID에 넣는다.

 

INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...

 

3) 객체 모델링 조회  

  객체다운 설계를 하여 조회를 하는데 오히려 복잡하고 맵핑 작업 시간이 더 걸려 결국엔 슈퍼 DTO 객체를 만들게 된다.

 

SELECT M.*, T.*

 FROM MEMBER M

  JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
public Member find(String memberId) {

   // SQL 실행 ... 

   Member member = new Member();

   // 데이터베이스에서 조회한 회원 관련 정보를 모두 입력

   Team team = new Team();

   // 데이터베이스에서 조회한 팀 관련 정보를 모두 입력



   // 회원과 팀 관계 설정

   member.setTeam(team);

   return member;

}

 

4) 처음 실행하는 SQL에 따라 탐색 범위 결정되는 경우

 

SELECT M.*, T.*

 FROM MEMEBER M

  JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 

member.getTeam(); // ok

member.getOrder(); // null 처음에 맴버과 팀만 가져왔기 때문에..  마음대로 사용자가 주문한 order 호출할 수가 없다..

 

만약에 memberDAO.find()를 다른 개발자가 개발했다면

엔티티 신뢰 문제 발생.. 개발자가 직접 눈으로 확인하지 않은 이상은 신뢰하고 사용하기 힘들다.

 

class MemberService {

  ...

 public void provess() {

    Mmber member = memberDAO.find(memberId);

    member.getTeam();  // ???

    member.getOrder().getDelivery();  // ???

}

 

모든 객체를 미리 로딩할 수는 없다.

상황에 따라 동일한 회원 조회 메서드를 여러개 생성한다.

 

memberDAO.getMember(); // Member만 조회

memberDAO.getMemberWithTeam(); // Member와 Team 조회

// Memberm, Order, Delivery

memberDAO.getMemberWithOrderWithDelivery();

 

그래서 자바 컬렉션에 저장하듯이 DB에 저장할 수는 없을까? 고민 끝에 JPA 등장!

JPA

- Java Persistence API

- 자바 진영의 ORM 기술 표준

 

ORM

- Object-relational mapping(객체 관계 매핑)

- 객체는 객체대로 설계

- 관계형 데이터베이스는 관계형 데이터베이스대로 설계

- ORM 프레임워크가 중간에서 매핑

- 대중적인 언어에는 대부분 ORM 기술이 존재

JPA는 애플리케이션과 JDBC 사이에서 동작

JPA 동작 - 저장

JPA 동작 - 조회

JPA는 표준 명세

- JPA는 인터페이스의 모음

- JPA 2.1 표준 명세를 구현한 3가지 구현체

- 하이버네이트, EclipseLink, DataNucleus

 

JPA를 사용하는 이유

- SQL 중심적인 개발에서 객체 중심으로 개발

- 생산성

- 유지보수

- 패러다임의 불일치 해결

- 데이터 접근 추상화와 벤더 독립성

- 표준

 

생산성 - JPA와 CRUD

저장 : jpa.persist(member)

조회 : Member member = jpa.find(memberId)

수정 : member.setName("변경할 이름")

삭제 : jpa.remove(member)

 

JPA의 성능 최적화 기능

1. 1차 캐시와 동일성(identity) 보장

2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

3. 지연 로딩(Lazy Loading)

 

1차 캐시와 동일성 보장

1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상

2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장

 

String memberId = "100";

Member m1 = jpa.find(Member.class,  memberId); //SQL

Member m2 = jpa.find(Member.class,  memberId); //캐시

println(m1 == m2) // true

 

SQL문이 1번만 실행된다.

 

트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음

2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

 

지연 로딩(Lazy Loading)

지연 로딩 : 객체가 실제 사용될 때 로딩

 

Member member = MemberDAO.find(memberId); => SELECT * FROM MEMBER

Team team = member.getTeam();

String teamName = team.getName(); => SELECT * FROM TEAM

 

즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

 

Member member = MemberDAO.find(memberId); => SELECT M.*, T.* FROM MEMBER JOIN TEAM ...

Team team = member.getTeam();

String teamName = team.getName();

 

 

 

** ORM은 객체와 RDB 두 기둥위에 있는 기술

** JPA 사용하기 전 과거로 돌아가고 싶지않다. (JPA 사용하는 어느 개발자가..)

    - 개발 인생에서 많은 시간을 SQL 생성하는데 투자하는데 그 시간을 아껴 더 좋은 코드를 생산할 수 있다.

 

 

참고 - 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한)

 

728x90
반응형

'JPA' 카테고리의 다른 글

JPA 순환 참조 원인 및 해결 방법  (0) 2021.08.26
JPA 연관관계 매핑 기초  (0) 2021.06.17
JPA 엔티티 매핑  (0) 2021.06.08
JPA 영속성 관리  (0) 2021.06.06
반응형
  1. 스프링 배치 심화학습
  2. 멀티 스레드로 여러 개의 Step 실행하기

1. 스프링 배치 심화학습

  1. 다양한 ItemReader 구현 클래스
  2. 다양한 ItemWriter 구현 클래스
  3. JobParameter 사용하기
  4. 테스트 시에만 H2 DB를 사용하도록 설정하기
  5. 청크 지향 프로세싱
  6. 배치의 인터셉터 Listener 설정하기
  7. 어노테이션 기반으로 Listener 설정하기
  8.  Step의 흐름을 제어하는 Flow

1.1 다양한 ItemReader 구현 클래스

리스트 타입으로 Reader를 구현한 ListItemReader 객체가 있습니다.

전에 기본편에서 만들었던 QueueItemReader 객체와 동일한 역할을 수행합니다.

ListItemReader 객체를 사용하면 모든 데이터를 한번에 가져와 메모리에 올려놓고 read() 메서드로 하나씩 배치 처리 작업을 수행할 수 있습니다.

그런데 수백, 수천을 넘어 수십만 개 이상의 데이터를 한번에 가져와 메모리에 올려놓아야 할 때는 어떻게 해야 할까요?

이때는 배치 프로젝트에서 제공하는 PagingItemReader 구현체를 사용할 수 있습니다.

구현체는 크게 JdbcPagingItemReader, JpaPagingItemReader, HibernatePagingItemReader가 있습니다. 

JpaPagingItemReader를 사용해보겠습니다.

JpaPagingItemReader에는 지정한 데이터 크기만큼 DB에서 읽어오는 setPageSize() 메서드 기능이 있습니다.

데이터를 지정한 단위로 가져와 배치 처리를 수행할 수 있습니다.

1. @Bean(destroyMethod="") -> 스프링에서 destroyMethod를 사용해 삭제할 빈을 자동으로 추적합니다.

destoryMethod=""와 같이 하여 기능을 사용하지 않도록 설정하면 실행 시 출력되는 warning 메시지를 삭제할 수 있습니다.

2. jpaPagingItemReader.setQueryString() -> jpaPagingItemReader를 사용하려면 쿼리를 직접 짜서 실행하는 방법밖에 없습니다. 

3. jpaPagingItemReader.setParameterValues(map) -> updatedDate, status 파라미터를 Map에 추가해 사용할 파라미터를 설정합니다.

 

JpaPagingItemReader 주의사항

inacticeJobStep()에서 설정한 청크 단위(커밋 단위)가 5라고 가정하면 Item5개를 writer까지 배치 처리를 진행하고 저장한다고 해봅시다. 저장된 데이터를 바탕으로 다음에 다시 지정한 크기로 새 인덱스를 할당해 읽어 와야 하는데 이전에 진행한 5라는 인덱스값을 그대로 사용해 데이터를 불러오도록 로직이 짜여 있어서 문제가 됩니다.

예를 들어 청크 단위로 Item 5개를 커밋하고 다음 청크 단위로 넘어가야 하는 경우를 가정하겠습니다.

하지만 entityManager에서 앞서 처리된 Item 5개 때문에 새로 불러올 Item의 인덱스 시작점이 5로 설정되어 있게 됩니다. 그러면 쿼리 요청 시 offset 5(인덱스값), limit 5(지정한 크기 단위)이므로 개념상 바로 다음 청크 단위(Item 5개)인 Item을 건너뛰는 상황이 발생합니다.

이러한 상황에서 가장 간단한 해결 방법은 조회용 인덱스값을 항상 0으로 반환하는 겁니다.

0으로 반환하면 Item 5개를 수정하고 다음 5개를 건너뛰지 않고 원하는 순서/청크 단위로 처리가 가능해집니다.

 

1.2 다양한 ItemWriter 구현 클래스

JpaItemWriter는 별도로 저장 설정을 할 필요 없이 제네릭에서 저장할 타입을 명시하고 EntityManagerFactory만 설정하면 Processor에서 넘어온 데이터를 청크 단위로 저장합니다.

1.3 JobParameter 사용하기

테스트 코드에 JobParameter를 생성해 JobLauncher에 전달하게끔 수정합니다.

1. new Date(); -> Date 타입은 JobParameter에서 허용하는 파라미터 중 하나입니다.

2. jobLauncherTestUtils.launchJob(new JobParametersBuilder().addDate("nowDate", nowDate).toJobParameters()) ->

JobParametersBuilder를 사용하면 간편하게 JobParameters를 생성할 수 있습니다. JobParameters는 여러 JobParameter를 받는 객체입니다. JobLauncher를 사용하려면 JobParameters가 필요합니다.

 

1.4 테스트 시에만 H2 DB를 사용하도록 설정하기

@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) 어노테이션을 사용하면 테스트 시에는 H2를 할당하게 처리할 수 있습니다.

1.5 청크 지향 프로세싱

청크 지향 프로세싱은 트랜잭션 경계 내에서 청크 단위로 데이터를 읽고 생성하는 프로그래밍 기법입니다. 

청크란 아이템이 트랜잭션에서 커밋되는 수를 말합니다.

read한 데이터 수가 지정한 청크 단위와 일치하면 wirte를 수행하고 트랜잭션을 커밋합니다.

청크 지향 프로세싱의 이점은 1000여 개의 데이터에 대해 배치 로직을 실행한다고 가정합시다. 청크로 나누지 않았을 때는 하나만 실패해도 다른 성공한 999개의 데이터가 롤백됩니다. 그런데 청크 단위를 10으로 해서 배치 처리를 하면 도중에 배치 처리에 실패하더라도 다른 청크는 영향을 받지 않습니다.

 

1.6 배치의 인터셉터 Listener 설정하기

배치 흐름에서 전후 처리를 하는 Listener를 설정할 수 있습니다.

구체적으로 Job의 전후 처리, Step의 전후 처리, 각 청크 단위에서의 전후 처리 등 세세한 과정 실행 시 특정 로직을 할당해 제어할 수 있습니다.

스프링 배치에서는 Job의 Listener로 JobExecutionListener 인터페이스를 제공합니다.

1. @Slf4j -> 필드에 로그 객체를 따로 생성할 필요 없이 로그 객체를 사용할 수 있도록 설정하는 롬복 어노테이션 

 

- Job 설정에 Listener 등록하기

InactiveUserJobConfig.java

1.7 어노테이션 기반으로 Listener 설정하기

InacticeStepListener.java

- Step 설정에 Listener 등록하기

 

1.8 Step의 흐름을 제어하는 Flow

스프링 배치는 세부적인 조건에 대한 흐름을 제어하는 Flow를 제공합니다.

흐름에서 조건에 해당하는 부분을 JobExecutionDecider 인터페이스를 사용해 구현할 수 있습니다.

JobExecutionDecider 인터페이스는 decide() 메서드 하나만 제공합니다.

1. start(new InactiveJobExecutionDecider()) -> start()로 설정해 맨 처음 시작하도록 지정합니다.

2. on(FlowExecutionStatus.FAILED.getName()).end() -> failed가 반환되면 end()를 사용해 곧바로 끝나도록 설정합니다.

3. on(FlowExecutionStatus.COMPLETED.getName()).to(inactiveJobStep) -> completed가 반환되면 기존에 설정한 inactiveJobStep을 실행하도록 설정합니다.

4. start(inactiveJobFlow) -> inactiveUserJob 시작 시 Flow를 거쳐 Step을 실행하도록 inactiveJobFlow를 start()에 설정합니다.

 

2. 멀티 스레드로 여러 개의 Step 실행하기

1. TaskExecutor를 사용해 여러 Step 동작시키기

 

2.1 TaskExecutor를 사용해 여러 Step 동작시키기

TaskExcutor 인터페이스는 멀티 스레드로 Step을 실행하는 가장 기본적인 방법입니다.

Task는 Runnable 인터페이스를 구현해 각각의 스레드가 독립적으로 실행되도록 작업을 할당하는 객체입니다.

 

 

 

 

 

728x90
반응형

+ Recent posts