3주 차 강의 내용 정리

  • JPA
  • 3주 차 미션(Lotto) 대한 피드백 

JPA

SQL을 직접 다룰 때 발생하는 문제점

  1. 반복 작업
    테이블 구조가 변경(추가, 삭제)되면 관련된 SQL을 전부 수정해줘야 한다.
  2. 신뢰성
    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 특징

  1. 데이터베이스 스키마를 자동으로 생성하는 기능을 지원

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)
    }
}
  1. @Entity : pk 꼭 가져야함.  엔티티 클래스임을 지정하며 테이블과 매핑된다.
  2. @Table: 굳이 선언할 필요 없음. Name property 이용해서 컨벤션에 맞는 테이블이름을 정할 있다. 생략할 경우 엔티티클래스이름과 동일한 테이블로 매핑
  3. @Id : pk
  4. @GeneratedVAlue : pk 생성 규칙을
  5. @column
    • 컬럼의 이름을 이용하여 지정된 필드나 속성을 테이블의 컬럼에 매핑한다.
    • 굳이 선언하지 않아도 된다.
  6. 매개 변수가 없는 생성자
    • 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 기능을 이용해서 자동으로 추가해 있다.

  1. @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 클래스 활용에 아직 많이 미숙하구나. 
  • 스트림 활용법에 대한 학습 필요.

회고 

 : 아직까진 예전에 실습해본 주제로 과제를 진행하다 보니 접근하기 나름 편했다. 역시 반복학습의 효과는 짱...

2주 차 강의 내용 정리

  • TDD, 클린코드
  • 1주차미션(racingCar) 대한 피드백 & 라이브 코딩

Clean Code 가이드

의미 있는 이름

  • 의도를 분명히 한 naming 규칙
    • 협업할 때 새롭게 코드를 볼 사람을 생각해서 더 나은 이름으로 작성하기
    • 주석을 사용하지 않아도 될 정도로 분명하게 naming 하기
// 잘못된 변수 사용
int d; // 경과시간(day)
  • 의미 있게 구분하기
    • 기준을 세우고 네이밍하는 것이 좋다. (ex. 카멜케이스, 스네이크케이스 등)
  • 인터페이스 클래스와 구현 클래스
    • 인터페이스는 공통적인 개념으로 naming하고, 구현 클래스는 의도가 드러날 수 있는 구체적인 naming을 사용하라
  • 클래스 이름
    • 명사, 명사구가 적합
    • Manager, Processer, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.
  • 메소드 이름
    • 동사, 동사구가 적합
    • 접근자, 변경자, 조건자에는 값 앞에 get, set, is를 붙인다.
    • 생성자를 중복해서 정의할 땐 정적팩토리 메소드를 사용. 메소드명은 인수를 설명하는 이름으로 사용한다.

경계

wrapper 클래스를 사용

Map, List 같은 collection 등 외부에 노출하는 경우 많은 인터페이스가 노출하게 되기 때문에 wrapper 클래스를 사용해서 숨기는 것이 좋다.

  • 사용자에게 모든 인터페이스를 노출하지 않아도 된다.
  • 내부적인 변경이 발생하더라도 외부 변경은 발생하지 않는다.
Map<Integer, Sensor> sensors = new HashMap<>();
Sensor s = sensors.get(sensorId);
public class Sensors {
    Map<Integer, Sensor> sensors = new HashMap<>();

    pubilc Sensor getById(String id) {
        return sensors.get(id);
    }
}

=> Map을 사용자에게 직접 노출하지 않고 Sensors 클래스르 이용해서 처리 가능하다.

1주차 미션에 대한 피드백

  • 로직이 동일한 테스트일 경우 @ParameterizedTest 를 활용해서 하나로 합칠 수 있다. 
    @ParameterizedTest
    @CsvSource({"20 + 10,30","20 - 10,10","20 * 10,200","20 / 10,2"})
    @DisplayName("사칙연산 test")
    void calculationTest(String inputData, Long result) {
        expression = new Expression(inputData);
        calculator = new Calculator(expression);
        Long testResult = calculator.calculate();
        assertThat(testResult).isEqualTo(result);
    }

 

 

  • 함수형 인터페이스 및 람다 사용.
//AS-IS
private static List<Car> mapCars(int carNums) {
        List<Car> cars = new ArrayList<>();
        for (int i = 0; i < carNums; i++) {
            cars.add(new Car());
        }
        return cars;
    }
//TO-BE
this.cars = Arrays.stream(splitCarNames(carNames)).map(name -> new Car(name)).collect(Collectors.toList());

 

  • 전략패턴 사용
    => 자동차가 움직이는 규칙을 독립시킬 수 있다. 규칙이 독립되면 테스트도 용의해진다. 
public interface MoveStrategy {
    boolean isMove(int value);
}

=> 이동 규칙 관련 인터페이스를 생성

 

public class RandomMoveStrategy implements MoveStrategy {
    private static final int START_CONDITION = 4;
    private static final int END_CONDITION = 9;

    @Override
    public boolean isMove(int value) {
        if(value >= START_CONDITION && value <= END_CONDITION) {
            return true;
        }
        return false;
    }
}

=> 인터페이스를 구현한 실제 적용할 규칙 클래스를 생성

 

public class Racing {
    private List<Car> cars;
    private MoveStrategy moveStrategy;

    public Racing(String carNames) {
        this.cars = Arrays.stream(splitCarNames(carNames)).map(name -> new Car(name)).collect(Collectors.toList());
        this.moveStrategy = new RandomMoveStrategy();
    }

    public void race() {
        for (Car car : cars) {
            car.racing(RacingCarUtils.randomValueGenerator(), moveStrategy);
        }
    }
}

=> 로직에 적용

 

2주차미션(racing-car) 리뷰

소스코드: github.com/louisJu/java-racingcar-ex

 

느낀 점

  • 변수명 결정하는데 시간이 오래 걸렸음. 위에서 언급한 사이트들을 이용한다면 조금 빠르게 naming 을 할 수 있지 않을까?
  • 전략패턴을 학습 및 적용해보면서 코드가 좀 더 이뻐지는 느낌을 받음
  • 테스트 코드 작성에도 신경을 많이 써야할 것 같음
  • 스트림, 람다를 의식적으로 많이 쓰도록 하자!

회고 

 : 코딩을 하다보면 나도 모르게 익숙하고 편한 방법(레거시를 만들어내는) 하게 된다. 의식적인 코딩을 통해서 좀더 나은 코딩을 하도록 노력하자. 
: 이유없는 코드라인은 만들지 않도록 하자.

1주 차 강의 내용 정리

  • 객체 지향 설계 & TDD
  • Pre mission(숫자야구게임)에 대한 피드백 & 라이브 코딩

TDD 접근 방법

  • UI, DB, 외부 라이브러리에 의존하지 않는 도메인 영역을 집중 설계
  • 분리 설계된 도메인에 대한 로직을 테스트하는 것에 집중

단위 테스트 구조

TDD 사이클

TDD의 기본적인 Cycle

나의 연습 방법

  1. 가능한 상세하게 도메인 설계를 진행 (할 수 있는 만큼)
  2. 도메인 별 구현할 기능들 리스트 업
  3. 리스트를 바탕으로 테스트코드 작성
  4. 테스트 코드 compile 에러 발생하지 않도록 처리
  5. 기능 구현
  6. 테스트 코드 확인
  7. 리팩터링 & 3~6 반복하면서 개발 진행

* 정량적인 리팩터링 방법 *
=> 객체지향 생활체조 원칙 적용

  • 규칙 1: 한 메서드에 오직 한 단계의 들여 쓰기(indent)만 한다.
  • 규칙 2: else 예약어를 쓰지 않는다.
  • 규칙 3: 모든 원시 값과 문자열을 포장한다.
  • 규칙 4: 한 줄에 점을 하나만 찍는다.
  • 규칙 5: 줄여 쓰지 않는다(축약 금지).
  • 규칙 6: 모든 엔티티를 작게 유지한다.
  • 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 규칙 8: 일급 컬렉션을 쓴다.
  • 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

 

Pre-mission(숫자야구게임) 리뷰

소스코드는 github.com/louisJu/java-baseball-ex에서 확인할 수 있음.

 

설명 

: 패키지 구조 - domain과 view로 구분

: 일급 컬렉션을 사용 (ball count, strike count, baseball number를 객체로 래핑함)

: else, indent를 많이 신경 씀.

 

느낀 점

  • 시작 전 설계에 대한 깊은 고민 없이 개발하다 보니 자주 수정이 발생.
     => (테스트 코드도 수정이 자주 발생, 커밋할 시점 잡기 어려움, 복잡해짐)
  • 개발 전 꼼꼼한 설계에 대한 중요성 다시 느낌.
  • 기능의 역할 정의에 어려움
     => 입력한 야구 번호 검증하는 로직을 어디서(baseball.class or resultChecker.class) 처리하는 게 맞는지 고민이 많았음 
  • 도메인별 명확한 역할 할당, 다른 도메인에서 사용할 땐 메시지를 전달하여 결과만 받아오도록 처리(캡슐화)
    => 참조 depths(dot)를 한 단계로만 처리하도록 함. 
     ex) result.getBall().getBallCount (X) -> result.getBall()

회고 

 : 첫 시간이었고 현재 서비스에서 Node.js를 이용해서 개발하다가 오랜만에 java를 이용해서 개발하니 친정에 돌아온 듯 재미있었다. 잊고 있었던 원칙들이 새록새록 생각나서 즐겁게 고민하면서 코딩했다.

 

참고자료

객체지향 생활체조 원칙

+ Recent posts