[스프링 인 액션] Ch 3

Ericamoyed·2021년 5월 8일
0

스프링인액션

목록 보기
2/3

데이터로 작업하기

JDBC를 사용해서 데이터 읽고 쓰기

  • Spring에서 관계형 데이터를 사용할 경우 사용 가능한 것은, JDBC, JPA
  • JDBC 지원은 JdbcTemplate 클래스에 기반
    • JdbcTemplate을 사용하지 않고, 자바로 직접 SQL 쿼리를 수행하려면?
      • dataSource 지정하고, prepareStatement로 ? 들어간 쿼리 작성하고, statement에 대해 쿼리 파람들 넣고 executeQuery 하면 쿼리 결과가 resultSet에 저장됨. 그래서 resultSet에서 getString 을 통해서 쿼리 결과를 가져와서 사용한다.
      • 저 과정에서 SQLException이 발생 가능한데, 얘는 checkedException이어서 반드시 try/catch 블록으로 감싸는 등 처리를 해야함
      • Exception 터졌을 때도 어디서 터졌는지 감지해야해서 if/else 덕지덕지.. -> 너무 전처리 후처리가 많다. 그냥 나는 쿼리만 작성하고 싶어..
    • 그렇다면 JdbcTemplate을 사용한다면?
      • jdbc.queryForObject({preparedStatment}, {queryParam})
      • exception 처리문이나 dataSource 지정 등 전처리/후처리 코드가 모두 사라진다 -> good
      • 요 친구를 사용하기 위해서는 pom.xml 에 JDBC 스타터 의존성을 빌드 명세에 추가해주면 된다

JDBCTemplate 이용하기

JDBC Repository 정의

  • 일단 필요한 요소들만 간단히 리스트업해서 볼 수 있도록 수행할 쿼리 method만 간단히 작성해서 IngredientRepository와 같은 이름으로 인터페이스를 만든다.
  • 그리고 실제로 그러한 method들이 어떤 쿼리를 수행해야하는지에 대한 정보는 위에서 작성한 Repository를 implement 하는 jdbcRepository를 만들어서, 거기서 정의한다. ex)JdbcIngredientRepository
    • 얘는 @Repository 어노테이션 붙여서, 스프링이 빌드할 때 이 클래스를 자동으로 찾아 빈으로 생성할 수 있게끔 해둔다.
    • 옹 JdbcTemplate은 기본으로 빈으로 생성되게 되어 있는데, 코드를 아래와 같이 작성만 해도 자동으로 JdbcTemplate jdbc 파라미터에 해당 빈이 자동 주입된다.
    private JdbcTemplate jdbc;
    @Autowired
    public JdbcIngredientRepository(JdbcTemplate jdc) {
      this.jdbc = jdbc;
    }
    • 회사 코드 느낌으로는 상위에 private 변수 지정할 때 autowired가 있고, 하위 코드는 작성할 필요가 없는데 요새는 요런 방식의 생성자 주입을 많이들 사용한다고 한다. 그리고 실제 우리 코드들 일부는 저런식으로 되어있긴 함.
    • 그리고 더 좋은건, 요새는 private final로 변수 지정하기만 하면, 만약 그게 빈에서 찾을 수 있으면 자동으로 주입해준다는 것이다. (@autowired 작성하지 않아도!) 생성자 주입 코드를 별도로 생성하지 않아도, 자동으로 빈 찾아서 생성자 주입을 시켜주는 것!
    • 여튼 저 repository 하위에 jdbc.query({preparedStatement}, {queryParam}) 요런식으로 쭉쭉 만들어둠
    • 우리는 Mybatis를 사용하기 때문에 요런 코드 없이, xml에 쿼리를 직접 명시하는 방식으로 사용할 수 있는 것!
  • jdbc~에 대해서 알아보기
    • Select 관련
      • jdbc.query: 쿼리 대상에 만족하는 것들을 list로 가져옴
      • jdbc.queryForObject(): 쿼리 대상에 만족하는 하나의 객체만 반환
    • Update, Insert 관련
      • jdbc.update

스키마 정의 및 데이터 추가

  • schema.sql을 통해 DDL (테이블 추가 및 생성 등의 작업)을 빌드시 자동으로 수행하게 할 수 있고,
  • data.sql을 통해 DML (데이터 삽입 및 수정 등의 작업)을 빌드시 자동으로 수행하게 하여 초기 데이터를 설정할 수 있다

JDBCTemplate을 사용해서 데이터를 저장하는 두가지 방법

  • 직접 template을 호출해서 update() 메소드를 활용
    • 아까랑 마찬가지로 쿼리 method만 간단히 작성해서 각 모델별 Repository interface 만들고, 걔네를 상속하는 진짜 레파지토리 JdbcRepository들 만들어서 쿼리 내용 채워줄 것이다.
    • jdbc.update()는 jdbc.query들과 달라서, (preparedStatment, queryParam)의 형태가 아니라 input으로 psc(preparedStatementCreater), keyHolder를 받는다.
    • preparedStatementCreater는 다행히도 기본제공하는 preparedStatementCreaterFactory에서 preparedStatement와 각 물음표에 들어갈 Type을 생성자 param에 넣고, newPreparedStatementCreater() 에서 각 물음표에 넣을 변수들을 제시해주면 작성 완료다
    • 생각해보면 jdbc.query()와의 차이는 각 ? 에 뭐가 들어갈지 type 지정해주는 정도? 밖에 없는듯
    • 그렇다면 keyHolder는?
      • 테이블에 하나의 행 추가시 DB에서 생성되는 ID를 알아야 이후에 참조가 가능한데 (약간 그거 같은거지 useGenerateKeys={}) 요 키를 뭐로 사용할지 제공해주고, 쿼리 수행 이후에 keyHolder.getKey()를 통해 궁극적으로 어떤 값이 삽입되어 생성되었는지 확인 가능
    • 얘네는 sample 보니까 Controller에서 repository 바로 부르던뎅.. 맞는 구조인지는 잘 모르겠음! 우리는 그렇게 안쓰니까 0_0
    • 마찬가지로 private으로 인스턴스 변수 지정해두고, 얘네를 param으로 가지는 생성자만들고, 생성자에 @Autowired 먹여 bean 주입 시키는 형태로 구성돼있음
    • @SessionAttributes
      • 요거는 다수의 http 요청에 걸쳐 존재해야하는 모델에 대해서 @SessionAttirubtes("order") 요런 식으로 컨트롤러 윗단에 달아둔다.
      • 다수의 http 요청에 걸쳐 존재해야하는 이유는, 한 order에 대해서 여러 타코를 담을 수 있어야하는데, 각 타코는 여러 http 요청에 대해 들어오기 때문. 각 요청마다 Order 객체를 공유해야함
    • @ModelAttribute
      • 지정한 객체가 모델에 생성되도록 해준다.. (내가 해석한 바로는 dto용new 객체를 후딱 생성해준 느낌. 우리가 하던 것 처럼 가져와서 딱 끝낼게 아니라 막 세션에 보존하고 등등.. 작업을 해야해서 new로 모델 객체를 만드는 생성자를 만들고, 거기에 얘는 DB에서 가져오는 데이터들 저장하거나 param으로 넘길 때 사용할거야 느낌으로 어노테이션 지정해준듯)
      • 매개변수의 값이 모델로 부터 전달되어 한다는 것과, 스프링 MVC가 이 매개변수에 요청 매개변수를 바인딩하지 않아야한다는 것을 나타낸다!
      • 함수 param에 hi(String name, @ModelAttribute Order order) 요런식으로 있으면, hi 호출 시에 그냥 String name만 신경써서 채우면 된다는 것. 왜냐면 모델어트리뷰트 있는건 위에 지정되어 있는 거 가져다 쓸거야.
  • SimpleJdbcInsert wrapper Class를 사용할 수 있다!
    • 데이터를 더 쉽게 테이블에 추가하기 위해 JdbcTemplate을 래핑한 객체
    • 여기서 Jackson이 쓰이는 이유는?
      • jackson은 본래 json 처리용인데, 여기서 쓰이는 이유는 객체 input을 map으로 변형하여 쿼리 내부에서 사용할 수 있는 형태로 바꿔주기 위해 사용
      • Jackson ObjectMapper와 convertValue() 메서드를 사용하면 Order 객체를 Map으로 변환 가능하다
      • 하지만 단점은,, ObjectMapper는 DateTime 값을 시간 타입으로 변환하는 것이 아니라 long 타입의 값으로 변환해서 Date 타입의 값은 직접 map에 넣어주는 것이 좋을 것이다.
    • 마찬가지로 @Autowired 붙어있는 생성자가 있음 -> 생성자 파라미터는 자동으로 autowired됨
      • 근데 최신버전 Java에서는 생성자에 @Autowired를 붙이지 않아도 생성자 param의 선언이 final로 되어있으면, 자동으로 자동주입 해준다고 함 ㅎㄷㄷ
    • db가 생성해주는 PK/AI 값을 사용하고 싶다면
    this.orderInserter = new SimpleJdbcInsert(jdbc)
    .withTableName("Taco_Order")
    .usingGeneratedKeyColumns("id");
    • 요런식으로 usingGeneratedKeyColumns를 builder 패턴에 넣어주면 된다
    • SimpleJdbcInsert의 두개의 유용한 메서드: execute(), executeAndReturnKey()
      • 두 메서드 모두 Map<String, Object>를 인자로 받는데, key는 테이블 컬럼명과 대응, value는 추가되는 값에 대응
    • SessionStatus: 객체가 DB에 저장된 이후에는 더이상 세션에 보존할 필요가 없으므로 저장된 이후에 sessionStatus.setComplete()를 통해 세션을 재설정한다
    • convert 클래스
      • Converter에 지정한 타입 변환이 필요할 때 convert() 메서드가 자동 호출되어 캐스팅을 커스터마이징할 수 있다.
      • ex) public class IngredientByIdConverter implements Converter<String, Ingredient>
      • 요 클래스 내부에서 convert 메소드를 오버라이딩해서 정의하면 되는데,
        public Ingredient convert(String id) 시그니처를 가지게 선언하면 된당 :)

스프링 데이터 JPA

다양한 스프링 데이터 프로젝트들

  • JPA: 관계형 데이터베이스 / MongoDB: 몽고 문서형 DB / 레디스: 키-값 스토어형 DB / Cassandra: 카산드라 데이터베이스
  • 스프링 데이터에서는 해당 인터페이스들을 구현하는 repository를 자동 생성해줌
  • 데이터 JPA는 JPA 스타트럴 통해 사용 가능한데, JPA를 구현한 Hibernate 도 함께 지원한다
    • Hibernate ?
  • 사용 방법
    • 일단 model 객체에 해당 모델에 대해서 JPA 매핑으로 사용할 것이라는 @Entity 어노테이션을 붙여줘야함
    • 그리고 id 속성(PK/Unique 속성)에 대해서는 @Id 어노테이션을 붙여줄 것
      • @GeneratedValue 속성을 붙여주면 상기에서 얘기했던 useGeneratedKeys와 동일한 역할을 하도록 해당 컬럼을 설정할 수도 있다
    • 그리고 반드시 @NoArgsContructor도 가져야함. 해당 예시에서는 @NoArgsContructor(Access=accessLevel.PRIVATE, force=true)로 되어있는데, access를 통해서는 클래스 외부에서 noargscontructor를 사용할 수 없도록 구성했고, force 속성을 통해서 모델 내에 final로 선언되어 있는 속성들에 대해 default로 null을 채우도록 했다
    • @Data 어노테이션은 원래 인자가 있는 생성자를 자동으로 추가하는데, @NoArgsContructor를 적용하면 해당 생성자가 제거돼버린다. 따라서 @RequredArgsContructor를 추가하여 다시 인자가 있는 생성자를 사용할 수 있도록 하는 것이 좋다
    • @PrePersist: 객체 저장 전에 수행하는 method에 붙인다. 예를들어 createYmdt를 현재 시간으로 지정한다거나.
    • 모델에 @Table 을 붙여주는 경우가 있는데, 요거는 해당 모델이 실제 RDB의 테이블 명과 다르거나, 해당 모델명이 그대로 테이블명에 사용되면 예약어 등의 이유로 이슈가 있을 때 붙여주면 된다.

JPA Repository

  • CrudRepository를 확장하여 구현한다.
    ex) public interface YujinRepository extends CrudRepository<Yujin, String>
    첫번째 param: Repository에 저장되는 개체 타입, 두번째 param: 해당 개체의 ID 속성의 타입
    • 음.. 근데 보다보니 이상하네 .. ? extends 했다는건 CrudRepository가 인터페이스가 아니라 class라는건데 class를 확장한 interface가 있을 수 있나?
  • 놀라운건 extends 하여 상기 예시처럼 시그니처만 구성해두면, 그 하위에 아무것도 작성하지 않아도 되고, 요 인터페이스를 확장한 class도 구성할 필요가 없다!! -> 요게 JPA의 마법
    • 왜냐하면 스프링 데이터 JPA가 애플리케이션 시작 시에 각 인터페이스 구현체를 자동으로 생성해주거든
    • 어떤식이냐면 readOrdersByDeliveryZipAndPlacedAtBetween() -> 요 이름은 DeliveryZipPlacedAt 값을 가지고 List<Order>를 읽어오게 됨
    • 하지만 실질적으로는 이름보다는,, 시그니처의 parameter들과 returnType을 가지고 JPA가 또닥또닥 한다고 한다. 즉, readOrders 대신 readErica가 들어가도 returnType이 List라면 Order를 읽어온다는 것 !
  • 근데 기본적으로 지원되는거 말고 내가 커스터마이징 하고 싶으면, 상기에서 선언한 인터페이스 하위에 원하는 abstract 메소드를 작성하면 된다. (기본적으로 제공되는 메소드 명과 다른 메소드 사용 가능)
  • 쿼리까지 지정해주고 싶으면 해당 메소드에 @Query 어노테이션을 달아, 수행하려는 쿼리를 명시해주면 된다

요약

  • JPA가 django ORM 처럼 class에 선언된대로 table 새로 만들어주고, migration 해야하고 그런건줄 알았는데, 그런건 아니여서 DB 필드들을 class에 매핑하는 DTO를 만드는 작업은 여전히 필요하지만 repository 구현 및 xml 작성 (Mybatis의 경우)이 불필요하다 정도의 장점이 있는 듯 하다
profile
꿈많은 개발자, 일상 기록을 곁들인

0개의 댓글