|  관련 SQL 개념

(1) Unique Key : 중복 데이터 저장을 방지하기 위한 제약조건

- 단일 칼럼 뿐 아니라, 복합 칼럼을 지정할 수도 있다.

- 아래와 같이 복합 키를 생성할 경우, DB 조회 속도를 향상시킬 수 있다. 

-- MySQL 기준
create table member(
    email varchar(50),
    name  varchar(255),
    unique key member_uk_email (email, name)
)

- 중복 데이터 저장을 막기 위한 방법으로 아래 두가지를 사용할 수 있다.

  * 단순 insert into 문의 경우 에러를 발생하지만, 아래 두 가지는 에러를 발생시키지 않는다.

INSERT IGNORE unique key가 걸린 칼럼에 중복 데이터가 이미 있는 경우 삽입 X
INSERT ~~~ ON DUPLICATE KEY UPDATE unique key가 걸린 칼럼에 중복 데이터가 이미 있는 경우 업데이트
insert ignore into member values ('jamie@gmail.com','JAMIE');

-- 0 raw affected (error x)
insert ignore into member values ('jamie@gmail.com','JAMIE');
insert ignore into member values ('jamie@gmail.com','JAMIE');
insert ignore into member values 
   on duplicate key update ('jamie@gmail.com','JAMIE');

-- 2 raw affected (error x)
insert ignore into member values 
   on duplicate key update ('jamie@gmail.com','JAMIE');
   
insert ignore into member values 
   on duplicate key update ('jamie@gmail.com','JAMIE');

(2) Index : 추가적인 쓰기 작업과 저장공간을 활용해 DB table의 검색 속도를 향상시키기 위한 자료구조

- Index는 다양한 자료구조를 통해 만들 수 있다.(ex. Hashtable)

- 가장 많이 사용되는 자료구조는, B+Tree 구조이다.

  * B+Tree 구조란, DB인덱스를 위해 자식노드가 2개 이상인 B-Tree를 개선시킨 자료구조를 말한다.

  * 일반적으로 조회시 O(logN)의 시간을 가진다.

- Index는 아래처럼 두 가지로 종류를 나눌 수 있다.

cluster index primary key를 통해 인덱스를 설정 (ex. 처음부터 정렬된 영어사전)
보조 index unique key를 통해 인덱스를 설정 (ex. 책 뒤의 찾아보기)

  * 참고 ) 

   cluster와 보조index를 같이 쓸 경우 INDEX로 입력할 수 있지만, 보조 index만 쓸 경우 UNIQUE INDEX를 써야한다.

  * 자세한 내용 : https://enterkey.tistory.com/417

- Index는 아래와 같이 테이블을 생성할 때 만들 수도 있고, 별도로 alter문으로 제약조건을 붙일 수도 있다.

create table member(
    id    bigint  primary key,  -- cluster key 
    email varchar(50),
    name  varchar(255),
    
    unique key member_uk_email (email, name),
    INDEX  member_idx (id, email)
)

- Index의 장단점

장점 - Index를 사용하면 조회 속도를 향상시킬 수 있어, DB 서버의 부하를 줄일 수 있다.
단점 - 하지만 추가 저장공간을 사용해야하고,
- insert, update, delete 같은 데이터 변경 쿼리가 자주 사용되는 경우에 인덱스를 쓰면
   paging이 빈번해져 성능이 악화될 수 있다.(== 조회 보다 db 변동이 많은 경우 불리)
- 추가적으로 cardinality가 낮은 경우(= 중복 데이터가 많은 경우), 인덱스를 사용하는 것이 비효율적일 수 있다.

   * Selectivility : 데이터 집합에서 특정 값을 얼마나 잘 골라낼 수 있는지에 대한 지표

      >> Selectivility = Cardinality / Total number of Records

      >> Selectivility = 1 <-- 모든 레코드가 유니크하다.

   * cardinality : 특정 데이터 집합의 유니크(Unique)한 레코드의 개수 

- Index를 설계할 때 알아둘 점

[효율적인 인덱스 설계 ]
- Where절에 사용되는 열
- Select절에 자주 등장하는 칼럼을 잘 조합해 Index로 만들면 조회 시간을 줄일 수 있다.
- JOIN절에 자주 사용되는 열
- Order by 절에 사용되는 열은 클러스터 인덱스가 유리하다.

[금지해야할 인덱스 설계]
- 대용량 데이터가 자주 입력되는 경우 primary보다 unique를 설정한다.
- 데이터 중복도가 높은 열은 인덱스 효과가 없다. (ex. 성별)
- 자주 사용되지 않으면 성능 저하를 초래할 수 있다.

 

|  스프링에서 중복 데이터 저장을 방지하기

1. Primary key 설정하기

-  사용할 칼럼에 @Id 를 넣어준다.
- @GeneratedValue 를 통해 auto_increment가 가능하게 한다.

  * auto : 자동선택, identity : db identity 칼럼 사용, sequence : 시퀀스를 쓰는 db vendor에서 사용

2. Unique Key & Index 설정

- @Entity 클래스에서, @Table(uniqueConstraints = {}) 를 통해 unique 제약조건을 설정한다.

- Unique key를 아래와 같이 설정하게 되면 해당 키를 곧 인덱스로 인식한다.

@Entity(name = "DIVIDEND")
@NoArgsConstructor
@Getter
@ToString
@Table( // 복합 unique키 설정 (중복 저장 X)
        uniqueConstraints = {
                @UniqueConstraint(
                        columnNames = {"companyId", "date"}
                )
        }
)
public class DividendEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long companyId;

    private LocalDateTime date;

    private String dividend;

    public DividendEntity(Long companyId, Dividend dividend) {
        this.companyId = companyId;
        this.date = dividend.getDate();
        this.dividend = dividend.getDividend();
    }
}

 

[ 참고 및 출처]

* 부트캠프 수업 내용 정리

* 인덱스 개념 참조 : https://mangkyu.tistory.com/96

* Cardinality와 Selectivility : https://soft.plusblog.co.kr/87

* 인덱스 핵심 설계 문법 : https://inpa.tistory.com/entry/MYSQL-%F0%9F%93%9A-%EC%9D%B8%EB%8D%B1%EC%8A%A4index-%ED%95%B5%EC%8B%AC-%EC%84%A4%EA%B3%84-%EC%82%AC%EC%9A%A9-%EB%AC%B8%EB%B2%95-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC

* 유니크 키를 통한 중복데이터 관리방법 https://umanking.github.io/2021/07/05/mysql-duplicate-record/

W3School에서 제공하는 MySQL 자료를 보고 있다. 

기초적인 내용들을 깔끔하게 설명해주고 있어서 메모해두었다.

 

- 현재 Data Type에 대해서 보고 있는 중..

https://www.w3schools.com/mysql/mysql_datatypes.asp

 

MySQL Data Types

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

1. 운영 서버와 테스트 서버의 DB 설정을 나누고 싶을 때

(1) 먼저 application.yml 에서 개발/운영 환경에 따라 DB 설정을 나눠준다.

https://1minute-before6pm.tistory.com/12

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Config-Data-Migration-Guide#profile-groups

(2) 그리고 나서 아래와 같이 Edit Configuraton의 Active prifiles를 설정한 profile 이름으로 바꾼다.

ㄴ [추가] Build.Gradle 의존성에 대한 이해

- 너무나 복잡하게 profile을 쓰고 db를 application.yml에서 나눴는데, 그럴 필요가 없단 걸 알게 됐다.

- 그냥 아래처럼 나누면 되었었다.

- implementation : 정말 라이브러리만 추가한 것을 말한다.

- runtimeOnly : 추가할 라이브러리는 런타임 시에 반드시 필요한 라이브러리임

- compileOnly : 추가할 라이브러리는 런타임 시에 반드시 필요하진 않음

* application.yml로 datasource를 통해 tomcat 또는 spring 실행 시에 사용할 db를 연결하는 것과는 다른 맥락

 

https://toss.tech/article/how-to-manage-test-dependency-in-gradle

 

테스트 의존성 관리로 높은 품질의 테스트 코드 유지하기

혹시 테스트 코드에서도 의존성을 관리해본 적이 있으실까요? 해당 포스트에서는 Gradle의 java-test-fixtures 플러그인을 사용하여 테스트 의존성 관리를 통해 높은 품질의 테스트 코드를 유지하는

toss.tech

 

 

2. 서버 메타데이터를 클래스로 뽑아서 관리하고 싶을 때

- 인증 메일을 보낼 때, 서버의 주소가 필요한데 그걸 매번 쳐주지 않고 주입시켜주려고 했다.

- application.yml에서 메타정보를 입력해서 가져올 수도 있는데, 더 우아한 방식이 있다고 해서 써봤다.

- 방법은 아래와 같이 서버를 클래스로 따로 설정해주는 것이다.

https://www.baeldung.com/spring-boot-configuration-metadata

/* 이메일 인증 시 서버 주소 필요하여 별도로 서버 설정 */
/* rf. https://www.baeldung.com/spring-boot-configuration-metadata */
@Configuration
@ConfigurationProperties(prefix = "server")
@Getter
@Setter
public class ServerPropertyConfig {

  private String name;

  private String ip = "127.0.0.1";

  private int port = 8080;

  private String address = String.format("http://%s:%d", ip, port);

}

 

3. Form 데이터를 Put/Delete로 전송할 때 Get으로 자동 변환 막기

- 이건.. 음 정확한 정보를 더 찾아봐야할 것 같다.

- 일단 알게 된 사항은 아래와 같이 hiddenmethod를 설정하면, 

  <input type = hidden> 을 통해 전달 받은 데이터를 PUT/DELETE로 처리할 수 있다는 것 정도이다. 

mvc:
  hiddenmethod:
    filter:
      enabled: true

 

4. H2 DB의 오류

[ 상황 ]

- ddl.sql과 schema.sql이 안 먹히고 datasource관련 exception이 터짐

[ 원인 ]

- 아래와 같이 schema.sql에 user를 적어두었었는데,

- H2 db의 경우 user는 내장된 예약어인지 어쩐지 어쨌든 사용하지 못하게 되어 있다고 한다.

drop table if exists user -- error

[ 해결 방법 ]

- cascade를 붙여서 작성한다.

drop table if exists "user" cascade

create table "user"
(

)

[ 그러나... ]

- 이 방법을 하게 되면 1.에서 development와 production에 따라 db vendor를 다르게 설정한게 의미가 없어진다.

- 왜냐고? production에서 나는 mysql를 쓸 건데, mysql은 또 "user"를 인식못한다. (이런..으귬느앎ㄴ;ㅣ러ㅏㅐㅂ쟛)

- 그래서 심플하게 개발단/운영단 나누어 작업하는 건 차후에 하기로 하고, 

  욕심부리지 않고 mysql만 사용하기로 했다. H2 진짜 어우 계속 화나게 하넴 ㅋㅋ 

 

 

|  개요

- 서비스에서 @Transactional 을 사용할 때와 사용하지 않을 때 수정과 삭제 시 차이를 보이는 걸 경험했다.

@Transactional update delete
사용 repository.save() 불필요 repository.delete() 시 삭제됨
미사용 repository.save() 필요 repository.delete() 시 삭제되지 않음

- 더불어 테스트에서 아래와 같이 @Transactional을 작성할 때, DB에 영구반영이 되지 않는 걸 확인했었다.

// 아래는 TEST 코드
@SpringBootTest
@Transactional
public class JpaMemoRepositoryTest{

    // 테스트 작성
    
}

- 왜 그럴까? 

(1) 수정 시 save하지 않아도 수정되는 이유

: @Transactional이 붙여진 메소드는 spring boot에서 트랜잭션으로 인식된다.  해당 메소드에서 DB의 row를 조회한 후, setter로 row에 변화를 준다고 하자. Hibernate는 persistent entities의 변동사항을 자동적으로 확인하고, 그에 따라 DB에 업데이트를 해준다. 

// default : dbms의 격리성 수준을 따르며, 부모와 별개로 실행되며, 읽기전용이 아님
@Transactional(isolation = Isolation.DEFAULT,
               progagation = Propagation.REQUIRED,
               readOnly = false){
    
}

(2) 삭제 시 @Transactional을 쓰지 않으면 삭제되지 않는 이유

: 이 부분은 정확하게는 모르겠지만, JPA의 PersistenceContext와 연관되어 있다고 한다. @Transactional을 명시하지 않을 경우, 해당 메소드는 트랜잭션으로 인식되지 않는다. 이 부분은 좀 더 알아보아야 할 것 같은데 save와 달리 delete는 삭제되지 않는다는 점만 일단 기록해두려 한다.

(3) 테스트에서 @Transactional을 쓸 경우 

: 스프링부트의 test로 지정된 패키지 내부의 클래스에 @Transactional을 쓰게 되면 모든 트랜잭션이 롤백되도록 되어 있다.

  따라서, 테스트에서 @Transactional을 쓸 경우 DB에 변동사항이 반영되지 않는다. 그럼에도 쓰는 이유는 서비스 로직을 파악하기 간편하기 때문. 검색해보면 일부 일부 사람들은 테스트에서는 해당 어노테이션을 쓰지 말 걸 권장하기도 하는데, 케바케인 것 같다.

(4) @Transactional와 연관해서 지연로딩과 즉시로딩에 대한 오류에 대해서도 알아볼 필요가 있다.

Lazy Loading 가능한 객체의 초기화를 지연시킨다. (참조하는 엔터티를 당장 쓰지 않을 경우)
Eager 객체의 초기화를 지금 당장 한다. (참조하는 엔터티를 지금 쓰는 경우)

-  A엔터티 내부에 @OneToMany 라는 Annotation으로 참조하는 B엔터티 데이터가 있다고 할 때, @OneToMany의 fetch type은 디폴트로 Lazy로 되어있다. 

- 이는 지연 로딩을 의미하는데, 지연 로딩의 경우, 참조된 B엔터티의 데이터들을 모두 읽지 않고 Proxy 객체(id값만 있음)를 통해 가져온다. 이 경우, 아래와 같이 코드를 작성하게 되면 오류를 발생시킨다. 

@RestController
class Controller{

    @GetMapping
    void findOneUserAndPetInfo(long userId){
    
    	User user = userService.findByUserId(userId);       
        List<Pet> pets = user.getPet(); // error!!
                
    }
    
}

- 이를 해결하는 방법은 서비스 메소드에 @Transactional을 붙이는 것이다. 그렇게 하면, Lazy Loading을 하더라도, 해당 메소드를 트랜잭션으로 인식해 필요한 데이터를 적시에 초기화한다. 

pf. https://zzang9ha.tistory.com/347

 

트랜잭션 개념

트랜잭션 : 데이터베이스의 상태를 변경시키기 위해 수행하는 작업단위

[출처] 코딩팩토리, https://wonit.tistory.com/462

- 트랜잭션은 DB의 상태를 변경시키는 것으로, CRUD(INSERT, SELECT, UPDATE, DELETE) 행위를 말한다.

(1) 트랜잭션의 연산 : Commit과 Rollback

- 트랜잭션은 순차적으로 Commit -저장-을 하며 실패해도 로그를 남겨, 앞의 트랜잭션이 다 끝나야 실제로 반영한다.

- 만약에, 트랜잭션이 비정상적으로 종료한다면 Rollback -철회-을 통해, 트랜잭션 전체 또는 부분적으로 결과를 취소한다.

(2) 트랜잭션의 상태

- 트랜잭션은 실행중(Active)이거나

  - 커밋 일부 완료(Partially Commited), 커밋 모두 완료(Commited)

  - 실행중 오류 발생으로 실패(Failed), 비정상적 종료로 rollback 수행(Aborted)

- 하는 총 5가지의 상태를 가질 수 있다.

(3) 트랜잭션의 특징

- 트랜잭션에는 4가지의 특징이 존재하는데,

ACID 내용
Atomic (원자성) All or Nothing : 모든 작업이 실행되거나 or 모두 실행되지 않아야 한다.

rf. 결제서비스에 관한 트랜잭션 중 갑자기 오류발생으로 끊긴다면? 
    ㄴ 결제서비스에 관한 트랜잭션 전체는 실행되지 않게 된다. 
Consistency (일관성) 트랜잭션 작업 결과는 항상 일관적이어야 한다.

* 모든 트랜잭션이 종료된 후엔 모든 DB의 제약조건을 지키고 있는 상태여야 한다.

rf. 웹툰 결제를 할 때 최소 결제 단위는 100원이어야 한다면, 
    결제 서비스 트랜잭션이 실행될 때 내 계정엔 100원 이상이 남아있어야 한다.
Isolation (격리성/독립성) 트랜잭션은 다른 트랜잭션과 독립적이어야 한다.

rf. 현실적으로 트랜잭션의 격리성을 지키기가 어렵다 (성능과 안정성의 trade-off)
- 격리성의 단계 ) 
  READ_UNCOMMITED > READ_COMMITED > REPEATABLE_READ > SERIALIZABLE
- 대체로 현장에서는 REPEATABLE_READ 를 한다고 한다.
Durability (지속성) 트랜잭션이 완료되면 영구적으로 결과에 반영되어야 한다.

* 트랜잭션은 순차적으로 commit되며, commit이 실패해도 모든 로그를 남겨 db에 반영된다.

 

|  트랜잭션을 사용해보기

1. @EnableTransactionManagement 달기

- 일일히 @Transactional 메소드에 붙이지 않고, @SpringBootApplication이 있는 클래스에 붙인다.

@SpringBootApplication
@EnableTransactionManagement
class XXXX{

}

2. 서비스 클래스 또는 각각의 메소드에 @Transactional 달기

 

|  여러 트랜잭션이 경쟁하면 생길 수 있는 문제

1. Diry Read

: 어떤 트랜잭션이 Row를 수정하는 중에 다른 트랜잭션이 해당 Row를 읽는 경우

* 계좌 정보를 읽고 잔액을 수정하는 중에 만약 다른 트랜잭션이 진입한다면?

작업1   작업2  
게시글 1번 조회 A    
게시글 1번 수정중 B 게시글 1번 조회 A
게시글 1번 반영 B 게시글 1번 수정중 C
    게시글 1번 반영 C

2. Non-repeatable Read

: 시간 간격 두고 특정 Row를 조회한 결과가, 다른 트랜잭션의 개입으로 달라지는 것 * 일관성을 위배

작업1   작업2  
게시글 1번 조회 A    
    게시글 1번 조회 A
    게시글 1번 수정중 C
게시글 1번 조회 C 게시글 1번 반영 C

3. Phantom-read

: 시간 간격을 두고 특정 범위를 조회한 결과가, 다른 트랜잭션의 개입으로 달라지는 것 * 일관성을 위배

작업1   작업2  
게시글 1번~4번 조회 A,B,C,D    
    게시글 1번 조회 A
    게시글 1번 수정중 E
게시글 1번~4번 조회 E,B,C,D 게시글 1번 반영 E

 

|  스프링에서 트랜잭션 처리를 지원하는 방식

- 여러가지 방식이 있으나, 가장 자주 사용하는 방식은 @Transactional이다.

- 이를 선언적 트랜잭션 방식이라고도 부른다.

(1) 서비스 메소드에 @Transactional 을 넣어 해당 작업이 트랜잭션임을 명시

(2) 트랜잭션 기능이 적용된 프록시 객체 생성

(3) PlatformTransaction manager가 트랜잭션 전체가 성공했는지 확인하고 Commit 또는 Rollback

- 스프링 트랜잭션의 세부 설정들

* 모든 트랜잭션을 동기화처리(synchronized) 하게 되면 속도가 너무 느려진다.

* 적절한 설정을 통해 속도와 안전성 두 마리 토끼를 잡자

pf. 전파 수준이 아직 이해가 잘 가지 않는다.. 관련 자료를 차후에 더 보고 정리하자.

https://n1tjrgns.tistory.com/266

https://www.baeldung.com/spring-transactional-propagation-isolation

세부 설정 내용
Isolation(격리수준) 트랜잭션에서 일관성이 없는 데이터를 허용하는 수준

- DEFAULT : DATABASE의 기본 격리성 레벨 
- READ_UNCOMMITED : 트랜잭션 commit 전 다른 트랜잭션 조회 가능 (Dirty Read 발생)
- READ_COMMITED      : 트랜잭션 commit 후 다른 트랜잭션 조회 가능 (Dirty Read 방지)
- REPEATABLE_READ  : 트랜잭션이 조회중인 특정 Row에 대해 shared lock을 걸어, 다른 트랜잭션이 해당 Row을 조회하지 못하도록 하는 것 (Non-repeatable Read 방지)
- SERIALIZABLE            : 트랜잭션이 사용하는 모든 데이터에 대해 shared lock을 걸어, 다른 트랜잭션의 개입을 완전 봉쇄하는 것 (Phantom read 방지)

// 현장에선 REPEATABLE_READ을 주로 사용한다고 한다.

@Transactional(isolation = Isolation.REPEATABLE_READ)
Propagation(전파수준) 트랜잭션 동작 중 다른 트랜잭션을 호출하는 상황에서, 
트랜잭션을 시작하거나 기존 트랜잭션에 참여하는 방법에 대해 결정하는 속성값

* 트랜잭션 A 안에 트랜잭션 B를 호출하는 경우,
  트랜잭션 A는 부모 트랜잭션 , B는 자식 트랜잭션이 된다.

* 참여한다 = 부모 트랜잭션의 흐름 안에 포함되어 동작한다.


- REQUIRED             
  : 디폴트 속성. 
  : 실행 중인 트랜잭션(부모)이 있으면, 해당 트랜잭션 뒤에 붙는다.
  : 새로운 트랜잭션을 생성한다.
  : 예외가 발생하면 롤백되며 호출한 곳에도 롤백이 전파된다.
   
- SUPPORT
  : 실행 중인 트랜잭션(부모)이 있으면, 해당 트랜잭션을 계속 진행한다.
  : 실행 중인 트랜잭션(부모)이 없으면, 트랜잭션이 아닌 일반 로직으로써 동작한다.

- REQUIRES_NEW
  : 실행 중인 트랜잭션(부모)이 있으면, 해당 트랜잭션은 대기하고, 새 트랜잭션을 만든다.
  : 예외가 발생해도 호출한 곳에 롤백이 전파되지 않는다.

- NESTED
  : = 중첩 트랜잭션.
  : 실행 중인 트랜잭션(부모)이 있으면, save point를 지정하고 새로운 트랜잭션을 생성한다.
  : 만약 자식 트랜잭션에서 예외가 발생할 경우, save point까지 롤백된다.
  : 만약 실행 중인 트랜잭션(부모)이 없을 경우, REQUIRED와 동일하게 작동한다.

ex. 일기작성을 부모 트랜잭션으로, 로그 저장을 자식 트랜잭션으로
- 로그 작성을 실패해도 일기 작성은 롤백되지 않는다.
- 일기 작성이 실패하면 로그 작성도 롤백된다.

rf. 예제
https://oingdaddy.tistory.com/28
ReadOnly 트랜잭션을 읽기 전용 속성으로 지정

* 성능 최적화, 특정 트랜잭션 안에서 읽기 외 작업이 일어나는 것을 방지할 때 사용

@Transaction(readOnly=true)
트랜잭션 롤백 예외 예외 발생했을 때 트랜잭션 롤백시킬 경우를 설정

@Transaction(rollbackFor=Exception.class) : 예외 상황 발생 시 롤백된다
@Transaction(noRollBackFor=Exception.class) : 어떤 예외가 되더라도 커밋된다

* 디폴트 : RuntimeException, Error
timeout 일정 시간 내에 트랜잭션 끝내지 못하면 롤백 

@Transaction(timeout=10)

 

|  정리

- 트랜잭션의 개념

  (1) 트랜잭션은, DB의 상태를 변화시키는 하나의 작업 단위, 다시 말해 CRUD 행위를 말한다.

  (2) 트랜잭션은, 커밋과 롤백이라는 두 가지 연산을 통해,
       총 5가지의 상태 (실행중, 일부 커밋, 완전 커밋, 실패, 롤백) 를 가질 수 있다.

  (3) 트랜잭션의 특징으로는 ACID가 있는데,

      - 원자성은 All or Nothing을 말하며,
      - 일관성은 작업 처리 결과는 늘 일관적이어야 한다는 의미이며, 
      - 독립성은 한 트랜잭션이 실행되고 다른 트랜잭션이 수행되어야 한다는 의미이며,
      - 지속성은 한 번 변화를 시켰으면 그것이 영구적으로 반영되어야 한다는 걸 의미한다. 

 

[ 참고 및 출처 ]

- 부트캠프 수업 내용을 정리

- https://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method

- https://kafcamus.tistory.com/31

- https://stackoverflow.com/questions/32269192/spring-no-entitymanager-with-actual-transaction-available-for-current-thread

- https://stackoverflow.com/questions/26601032/default-fetch-type-for-one-to-one-many-to-one-and-one-to-many-in-hibernate

- 기타 출처의 경우, 페이지 내 링크 참조

이 포스트를 DB 카테고리에 넣어야할지, REST API 카테고리에 넣어야할지 모르겠다.

갈 수록 카테고리들이 서로 연관되다보니 그냥 하나로 합쳐야하나 고민이 된다...

 

데이터를 삭제할 때에는

1. 논리 삭제

2. 물리 삭제

두 가지 방법을 사용할 수 있다.

 

* 관련 포스팅 : https://server-engineer.tistory.com/323

 

링크에선 물리 삭제는 일반적으로 개인정보보호가 필요한 경우에 사용한다고 했는데,

실제로 현장에 있을 때 회원 정보와 같은 민감 정보는 법에 위촉되지 않는 선에서 약관에 따라 일정 기간 동안 가지고 있다가 시간이 지나면 영구 삭제하는 것으로 알고 있다.

 

논리 삭제는 플래그 변수를 디비에 하나 추가해서 이게 삭제된 것임을 알려주는 것인데,

전에 기획을 할 때 개발자 분에게 '플래그 처리를 할 건지, 삭제를 할 건지' 여부를 질문 받았던 때를 기억해보니

데이터를 킵하는게 중요하거나, 차후에 고객으로부터 다시 데이터를 복구시켜 달라는 요청이 발생할 가능성이 있는 경우에 플래그 처리를 했던 것 같다.

그 때는.. 이런 것까지 하나하나 세세하게 기획서에 적어두는게 맞는 건가 싶었는데, 돌이켜보니 때에 따라 데이터 유실은 개발자에게 책임을 물릴 수 있을 민감한 요소라, 적어두면 좋았을 내용인 것 같다.

1. @AutoWired 동작원리

https://beststar-1.tistory.com/40

 

@Autowired의 동작원리

@Autowired란? 의존관계 주입(DI)을 할 때 사용하는 어노테이션(Annotation)이며, 의존 객체의 타입에 해당하는 빈(Bean)을 찾아 주입하는 역할을 한다. 💡 의존관계 주입에 대해서는 IoC(Inversion of Control,

beststar-1.tistory.com

 

2. 생성자 주입과 관련한 설명

https://jackjeong.tistory.com/41

 

[Spring] 생성자 주입 vs 필드 주입 (@Autowired)

안녕하세요~ 잭코딩입니다! 이번에는 스프링 프레임워크에서 의존성을 주입하는 방법을 살펴보고 어떤 방식으로 주입하는 게 좋은지 살펴볼까요? 우선 결론부터 말하자면 생성자 주입 (Constructo

jackjeong.tistory.com

 

1. 레디스 개념 및 특징

https://www.youtube.com/watch?v=Gimv7hroM8A 

https://www.youtube.com/watch?v=mPB2CZiAkKM 

- 이탈리아의 한 해커가 MySQL 로 작업 중 DB 처리 속도가 너무 느린 것을 보고 레디스를 만들었다.
- REDIS : Remote (외부에 있는) Dictionary (Key-Value형태) Server (서버)
- 공식문서에 따르면, Redis는 인메모리 기반의 data structure store로,
   db/cache/message broker, streaming engine으로 사용될 수 있다고 한다.
  * 단, 많은 개발자들은 Redis를 Store가 아닌 Cache라고 분류한다.
  * Redis는 지속성을 보장하기 위해 데이터를 Disk에 저장할 수 있다.
- Redis는 NoSQL로 분류되며, Key-Value 형태로 데이터를 저장하는데,
  Value 타입으로 문자열, 해쉬, 리스트, Set, sorted Set 형태의 다양한 자료구조를 지원한다.
- Redis는 기본적으로 Single-Threaded이며, 자료구조는 Atomic 한 성질을 가지고 있다. 
  * 서로 다른 트랜잭션에 대한 Read/Write를 동기화한다.

- 언제 쓸까? 
> 싱글 서버일 때 : Atomic 자료구조 + 캐시 기능 사용
ㄴ 이메일 인증 기능
ㄴ 좋아요, 조회수 처리
ㄴ 랭킹 보드로 사용
ㄴ 유저 API 제한 : ex. 주문 시 재고 확인
  * 캐시 : 여기서의 캐시란, 나중에 할 요청 처리를 미리 저장해 두었다가 빠르게 서비스해주는 걸 말한다.

- 주의할 점
> 싱글 쓰레드 서버이므로 시간 복잡도를 고려해야한다.
> 인-메모리 특성상 메모리 파편화, 가상 메모리 등의 이해가 필요하다.

- 싱글 쓰레드를 쓰는 이유?
> 비동기, 컨텍스트 스위칭 효율성 등

https://codingmania.tistory.com/18

https://blog.kingbbode.com/25

 

2. 레디스의 활용 사례

https://happyer16.tistory.com/entry/%EB%A0%88%EB%94%94%EC%8A%A4Redis%EC%9D%98-%EB%8B%A4%EC%96%91%ED%95%9C-%ED%99%9C%EC%9A%A9-%EC%82%AC%EB%A1%80

 

레디스(Redis)의 다양한 활용 사례

들어가기 전에 나는 아직 실력이 부족한 개발자여서 레디스=캐시 로의 의미로만 이해하고 있었다. 대부분의 서비스에서 단순 캐시용도로만 사용하기도 한다. 레디스에 대해 정확히 이해를 하지

happyer16.tistory.com

 

3. 레디스를 통해 캐시 처리하기

- 레디스를 언제 캐시로 사용하면 좋은가?

https://yonguri.tistory.com/82

- 레디스를 캐시로 사용한 사례(정리가 정말 깔끔하다.)

https://www.woolog.dev/backend/spring-boot/spring-boot-redis-cache-simple/#%EC%82%AC%EC%9A%A9-%ED%95%A0-redis-%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85

- 레디스를 통해 조회수 처리하기

https://dev-monkey-dugi.tistory.com/151

- 레디스를 통해 좋아요 처리하기

https://intrepidgeeks.com/tutorial/create-a-good-spring-boot-redis-cache-system.

 

4. 레디스의 Redisson을 통해 분산락 처리하기

https://kkambi.tistory.com/196

레디스를 통해 재고관리하기 https://mslim8803.tistory.com/74?category=1005542 

 

6. 레디스 사용 방법 (공식 사이트)

https://redis.io/docs/about/

 

Introduction to Redis

Learn about the Redis open source project

redis.io

 

자바 정규 표현식 문법

https://hbase.tistory.com/160

 

[Java] 정규표현식 사용법 및 예제 - Pattern, Matcher

자바에서 정규표현식(Regular Expression)'을 사용해보자. 1. 정규표현식(Regular Expression) 정규표현식 혹은 정규식은 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용되는 언어다. 정규 표현식은

hbase.tistory.com

https://coding-factory.tistory.com/529

 

[Java] 자바 정규 표현식 (Pattern, Matcher) 사용법 & 예제

정규표현식(Regular Expression)이란 컴퓨터 과학의 정규언어로부터 유래한 것으로 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 쓰이는 형식언어 입니다. 개발을 하다보면 전화번호, 주민등

coding-factory.tistory.com

 

정규 표현식 예제

https://www.wrapuppro.com/programing/view/MIw5kPB3ao2YJVx

 

[Javascript] 비밀번호 정규식 - 8자 이상의 문자열, 하나 이상 숫자, 문자, 특수문자 포함하기 : WRAPUP

웹취약점과 관련하여 비밀번호 변경 규칙을 지정하는 것은 필수입니다.사용자 패스워드 변경에 대한 규칙이 없으면 계정관리에 취약점이 생겨 해킹에 위험성이 높아집니다.또한, 이전에 사용

www.wrapuppro.com

 

정규 표현식 테스트 사이트

https://coding-factory.tistory.com/819

 

[Web] 정규표현식 테스트 사이트 모음 총정리

정규표현식을 사용하다 보면 이 문법이 맞는지 아닌지 헷갈리는 경우가 있습니다. 또 언어마다 정규표현식이 조금씩 다른 부분도 있어 테스트를 많이 해야 합니다. 개발을 할 때 자체 테스트로

coding-factory.tistory.com

 

https://gmlwjd9405.github.io/2019/10/28/intellij-jpa-erd.html

 

[IntelliJ] intellij에서 JPA Entitiy 기반의 ERD 그리기 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

|  개요

- @RequestParam, @RequestBody

- @RequestParam은 URI를 통해서 넘겨 받는 값을 말하고,

- @RequestBody는 Http body에 데이터를 묶어서 받는 값을 말한다.

- 일반적으로 RequestParameter는 ?key=value와 같이 단일 데이터를 넘겨 받고,

  RequestBody는 x-www-form-urlencoded나 Json과 같이 특정 데이터 포맷으로 묶여서 담겨지는 경우가 많다.

- Post 방식으로 넘겨 받는 URI 모습

  *body 값으로 다양한 데이터 포맷이 존재하는 걸 볼 수 있다. 

|  실험

- POST 방식으로 Body에 담겨 넘어오는 데이터의 타입을 크게 두 가지로 분류해보았다. (물론 데이터 타입은 더 다양하다)

  1) x-www-form-urlencoded (form 디폴트 타입)

  2) json 타입

- Annotation, @RequestParam, @RequestBody에 따라

  두 가지 타입의 데이터를 어떻게 읽어오는지 아래의 메소드들을 사용해서 확인해보았다.

// 아래 각 케이스에 대하여 x-www-form / json 타입의 요청을 전달한다.

// case 1 : 단일 데이터 + @RequestParam
@PostMapping("/register")
public void register(@RequestParam String id){
    System.out.println(id); 
}

// case 2 : 단일 데이터 + @RequestBody
@PostMapping("/register2")
public void register2(@RequestBody String id){
    System.out.println(id); 
}

// case 3 : 객체 데이터 + @RequestParam
@PostMapping("/register3")
public void register3(@RequestParam UserRegister userRegister){
    System.out.println(userRegister); //
}

// case 4 : 객체 데이터 + @RequestBody
@PostMapping("/register4")
public void register4(@RequestBody UserRegister userRegister){
    System.out.println(userRegister); //
}

----- ++ 추가적으로 Annotion 미사용시 케이스도 확인

// case 5 : 단일 데이터 + Annotation 없음
@PostMapping("/register5")
public void register5(String id){
    System.out.println(id); //
}

// case 6 : 객체 데이터 + Annotation 없음
@PostMapping("/register6")
public void register6(UserRegister userRegister){
    System.out.println(userRegister); //
}

 

|  결과

  x-www-form-urlencoded json
단일 객체 단일 객체
요청 ) id=test1234 id=test1234&pwd=1234 {
    "id":"test1234"
}
{
     "id": "test1234",
     "pwd":"1234"
@RequestParam test1234 400 에러** 400 에러* 400에러****
@RequestBody id=test1234 415 에러*** {
    "id":"test1234"
}
UserRegister(id=test1234, pwd=null)
Annotation 없음 test1234 com.example.demo.dto.UserRegister@498727e7 null com.example.demo.dto.UserRegister@498727e7

*400 : Required request parameter 'id' for method parameter type String is not present

**400 : Required request parameter 'userRegister' for method parameter type UserRegister is not present

***415 : Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

****400 : Required request parameter 'userRegister' for method parameter type UserRegister is not present

 

|  정리

- @RequestParam은 x-www-form-urlencoded 타입의 단일 변수 값을 읽어올 수 있다.

- @RequestBody는 json 타입의 단일 또는 객체 값을 읽어올 수 있다.

  x-www-form-urlencoded json
단일 객체 단일 객체
사용방식
@RequestParam 또는
Annotaion 미사
Annotation 미사용 @RequestBody @RequestBody 또는
Annotaion 미사

 

+ Recent posts