콘텐츠로 이동

2024 02 12

2024-02-12

Kotlin Companion Object

참고: https://lannstark.tistory.com/141 참고: https://onlyfor-me-blog.tistory.com/441

  • 개요
    • 자바의 static 필드/메서드를 코틀린에서 쓰려면...?
    • 코틀린 문법에서는 클래스 안에 이걸 둘 수는 없어 (static 없음)
    • companion object 사용하세요! (오 Scala 같아!)
      • 하나의 클래스에 하나의 Companion Object 사용 가능
  • Java => Kotlin

    • Java
      public class Person {
          public static void sayHello() {
              System.out.println("hello!");
          }
      }
      
      public static void main(String[] args) {
          System.out.println(Person.sayHello());
      }
      
    • Kotlin
      class Person {
        companion object {
          fun sayHello() {
              println("hello!")
          }
        }
      }
      
      fun main() {
          Person.sayHello()
      }
      

JPA + Spring Batch - JpaRepository delete 쿼리가 안나감

참고: https://myvelop.tistory.com/116

  1. Select 쿼리만 나감 => JPA 대량 데이터 삭제 글 후술

    interface NoticeRepository: JpaRepository<Notice, Long> {
        fun deleteByIdBetween(startId: Long, endId: Long)
    }
    

    • 이건 Transactional 이 없어서 그래
    • DB에 변경이 가해지려면 트랜잭션 안에서 이루어져야겠지?
    • 현재 메서드에 트랜잭션을 열어주는 공간이 없다면, 변경에 대해서 DB에 커밋이 어려워
      • 주로 Spring Boot에서는 Service에서 해줬지만,,, 배치에서는 레포단이나 그냥 전체에서 해도 괜찮을 듯
  2. @Transactional 붙임

    interface NoticeRepository: JpaRepository<Notice, Long> {
        @Transactional
        @Modifying
        fun deleteByIdBetween(startId: Long, endId: Long)
    }
    

    • 이제 delete 쿼리 나감
    • @Modifying은 무엇이고 하니...
      • Spring Data JPA에게 해당 쿼리가 DB의 레코드를 변경시킨다는 것을 알려주는 것
      • 그냥 디폴트로 쓰는 CRUD Operation의 경우 (알아서 JPQL 만들어 주는 것)은 @Modifying 없어도 OK
      • 하지만, @Query 써서 직접적으로 DB에 접근하는 경우, DELETE/UPDATE/INSERT 쓸 때에는 @Modifying을 써주세요!!
        • 그래야 Spring Data JPA가 영속성 컨텍스트를 비워야겠구나~ 하는 시점을 알게 된 답니다
      • 참고로, @Modifying 쿼리는 Entity를 리턴할 수 없어
        • 왜냐면 해당 쿼리는 주로 영속성 컨텍스트에 엔티티 로딩하지 않고 수행되거든
        • 트랜잭션 컨텍스트랑 같이 써줘요!
  3. @Query 작성

    interface NoticeRepository: JpaRepository<Notice, Long> {
        @Transactional
        @Modifying
        @Query("delete from notice n where n.id between :startId and :endId")
        fun deleteByIdBetween(startId: Long, endId: Long)
    }
    

    • 이게 위에 처럼 쓰니까, 아니 delete 쿼리를 id별로 한개씩 내보내네;;;
    • 커스텀 JPQL로 쓰고, @Modifying 붙여주자!

JPA 대량 데이터 삭제 주의할 점

참고: https://jojoldu.tistory.com/235

  • delete 문제점
    • Spring Data JPA에서 deleteByXXX 메서드는
      • 삭제 대상을 전부 조회하는 쿼리 1번 발생 (위에 서술한 select만 나갔던 이유. 트랜잭션 없으니 지울라니까 뇌절)
      • 삭제 대상은 1건씩 삭제된다
      • cascase = CascadeType.DELETE로 하위 엔티티와 관계가 있다면 1건씩 삭제됨
  • 해결책
    • delete의 경우 직접 범위 조건의 삭제 쿼리를 생성하자!
      @Transactional
      @Modifying
      @Query("delete from Customer c where c.id in :ids")
      void deleteAllByIdInQuery(@Param("ids") List<Long> ids);
      

JPA + Spring Batch - saveAll()이 벌크로 동작 안 하는 이유

참고: https://imksh.com/113 참고: https://sabarada.tistory.com/220 참고: https://medium.com/@tmdghk502/springdata-jpa-bulk-insert-4a3e58d883ad

  • 개요
    • saveAll 메서드의 Entity는 왜 벌크로 동작하지 않는가?
  • saveAll() & save()
    • saveAll()에 List 넣게 되면 반복문 돌면서 결국 save()
    • List 내가 돌면서 save()하나, 그냥 saveAll()에 박아두나 똑같음
  • 자 여기서 PK(id) 전략에 따라 save()가 좀 다른데...
    • MySQL에서 직접 PK 관리하도록 IDENTITY 전략을 쓴다면?!
    • 영속성 컨텍스트에서 Entity Id 가져오고
      • ID = null -> 아 새로운 엔티티구나!
        • em.persist() 하려면 PK는 필수라서, 영속성 컨텍스트 등록을 위해 INSERT 쿼리가 바로 실행됨
      • ID != null -> 아 있는 놈이구나!
  • 그래서 결국 JPA에서는
    • ID 전략이 IDENTITY 이면서 ID가 없는 친구는 일단 영속성 컨텍스트에 등록하려고만 해도 ID가 필요하니,
    • INSERT가 그때그때 될것이야 (bulk 어려움)
    • Hibernate 자체에서 IDENTITY인 경우, Bulk Insert 안 되도록 막아뒀어!!
  • 저는 벌크로 넣고 싶은데요?
    • JdbcTemplate의 rewriteBatchedStatements 옵션을 쓰란다
      @Repository
      @RequiredArgsConstructor
      public class BulkInsertRepository {
      
          private final JdbcTemplate jdbcTemplate;
      
          public void saveAll(List<Dummy> dummies) {
              jdbcTemplate.batchUpdate("INSERT INTO dummy(name) values (?)",
                  new BatchPreparedStatementSetter() {
                      @Override
                      public void setValues(PreparedStatement ps, int i) throws SQLException {
                          ps.setString(1, dummies.get(i).getName());
                      }
      
                      @Override
                      public int getBatchSize() {
                          return dummies.size();
                      }
                  });
          }
      }
      
  • 다시 정리
    • MySQL 사용 + ID auto_increment + JPA 사용 -> JPA 철학에 의거, bulk insert 못함. 채번 전략 바꿔야 함
      • 대안으로 채번 전략을 바꾸던가 (Sequence, Table)
      • jdbc batchUpdate를 직접 쓰던가

Access-Control-Allow-Credentials

참고: https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98

  • 개요
    • Credentials: 쿠키, Authorization 인증 헤더, TLS 증명서 내포하는 자격 인증 정보
    • 브라우저의 요청 API들은 별도 옵션 없이 브라우저 쿠키와 같은 인증 데이터 함부로 담지 않음
    • 요청과 응답에 쿠키를 허용하고자 한다면, withCredentials 옵션으로 해결해보자!
  • withCredentials
    • 다른 도메인에 요청을 보낼 때 요청에 인증 정보를 담아서 보낼지를 결정하는 항목
      • 클라이언트에서 withCredentials: true
      • 서버에서 Access-Control-Allow-Credentials: true
  • 클라이언트 처리

    • axios
      // 1. axios 전역 설정
      axios.defaults.withCredentials = true; // withCredentials 전역 설정
      
      // 2. axios 옵션 객체로 넣기
      axios.post(
              'https://example.com:1234/users/login',
              { profile: { username: username, password: password } },
              { withCredentials: true }
      ).then(response => {
        console.log(response);
        console.log(response.data);
      })
      
    • fetch
      fetch("https://example.com:1234/users/login", {
          method: "POST",
          credentials: "include", // 클라이언트와 서버가 통신할때 쿠키 값을 공유하겠다는 설정
      })
      
  • 서버 처리
    1. Access-Control-Allow-Credentials: true
    2. Access-Control-Allow-Origin: "*"는 안 됨! 보안상 취약!
    3. Access-Control-Allow-Methods: "*"는 안 됨! 보안상 취약!
    4. Access-Control-Allow-Headers: "*"는 안 됨! 보안상 취약!