3주 차 강의 내용 정리
- JPA
- 3주 차 미션(Lotto) 대한 피드백
JPA
SQL을 직접 다룰 때 발생하는 문제점
- 반복 작업
테이블 구조가 변경(추가, 삭제)되면 관련된 SQL을 전부 수정해줘야 한다. - 신뢰성
Human error가 발생할 가능성이 높아짐 (ex. 데이터 fetch 로직에서 테이블 간 매핑이나 쿼리 작성이 빠져있는 경우)
=> JPA를 사용하면 위 issue에서 자유로워질 수 있다.
JDBC, SQLMAPPER, ORM
공통점: persistence (데이터를 영속적으로 저장하기 위해 사용하는 기술)
JDBC: java의 JDBC API
DriverManager -> Connection -> Statement -> ResultSet 으로 이루어져 있는 구조
커넥션, 쿼리를 직접 작성하고 관리해야 한다.
SQL MAPPER: MyBatis, Spring JDBC
SQL을 자바 코드로부터 분리한 것
ORM: JPA, Hibernate, Spring data JPA, Sping data JDBC
사람이 쿼리를 관리하지 않는 것! (Lazy Loading, Dirty Checking, Caching 등의 특징이 있음)
JPA 특징
-
데이터베이스 스키마를 자동으로 생성하는 기능을 지원
spring.jpa.hibernate.ddl-auto=create
설정값 종류
create: 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
create-drop: create와 같으나 종료 시점에 테이블 DROP
update: 변경된 부분만 반영 (운영 DB에 사용하면 안됨)
validate: entity와 table이 정상 매핑되었는지만 확인
none: 사용하지 않음
※ 크리티컬 이슈를 발생시킬 수 있기 때문에 실무에서는 보통 validate or none으로만 사용한다.
2. @(Annotation)으로 많은 기능을 활용할 수 있다.
@Entity // (1)
@Table(name = "station") // (2)
public class Station {
@Id // (3)
@GeneratedValue(strategy = GenerationType.IDENTITY) // (4)
private Long id;
@Column(name = "name", nullable = false) // (5)
private String name;
protected Station() { // (6)
}
}
- @Entity : pk는 꼭 가져야함. 엔티티 클래스임을 지정하며 테이블과 매핑된다.
- @Table: 굳이 선언할 필요 없음. Name property를 이용해서 컨벤션에 맞는 테이블이름을 정할 수 있다. 생략할 경우 엔티티클래스이름과 동일한 테이블로 매핑
- @Id : pk
- @GeneratedVAlue : pk 생성 규칙을
- @column
- 컬럼의 이름을 이용하여 지정된 필드나 속성을 테이블의 컬럼에 매핑한다.
- 굳이 선언하지 않아도 된다.
- 매개 변수가 없는 생성자
- The entity class must have a no-arg constructor. The entity class may have other constructors as well. - JSR 338
3. 영속성 컨텍스트
- 엔티티를 영구 저장하는 환경
- 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다
- 영속성 컨텍스트의 기능
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
4. 엔티티의 생명주기
비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
영속(managed): 영속성 컨텍스트에 저장된 상태
준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed): 삭제된 상태
JPA의 연관관계
엔티티들은 대부분 다른 엔티티와 연관 관계를 맺는다.
객체는 참조를 사용, 테이블은 외래 키를 사용해서 관계를 맺는다.
- 방향: 단방향, 양방향이 있다. 방향은 객체 관계에서만 존재하고 테이블 관계는 항상 양방향이다.
- 다중성: 일대일, 다대다, 일대다, 다대일
- 연관관계의 주인
- 객체 사이에 관계가 형성되면 관계의 주인을 정해야 한다.
- 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 등록, 수정, 삭제할 수 있다.
- 주인이 아닌 쪽은 읽기만 가능하다.
- 연관관계의 주인이 아닌 곳에서 입력된 값은 외래 키에 영향을 주지 않는다.
- 양방향 관계에서는 양쪽 다 데이터 일관성에 대해 신경을 써야 한다.
- 양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.
- 한 번에 양방향 관계를 설정하는 메서드를 연관 관계 편의 메서드라 한다.
public void setLine(Line line) {
this.line = line;
line.getStations().add(this);
}
public void addStation(Station station) {
stations.add(station);
station.setLine(this);
}
매핑 시 무한루프에 빠지지 않게 조심해야 한다.
TIPS
-
AUDITING
애플리케이션의엔티티의 생성 시간과 마지막 수정시간을 관리할 필요가 있다면 수동으로 매번 추가하는 대신 Auditing 기능을 이용해서 자동으로 추가해 줄 수 있다.
- @Configuration 클래스에 @EnableJpaAuditing을 추가한다.
@EnableJpaAuditing
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 엔티티에 콜백 리스너를 추가한다.
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Line {
3. 생성 날짜와 마지막 수정 날짜 프로퍼티에 @CreatedDate와 @LastModifiedDate를 추가한다.
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Line {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
4. @MappedSuperclass 를 사용해서 abstract class를 생성해서 사용한다면 중복코드를 분리할 수 있다.
-
비교
엔티티 비교시에는 식별자만 있어도 충분하다.
좀 더 디테일한 JPA 관련 내용은 추후 해당 블로그에 기재하도록 하겠다.
3주 차 미션에 대한 피드백
- . 매직 넘버는 명명된 상수로 치환해서 사용하기
// AS-IS
public class Calculator {
public int splitAndSum(String text) {
String[] numArr = Splitter.split(!isEmptyOrNull(text) ? text : "0");
//TO-BE
public class Calculator {
private final static String ZERO = "0";
public int splitAndSum(String text) {
String[] numArr = Splitter.split(!isEmptyOrNull(text) ? text : ZERO);
- 유틸리티 클래스의 접근성 제한하기
- 유틸 클래스의 경우 불필요한 객체 생성을 방지하기 위해서 Private 생성자를 사용하기
- 직관적인 코드 작성
//AS-IS
if(inputValue == null || inputValue.isEmpty()) {
return true;
}
return false;
//TO-BE
return inputValue == null || inputValue.isEmpty();
- 스트림을 사용해서 간략하게 코드 작성
//AS-IS
int resultNum = Integer.parseInt(numArr[0]);
for(int i = 1; i < numArr.length; i++) {
resultNum = add(resultNum, Integer.parseInt(numArr[i]));
}
return resultNum;
//TO-BE
return Arrays.stream(numArr)
.mapToInt(Integer::parseInt)
.sum();
- RetainAll 함수를 사용하는 방법
//AS-IS
return lotto.nums.stream().
filter(n -> this.nums.contains(n)).count();
- 로또 2,3등 구하는 로직 개선
//AS-IS
public static Rank of(int matchNums, boolean matchBonus) {
if (matchNums == SECOND.matchNums && matchBonus) {
return SECOND;
}
if (matchNums == THIRD.matchNums) {
return THIRD;
}
return Arrays.stream(Rank.values())
.filter(rank -> rank.matchNums == matchNums)
.findAny()
.orElse(MISS);
}
//TO-BE
return Arrays.stream(values())
.filter(rank -> rank. matchNums == matchNums)
.filter(rank -> !rank.equals(SECOND) || matchBonus)
.findFirst()
.orElse(MISS);
3주 차 미션(Lotto) 리뷰
소스코드: github.com/louisJu/java-lotto-ex
느낀 점
- 상수처리에 대해서 조금 더 신경 써야 할 것 같음.
- ENUM 클래스 활용에 아직 많이 미숙하구나.
- 스트림 활용법에 대한 학습 필요.
회고
: 아직까진 예전에 실습해본 주제로 과제를 진행하다 보니 접근하기 나름 편했다. 역시 반복학습의 효과는 짱...
'MyStory > dev_life' 카테고리의 다른 글
동시성 이슈를 고려해서 쿠폰 발급하기 (Redisson을 활용한 분산 락 처리) (0) | 2023.02.19 |
---|---|
우아한 테크 캠프 프로 5주차 회고 - 1 (0) | 2021.03.14 |
우아한 테크 캠프 프로 4주차 회고 (0) | 2021.03.07 |
우아한 테크 캠프 PRO 2주차 회고 (0) | 2021.02.07 |
우아한 테크 캠프 PRO 1주차 회고 (0) | 2021.02.04 |