기록은 기억의 연장선

더 많은 것을 기억하기 위해 기록합니다


  • Home

  • Tags

  • Categories

  • Archives

  • Search

jhipster

Posted on 2018-11-25 | Edited on 2020-11-02 | In etc | Comments:

정의

https://inyl.github.io/programming/2016/10/15/JHipster.html

Spring Boot + Front end + etc… 등등으로 구성된 프로젝트를 generate 해주는 서비스이다.
현재는 yo jhipster 대신에 jhipster 명령어만 입력해서 실행시킬 수 있다.
이후 나오는 선택사항들을 토대로 프로젝트가 generator 된다(아주 방대한 양의 소스가 generate 된다)
지원하는 기술이 굉장히 많다.
msa 프로젝트 초기 구성시에 좋다.

msa 프로젝트 generate 예시

  1. service type - Microservice Application
  2. project name
  3. port
  4. package name
  5. discovery server - jhipster registry
  6. authentication type - jhipster UAA
  7. uaa project path
  8. database type - SQL(H2, MySQL…)
  9. production database - MySQL
  10. development database - MySQL
  11. cache abstraction - Hazelcast
  12. 2nd level cache - Yes
  13. build tool - Gradle
  14. other technology - OpenAPI generator, Kafka
  15. internationalization - Yes
    1. native language - Korean
    2. additional language - English
  16. test framework - Cucumber
  17. other generator - No

msa 설정 시 추가로 해줘야할 부분

jhipster-registry 설정

msa 구성 시 eureka 서버를 사용해야 하는데 jhipster-registry가 그 역할을 해준다.
그러므로 클론받고 띄워줘야 한다.

1
2
3
4
git clone https://github.com/jhipster/jhipster-registry

cd jhipster-registry
./mvnw

kafka 설정(선택했을 경우)

jhipster init 할때 kafka를 설정했을 경우, docker로 kafka를 실행한다

1
2
cd {jhipster_project_home}
docker-compose -f src/main/docker/kafka.yml up -d

jdl

jhipster에서 도메인을 나타낼 떄 사용하는 language이다.
https://www.jhipster.tech/jdl/

작성한 도메인의 관계도는 아래의
https://start.jhipster.tech/jdl-studio/
에서 가시적으로 확인해볼 수 있고,
.jh 확장자로 따로 저장도 가능하다.

작성한 .jh 파일은 아래의 명령을 통해 jhipster에 적용 갸능하다.

1
jhipster import-jdl <jdl file path>

위 명령을 수행하면 작성한 내용을 기준으로 entity, repository를 생성하고 생성된 repository를 사용하여 기본적인 CRUD를 호출하는 controller를 만들어준다.
(초반 generate 시에 위 파일을 지정해줄수도 있다고 한다)

하지만 생성된 entity, repository를 그대로 사용할수는 없으니(당연하다)
초반 와꾸잡는데만 사용하고, 그 시점이후로는 jdl 파일을 건드리진 않는다.

Read more »

[spring] Spring ApplicationEvent

Posted on 2018-11-25 | Edited on 2020-11-02 | In spring | Comments:

http://wonwoo.ml/index.php/post/1070#comment-7184

event를 발생시키는 publisher(ApplicationEventPublish),
event를 받는 lister(ApplicationEventLister),
event가 있음(ApplicationEvent).

publisher에서 event를 생성해서 발생시키면
lister에서 자신이 구현한 event가 맞을 경우 그 이벤트를 받을 수 있다

스프링 4.2 부터는 어노테이션으로 가능하고, 많은 옵션들(spel 등)을 줄 수 있다.

Read more »

회사 용어

Posted on 2018-11-25 | Edited on 2020-11-02 | In etc | Comments:

TBD

To be determined. 결정 예정

TBU

To be updated, 업데이트 예정

ASAP

As soon as possible, 가능한 빨리, ‘아삽’이라고도 읽음

Read more »

mac custom setting

Posted on 2018-11-22 | Edited on 2020-11-02 | In etc | Comments:

mac을 편리하게 사용하고자 직접 세팅한 내용을 공유하고자 한다.

일반 키보드

레오폴드 키보드 사용

  1. 한영 전환
    아래의 내용을 btt에서 똑같이 사용했다
    https://jojoldu.tistory.com/345?category=798573

  2. alt키를 command key로 사용하기
    환경설정 - 키보드 - 보조키에서 option, command 키 바꿀 수 있음

  3. home, end 사용하기
    ~/Library/KeyBindins/DefaultKeyBinding.dict를 수정하면 된다
    http://junho85.pe.kr/580

key bindings 값 찾기
http://junho85.pe.kr/579

  1. windows의 aero snap 사용하기
    btt - keyboard
    Command + Control + 방향키로 Maximize Window Left, Right, Top Half, Bottom Half 를 설정했다

  2. 최대화
    btt - keyboard
    Command + ctrl + A = 현재 위치에서 최대화
    Command + ctrl + S = 다른 모니터(듀얼모니터)로 넘기기
    Command + ctrl + D = 다른 모니터로 넘기기 + 최대화

일반 마우스

앞으로 가기, 뒤로가기가 있는 버티컬 키보드 사용

  1. 앞으로 가기, 뒤로가기 매핑
    btt - normal mice
    button3에 뒤로가기, button4에 앞으로 가기 매핑했다.
    action을 사용하지 않고 Command + [, Command + ]로 매핑했다.
    이렇게 하면 intellij 에서도 마우스로 앞뒤로 왔다갔다 할 수 있다.

  2. swipe 매핑
    btt - drawings
    오른쪽에서 왼쪽으로 그리면 right swipe
    왼쪽에서 오른쪽으로 그리면 left swipe

  3. mission control
    btt - drawings
    위로 그리면 mission control

  4. 닫기
    btt - normal mice(chrome)
    마우스 중간 버튼을 누르면 ctrl + w가 동작하도록 했다

  5. 새로고침
    btt - drawings(chrome)
    아래로 길~게 내리면 Command + r 이 동작하도록 했다(스마트폰 처럼)

기타

  1. iterm에cmd + 좌우 동작안하고 fn 키로만 됨
    https://stackoverflow.com/questions/6205157/iterm-2-how-to-set-keyboard-shortcuts-to-jump-to-beginning-end-of-line
    설정에서 hex code 입력으로 바꿔주면 됨

  2. 듀얼 모니터
    MonitorControl application 으로 밝기, 소리 조절 가능
    https://github.com/the0neyouseek/MonitorControl

해결하지 못한 부분

  1. keyboard로 듀얼 모니터간 포커스 전환
    개발하다 보면 마우스보다 키보드를 많이 쓰는데, 반대편 모니터에 있는 애플리케이션을 사용하려면 결국 마우스로 클릭해야 한다.
    Command + tab으로 하나하나 찾기는 너무 답답하고, 반대편 모니터를 마우스로 찍는 행위만 해주면 좋을텐데…

  2. 오른쪽 command의 본래 기능 없애기
    오른쪽 command를 한영키로 사용하니까 문자키와 한영키를 눌렀을때 command키가 동작해서 난감하다
    q -> ㅂ로 바꾸다가 Command + Q가 되어버리는…

Read more »

[jpa] 엔티티 매핑

Posted on 2018-11-22 | Edited on 2020-11-02 | In jpa | Comments:

@Entity

JPA가 관리하는 엔티티가 되기 위해서 필수로 붙여야 하는 어노테이션이다.

속성 기능 기본값
name JPA에서 사용할 엔티티 이름 지정. 중복되는 이름이 있어선 안된다. 클래스 이름 그대로 사용(e.g. Member)

(name은 나중에 JPQL 작성할 때 사용된다.)

엔티티가 될 클래스에는 몇가지 룰이 존재한다.

  • 기본 생성자 필수(public or protected)
  • final class, enum, interface, inner class 에는 사용 불가
  • 저장할 필드에 final 사용 불가

이 제약조건은 proxy 패턴, reflection에서 자주 등장하는 용어이다.
proxy 패턴을 사용하려면 대상 클래스를 상속 받아야 하는데, final class, enum, interface, inner class는 상속이 불가능하다.
reflection으로 클래스를 생성할 때, constructor는 대부분 사용하지 않는다.
외부에서는 생성자의 매개변수들이 무엇을 의미하는지 알수가 없기 때문이다.
그러므로 대부분 기본 생성자 + setter를 사용하는데, final은 setter 호출이 불가능하다.

아마 이런 이유일거라고 생각하고, JPA는 결국 proxy 패턴과 reflection을 쓴다는 것을 알 수 있다.
(틀렸을수도 있음)

@Table

엔티티와 매핑할 테이블을 지정할 때 사용한다. 생략하면 엔티티 이름을 테이블 이름으로 사용한다.

속성 기능 기본값
name 매핑할 테이블 이름 엔티티 이름
uniqueConstraints(DDL) DDL 생성 시에 unique constraints 만듬. 2개 이상의 복합 unique constrains도 가능

기본 키 매핑

위에서도 언급했듯이 기본키는 필수값이다.
@Id 어노테이션을 사용해서 지정할 수 있다.

1
2
3
4
5
class Member{
@Id
@Column(name="id")
private String id;
}

@Id 적용 가능한 타입은 아래와 같다.

  • 자바 기본형
  • 자바 wrapper형

nullable한 컬럼 때문에 primitive 대신 wrapper를 사용하는 것을 권장(?)한다.
상황에 따라 쓰면되긴 하지만, 알관성을 위해서라도 하나만 쓰는 것이…

  • String
  • java.util.Date, java.sql.Date
  • java.math.BigDecimal, java.math.BigInteger

기본키 생성 전략

엔티티의 기본키를 설정하는 방법에는 크게 직접할당과 자동생성이 있다.
직접할당은 말 그대로 기본키 값을 애플리케이션에서 직접 할당하는 방법이다.
자동생성의 경우 직접할당과 반대로 데이터베이스에 의존하는 방식이다.

자동생성을 사용히려면 기본키 컬럼에 @GeneratedValue 어노테이션 + 전략(strategy)을 지정해줘야 하며, 전략들은 아래와 같다.

  1. IDENTITY 전략
    MYSQL의 AUTO_INCREMENT와 같다고 보면 된다.
    아래와 같이 선언해주면 된다.
1
2
3
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

row insert시에 데이터베이스가 자동으로 생성해주는 기본 키 값을 사용하는 방식이다.
이 말인 즉 데이터베이스에 직접 insert 하는 작업이 선행되어야 한다는 뜻이므로,
JPA의 쓰기지연이 동작하지 않는다는 의미이다.

원래라면 insert 1회 + select 1회(저장된 로우의 기본키 값을 얻어오기 위해)로 2번 튱산해야 하는데,
하이버네이트는 JDBC3 부터 추가된 Statement,getGeneratedKeys()(저장과 동시에 생성된 기본 키 값을 얻어오는 메서드)를 사용함으로써 데이터베이스와 한번만 통신한다(최적화)

  1. SEQUENCE 전략
    ORACLE의 SEQUENCE와 같다고 보면 된다.
    기본적으로 SEQUENCE가 생성되어 있어야한다. 그리고 아래와 같이 선언해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR", // 사용할 sequence 이름
sequenceName = "BOARD_SEQ", // 실제 데이터베이스 sequence 이름
initialValue = 1, allocationSize = 1
)
public class Board{
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR" // 위의 sequence 이름
)
private Long id;
}

이제 id 식별자 값을 얻어올 때 마다 BOARD_SEQ 시퀀스에서 식별자를 조회해오게 된다.
조회한 식별자를 엔티티에 할당한 후, 영속성 컨텍스트에 저장한다.
이후 flush가 발생하면 엔티티를 데이터베이스에 저장한다.
(identity와 달리 insert를 선행할 필요 없어므르 쓰기지연을 사용할 수 있다)
하지만 결과적으로 보면 데이터베이스와 2번 통신하는 셈이다(select 1회 + insert 1회)
@SequenceGenerator를 통해 생성기를 등록해야 한다.

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다. 1
allocationSize 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨) 50
  • initialValue는 sequence 초기값을 설정할 떄 사용하는 옵션인데, 이말인 즉 sequence도 미리 생성해놓지 않으면 자동 생성 가능하다는 것이다(DDL 자동생성을 on 하면 됨)

  • allocationSize는 시퀀스 한번 호출에 증가하는 수이다.
    default 값이 50인데, 이는 최적화를 위해서이다. 1-50까지의 sequence 값을 한번에 받고 메모리에 저장해서 할당해주다가, 51번째 sequence가 필요할 떄 데이터베이스 sequence에서 51-100의 sequence를 조회해오는 식으로 동작한다.

  1. TABLE 전략
    sequence를 흉내내는 전략이다. table을 하나 만들어 name과 sequence 값을 저장해둔다.
    table을 사용하므로 모든 데이터베이스에서 사용 가능하다. 사용법은 sequence와 거의 동일하다.
    @TableGenerator를 통해 생성기를 등록해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@TableGenerator(
name = "MY_BOARD_SEQ_GENERATOR", // 사용할 table sequence 이름
table = "MY_BOARD_SEQ", // 실제 데이터베이스 table 이름
pkColumnValue = "BOARD_SEQ", allocationSize = 1
)
public class Board{
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "MY_BOARD_SEQ_GENERATOR" // 위의 sequence 이름
)
private Long id;
}

기본적으로 테이블은 sequence_name, next_val 의 컬럼을 가진 형태로 생성되고, 로우의 내용은 아래와 같다.
(이름이 맘에 안들면 pkColumnName=XXX, valueColumnName=XXX 의 형태로 지정해주면 된다.)

sequence_name next_val
BOARD_SEQ 2
MEMBER_SEQ 7
PRODUCT_SEQ 50

보다시피 하나의 테이블로 관리하므로, pkColumnValue로 어떤 sequence_name을 사용할지 지정해줘야 한다.

참고로 table key는 양이 많아질수록 성능이 급격히 안좋아지므로, 사용하지 않는 것이 좋다.
https://vladmihalcea.com/why-you-should-never-use-the-table-identifier-generator-with-jpa-and-hibernate/

  1. AUTO 전략
    선택한 데이터베이스 dialect에 따라 전략(IDENTITY, SEQUENCE, TABLE)을 자동으로 선택해주는 방식이다.
    예를 들면 오라클은 SEQUENCE, MYSQL은 IDENTITY가 선택된다.
    @GeneratedValue의 기본값은 AUTO이다.

auto_increment랑 sequence를 양쪽 다 지원하는 데이터베이스가 있을 경우(PostgreSQL)
sequence를 우선적으로 선택한다고는 하는데, db마다 다를 수 있을 듯 하다.

참고로 @GeneratedValue를 사용한 Id 컬럼에 대해서는 wrapper형을 써주는 것이 좋다.
primitive 타입의 경우 초기화 하지 않을 경우 값이 0인데, 이는 명시적이지 않기 때문이다.
(0으로 세팅한건지, 값이 세팅하지 않은건지 모호함)
물론 기본 auto_increment가 1부터 시작하기 때문에 0일 경우 ORM이 id를 generate 해줘야겠다고 판단하겠지만, 그래도 명시적인게 좋다고 생각한다.
엔티티 모델링에서 boxing/unboxing 비용은 큰 관심사가 아니다.

식별자 권장 전략

기본키의 형태는 크게 자연키(비즈니스 의미가 있는 키. e.g. 주민등록번호)
와 대리키(임의로 만들어진 키)가 있는데, 외부풍파에 쉽게 흔들리지 않는 대리키를 사용하는 것아 좋다.
비즈니스라는 것은 내 생각보다 훨씬 쉽게 변하기 때문이다.

필드 매핑

@Column

객체를 필드 테이블에 매핑할 때 사용한다. 가장 많이 사용된다.

속성 기능 기본값
name 필드와 테이블 이름 매핑 객체 필드 이름
nullable(DDL) null 값 허용 여부. false 설정하면 DDL 생성 시 not null이 붙는다 true
unique(DDL) 컬럼 하나에 unique constraints 지정할 때 사용. 여러개 지정하려면 @Table의 uniqueConstraints를 사용해야 함
length(DDL) 문자 길이 제약조건. 명시적으로 길이를 볼 수 있는 장점도 있다 255

@Column을 생략해도 엔티티의 필드는 전부 자동으로 테이블과 매핑된다.
이 때 몇가지 특징이 있다.

  1. 이름은 어떻게 매핑되는가?
    @Column의 기본값과 동일하게 컬럼명으로 사용된다. 근데 여기서 딜레마가 하나 있다.
    java는 naming을 관례적으로 camel case를 사용하고, database는 naming을 관례적으로 under score를 사용한다는 것이다.
    이떄 persistence.xml에
1
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"></property>

전략을 주게 되면 위의 딜레마를 해결 가능하다(서로간에 자동 변환)

  1. nullable 속성
1
2
3
4
5
6
int id; // not null로 생성됨. primitive에는 null이 들어갈 수 없기 때문.

Integer id; // nullable true로 생성됨

@Column
int id; // @Column의 기본값인 nullable=true가 적용되서 nullable=true로 생성됨. 주의해야함

보다시피 3번쨰 방법은 nullable=true 임에도 불구하고 null을 넣을 수 없다.
이런 상황을 위해 그냥 primitive 대신 wrapper형을 써주는 것이 좋다.
(not null에는 primitive, nullable에는 wrapper형을 쓸수도 있지만 통일시키지 않아서 오는 불편함이 더 클것이다)

@Enumerated

java의 enum 타입을 매핑할 때 사용된다. 유용하게 사용 가능하다.

1
2
@Enumerated(EnumType.STRING)
private RoleType roleType;

이렇게 주면 enum의 값 그대로(문자열) 데이터베이스에 저장된다.
ORDINAL을 속성을 사용하면 enum의 순서대로 index가 데이터베이스에 저장되는데,
유연하지 못하므로 STRING 속성을 사용하는 것이 낫다.

@Temporal

날짜 타입을 매핑할 때 사용된다.

1
2
3
4
5
6
7
8
@Temporal(TemporalType.DATE)
private Date date; // date date 생성

@Temporal(TemporalType.TIME)
private Date time; // time time 생성

@Temporal(TemporalType.TIMESTAMP)
private Date timestamp; // timestamp timestamp 생성

자바의 Date 타입에는 년월일 시분초가 있지만, 데이터베이스에서는 date, time, datetime 3가지 타입이 존재한다.
그러므로 @Temporal을 생략하였을 시, 가장 비슷한 timestamp가 지정된다.
@Temporal은 Date, Calendar에만 붙이는 속성이고,
java8 부터 등장한 LocalDate, LocalTime, LocalDateTime에는 @Temporal 속성을 붙일 수 없다.

java8 날짜 타입들은 jpa가 바로 인식하지 못하므로 추가적인 조치가 필요하다
https://homoefficio.github.io/2016/11/19/Spring-Data-JPA-에서-Java8-Date-Time-JSR-310-사용하기/

@Lob

BLOB, CLOB 타입에 매핑된다.

1
2
3
4
5
@Lob
String lob; // CLOB으로 매핑. mysql에선 longtext로 생성됨

@Lob
byte[] lob; // BLOB으로 매핑. mysql에선 longblob으로 생성됨

@Transient

매핑하지 않을 필드에 설정한다. 임의로 값을 보관하고 싶을때 등에 사용한다.

@Access

JPA가 엔티티 데이터에 접근하는 방식을 지정한다.
필드 접근: AccessType.FIELD로 지정한다. 필드에 직접 접근한다. 접근 권한이 private이어도 접근할 수 있다.
프로퍼티 접근: AccessType.PROPERTY로 지정한다. Getter를 사용한다.
설정하지 않으면 @id의 위치를 기준으로 접근 방식이 설정된다.

1
2
3
4
5
6
7
8
9
@Entity
@Access(AccessType.FIELD)
public class Member{
@Id
private String id;

private String data1;
private String data2;
}

@id가 필드에 있으므로 @access(AccessType.FIELD)로 설정한 것과 같다. @access 생략가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Access(AccessType.PROPERTY)
public class Member{
private String id;

private String data1;
private String data2;

@Id
public String getId(){
return id;
}

@Column
public String getData1(){
return data1;
}

public String getData2(){
return data2;
}
}

@id가 프로퍼티에 있으므로, @access 생략가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
public class Member{
@Id
private String id;

@Transient
private String firstName;

@Transient
private String lastName;

private String fullName;

@Access(AccessType.PROPERTY)
public String getFullName(){
return firstName + lastName;
}

@id가 필드에 있으므로 기본은 필드 접근 방식 사용, @getFullName()만 프로퍼티 접근방식을 사용한다.
결과적으로 회원 엔티티를 저장하면 회원 테이블의 FULLNAME 컬럼에 firstName + lastName 결과가 저장된다.

@Access를 사용하는 이유
https://stackoverflow.com/questions/13874528/what-is-the-purpose-of-accesstype-field-accesstype-property-and-access

Read more »

PUT, PATCH의 차이

Posted on 2018-11-18 | Edited on 2020-11-02 | In rest | Comments:

정의

https://stackoverflow.com/questions/28459418/rest-api-put-vs-patch-with-real-life-examples

PUT은 전체 엔티티를 전달해줘야하고, PATCH는 변경하고자 하는 속성만 전달해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
// PUT
{
"name" : "joont",
"age" : 27,
"sex" : "male",
// send all data
}

// PATCH
{
"name" : "joont92" // send only data you want to change
}

PUT에 전달한 엔티티에 일부 속성이 누락될 경우 해당 속성은 API 호출 후 값이 유실된다.(매우 중요)
PUT은 대체한다는 개념으로 보면 된다. 없으면 생성도 된다.

사례

1
2
3
API : item/options
content : option list
행위 : 기존 item에 있는 option들을 전부 지우고, 전달받은 option들로 전부 다시 인서트함

위처럼 자식의 내용을 다 지우고 다시 인서트 하는 형태의 API의 경우 PUT이 좀 더 바람직하다.
교체의 개념과 딱 맞기 떄문이다.

근데 여기서 전달받은 options의 element에 식별자가 있으면 update 한다고 할 경우, 이건 PUT이 맞을까?

Read more »

swagger 2.0 문법

Posted on 2018-11-18 | Edited on 2020-11-02 | In openAPI | Comments:

문법

기본 구조

https://swagger.io/docs/specification/2-0/basic-structure/

paths에 api url,
definitions에 object를 정의해서 서로 사용 가능

data types

https://swagger.io/docs/specification/data-models/data-types/

기본적으로 string, number, integer, boolean, array, object 타입을 가지며
2.0에서 저 많은 옵션들을 다 지원하는지는 모르곘다.
array는 항상 items 옵션을 가진다.

request

https://swagger.io/docs/specification/2-0/describing-parameters/

path, query, formData type들을 받을 수 있으며
required, default, minimum 등 여러가지 옵션을 줄 수 있다.

https://swagger.io/docs/specification/2-0/describing-request-body/

body 형태의 파라미터도 받을 수 있다.
path 파라미터를 제외하고 하나만 받을 수 있다.

response

https://swagger.io/docs/specification/2-0/describing-responses/

response code, header, response data 등을 줄 수 있다.
response data로 definitions에 정의한(definitions는 그냥 이름을 뿐임) object를 줄 수 있다.

enum

https://swagger.io/docs/specification/2-0/enums/

기본적으로 enum은 string array 형태로 작성한다.
그리고 definitions 처럼 따로 선언해서 재사용하게 할 수 있다.
parameters에서는 &ENUM을 사용하지만 object 내에서는 #/definitions/ENUM형태로 선언해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CategoryType:
type: string
enum: &CATEGORYTYPE
- MAIN
- SUB

CategoryDTO:
title: CategoryDTO
type: object
properties:
externalId:
type: string
type:
enum: *CATEGORYTYPE

이런식으로 선언할 경우 code-gen에서 이상한 TypeEnum 형태의 inner enum을 generate 한다.

Read more »

[jpa] 영속성 관리

Posted on 2018-11-15 | Edited on 2020-11-02 | In jpa | Comments:

엔티티란?

간단하게 DB 테이블에 매핑되는 자바 클래스를 얘기한다.
이 클래스의 인스턴스가 결국 RDB의 레코드 하나로 매핑될 수 있다.

영속성 관리

영속성이란 간단하게 영구히 저장되는 성질을 얘기한다.
ORM 이기 떄문에 영구히 저장되는 환경은 당연히 RDB이고, 영구히 저장할 대상은 엔티티이다.
JPA에서는 이 행위를 엔티티 매니저라는 애가 수행한다.

엔티티 매니저

이름 그대로 엔티티를 관리하는 관리자이다.
엔티티와 관련된 모든 작업(삽입, 수정, 삭제 등)을 수행할 수 있다.

엔티티 매니저를 생성하는 플로우는 아래와 같다.

  1. 매타정보 입력
1
2
3
4
5
6
7
8
9
<persistence-unit name="test">
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
</properties>
</persistence-unit>

엔티티 매니저가 사용할 메타정보이다. database 접속 정보 등이 설정되어 있다.
META-INF/persistence.xml에 입력하면 자동으로 스캐닝한다.

  1. 엔티티 매니저 팩토리 등록
    엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성할 수 있다.
1
EntityManagerFactory emf = Persistence.createEntityMangerFactory("test");

앞서 등록한 메타정보를 통해 엔티티 매니저 팩토리를 생성한다.
보다시피 팩토리이므로 어플리케이션 실행 시 한번만 생성해서 공유하도록 하면 된다.(여러 스레드가 접근해도 안전함)
이 시점에 connection pool을 init 한다.

J2SE에서는 엔티티 매니저 팩토리 생성 시 커넥션 풀을 생성하고, J2EE의 경우 컨테이너가 제공하는 데이터 소스를 사용한다.

  1. 엔티티 매니저 생성
    등록한 엔티티 매니저 팩토리에서 생성하면 된다.
1
2
3
4
5
EntityManager em = emf.creteEntityManger();

em.persist(entity); // 등록
em.find(entity); // 조회
em.remove(entity); // 삭제

보다시피 팩토리에서 매번 생성해서 사용하는 구조이며, 생성 시 마다 connection을 하나 준다고 생각하면 된다.
(엔티티 매니저를 생성했다고 바로 커넥션을 얻는 것은 아니고, 정말 필요할 시점에 커넥션을 획득한다)
여기서 생성된 엔티티 매니저는 데이터베이스에 대한 직접적인 하나의 커넥션이므로, 쓰레드간에 절대 공유해서는 안된다.

개발자 입장에서는 엔티티 매니저는 엔티티를 저장하는 가상의 데이터베이스라고 생각하면 된다.

영속성 컨텍스트

용어를 정의하면 엔티티를 영구 저장하는 환경이다.
엔티티 매니저는 작업을 수행할 때 RDB에 바로 접근하지 않고, 이 영속성 컨텍스트를 통해 작업을 수행한다.
즉 어플리케이션과 RDB 사이에 하나 더 있는 영역인데, 이 영역을 통해 얻는 이점은 아래와 같다.(뭐든 중간에 하나 두면 성능 최적화를 할 요소가 많아진다)

  • 1차 캐시
    • 1차 캐시의 키는 식별자 값이다.
    • em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 만약 없으면 데이터베이스에서 조회한다.
    • 데이터베이스에서 조회후 1차 캐시에 저장한 후에 영속상태의 엔티티를 반환한다.
    • 이후 한 트랜젝션안에서는 엔티티 인스턴스는 1차 캐시에 있으므로 이 엔티티를 조회시 메모리에 있는 1차 캐시에서 바로 불러오므로 성능상의 이점을 누릴 수 있음
  • 동일성 보장
    • 동일성과 동등성 ==, equal
    • 영속성 컨텍스트에서 관리하는 엔티티 인스턴스는 동일성을 보장한다.
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

엔티티 매니저가 생성될 때 하나 생성된다.
보다시피 1차 캐시 + 부가적인 기능들을 묶어서 영속성 컨텍스트라고 부른다. 레이어로 딱 나뉘어져 있는 것은 아니다.

궁금해서 em.persist의 소스를 조금 따라가 보다보니… 1차 캐시에 대한 내용을 약간 확인할 수 있었다.

1
2
3
4
5
6
7
8
private Map<EntityKey, Object> entitiesByKey; // 1차 캐시(!)  

// ...
@Override
public void addEntity(EntityKey key, Object entity) {
entitiesByKey.put( key, entity ); // 여기!
getBatchFetchQueue().removeBatchLoadableEntityKey( key );
}

보다시피 SaveOrUpdateEventListener 에서 엔티티들을 Map에 저장하고 있다.
key, object의 형태로 저장함을 볼 수 있다. (key는 hashCode와 persister(?) 등으로 조합된 클래스이다)
엔티티 키를 만드는 행위를 간단히 보면 아래와 같다.

1
final EntityKey key = source.generateEntityKey( event.getRequestedId(), persister );

(엔티티의 식별자값(primary key)을 통해 만들고 있다. 엔티티 매니저에 의해 관리되러면 식별자값은 필수이다!!)
모든 행위들에 대해 이런식으로 저장하고, 트랜잭션이 끝나는 시점에 이런 정보들을 종합하여 최종 SQL을 날린다고 보면 된다.(이 행위를 flush라고 한다)

의문. PK가 아닌 다른 조건으로 조회했을때는 어떻게 되는건가?
영속성 컨텍스트는 1차 캐시의 역할을 하므로, 이미 조회해온 엔티티에 대해서는 추가 조회를 하지 않고 1차 캐시에 있는 엔티티를 돌려준다.
근데 만약… 조회의 조건을 바꿔서 검색했을때는 어떻게 되는걸까?
예를 들어 findByUserNameContaining으로 조회하면 해당 엔티티들이 전부 1차 캐시에 저장될 것이다.
이후에 다른 조건, 예를 들면 findByUserNickNameContaining으로 조회했을 경우 분명 첫번째 조회 결과와 두번째 조회 결과는 겹치는 부분이 있을 것이다.
이 부분에 대해서 그냥 재조회를 하는건지, 아니면 겹치는 부분은 1차 캐시의 엔티티를 반환해주는지 궁금하다.
후자의 경우가 더 비효율적일것 같은데…(리스트끼리 서로 돌면서 여부를 체크해야하기 때문에)

엔티티 생명주기(상태)

위에서 언급한 영속성 컨텍스트에 저장하는 행위에도 결국 일종의 룰이 필요할 것이다.
여기서 등장하는 것이 엔티티 상태 이다.
엔티티 매니저는 엔티티들의 상태를 통해 여러가지 작업들을 수행한다.
이를 엔티티 생명주기라고 한다.

비영속

영속성 컨텍스트와 전혀 관계없는 상태이다.
그냥 엔티티를 생성하면 비영속 상태이다.

1
Member member = new Member(); // 비영속 상태!

영속

엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한 상태를 말한다.
(간단하게 말하면 위의 Map에 저장된 상태)
영속상태로 전환하는 법은 간단하다.
em.persist나 em.find를 통해 엔티티를 저장하거나 조회하기만 하면 영속 상태가 된다.
영속성 컨텍스트에 저장되고, 엔티티 매니저에 의해 관리된다는 뜻!

준영속

조금 특별한 상태이다.
영속상태였다가 비영속상태로 변환된 엔티티를 준영속 상태라고 한다.
em.detach 메서드를 통해 전환할 수 있으며, 전환 시 1차 캐시, 쓰기지연 저장소에 저장된 정보들이 모두 삭제된다.
결과적으로 영속상태가 아니개 되는 것이므로 영속성 컨텍스트에서 제공하는 모든 기능을 사용할 수 없다.

1
2
3
4
5
em.persist(member); // 영속 상태

member = em.detach(member); // 준영속 상태

member.setName("changed name"); // update 발생하지 않음

em.detach외에도
em.clear를 통해 영속성 컨텍스트 내의 모든 엔티티를 지워버림으로써 준영속 상태로 만들 수 있고,
em.close를 통해 영속성 컨텍스트를 종료해버림으로써 준영속 상태로 만들 수 있다.

비영속 상태와 별 다를것 없지만 하나 확실한 것은, 실존하는 데이터라는게 증명이 된다는 것이다.
(영속성 컨텍스트에 들어갔었으면 등록되거나, 조회되어진 데이터이므로)

실제로 개발자가 준영속 상태를 활용할 경우는 거의 없다.

행위

조회

em.find를 통해 엔티티를 조회해올 수 있다.
바로 데이터베이스에서 조회해오는 것은 아니고, 영속성 컨텍스트를 거쳐서 조회한다.
처음 em.find를 통해 오브젝트를 찾으면 먼저 영속성 컨텍스트에 해당 오브젝트(key로 조회)가 있는지 찾고
있으면 db로 가지 않고 그 오브젝트를 바로 리턴하고, 없으면 db에서 조회해온 뒤 영속성 컨텍스트에 저장하고 그 오브젝트를 리턴한다.
어플리케이션 레벨에서 캐싱이 가능하단 뜻이다!

1
2
3
4
Member member1 = em.find(Member.class, "joont92");
Member member2 = em.find(Member.class, "joont92");

assertSame(member1, member2); // success

하이버네이트와 같은 ORM 프레임워크를 사용하지 않았다면
동일한 레코드임에도 불구하고 쿼리를 두번 날리는 결과가 발생했었을 것이다.

등록

em.persist를 통해 엔티티를 데이터베이스에 등록할 수 있다.
(정확히 얘기하면 persist는 해당 엔티티를 영속성 컨텍스트에 등록하라는 의미이다. 뒤에 나올 @GeneratedValue의 특징 떄문에 persist == 저장 이라고 착각할 수 있는데, 이는 틀렸다)
해당 메서드를 실행함과 동시에 데이터베이스에 바로 저장하는 것은 아니고, 먼저 영속성 컨텍스트에 저장한다.
근데 여기서 단순히 영속성 컨텍스트에 저장하는 작업만을 하는것은 아니고,
쓰기지연 SQL 저장소라는 곳에 insert 쿼리를 등록하는 작업까지 동시에 진행한다.
그리고 마지막에 flush가 일어나면 여기에 저장된 SQL을 데이터베이스로 발사!하는 것이다.
이런식으로 쿼리를 바로 날리지 않고 쓰기지연을 수행하는 이유는, 네트워크 통신 횟수를 줄여 이득을 취하기 위함이다.

추후에 나오겠지만 auto_generate key를 사용하는 데이터베이스는 이 flow대로 진행되지 않는다.
key 값을 얻어오기 위해 persist와 동시에 insert 쿼리를 실행해버린다.

어떻게 쓰기 지연이 가능할까?

  1. 데이터베이스에 트랜잭션이라는 개념이 있기 때문이다.
    데이터베이스에 DML을 아무리 날려도 commit을 하지 않으면 적용되지 않는다는 특징을 이용하여 쓰기 지연을 가능하게 할 수 있다.
  2. 데이터베이스에 직접 날리지 않고 쿼리를 메모리에 저장해두는 방식으로 가능하다.

수정

수정은 딱히 메서드가 존재하지 않는다.
이는 JPA에 변경감지라는 특징이 있기 때문이다.
엔티티가 처음으로 영속상태에 들어갈 경우, Map에 저장만 하는 것이 아니라 초기 엔티티의 스냅샷이라는 것을 찍어둔다.
그리고 마지막 flush가 일어날 때 영속성 컨텍스트에 저장된 엔티티의 속성 값들과 엔티티 스냅샷의 속성 값들을 비교한다.

메서드 실행 시점에 쿼리를 쌓는 다른 메서드들과는 달리, 엔티티 매니저가 flush될 때 쿼리를 생성한다. 즉, 시점이 다르다.
deep 탐색까진 하지 않는다(list의 member들까지 탐색하지는 않음)

그리고 변경이 일어났을 경우 update 메서드를 생성하여 이를 데이터베이스에 발사한다.(변경 감지)
(이러한 로직이므로 따로 dirty check가 필요없다)

생성되는 update문의 형태
실제로 update를 발생시켜보면 알겠지만, 업데이트가 발생하는 특정 속성에 대해서만 업데이트 하는 것이 아니라
전체 오브젝트에 대해 업데이트를 실행하는 쿼리가 생성된다.
이렇게 하면 매번 사용하는 수정 쿼리가 같다는 점을 이용한 것이고, 속성 하나하나에 대해 쿼리를 다 생성해놓지 않아도 된다는 장점이 있다(진짜 장점인가)
JPA가 로딩 시점에 업데이트 쿼리를 미리 생성해둔다.

하지만 필드의 내용이 너무 많을 경우 매번 이런식으로 풀 업데이트 쿼리를 날리는 것은 비효율적이다.
기본적으로 모든 컬럼을 다 보내는것 자체가 데이터 전송량 낭비이기 때문이다.
이때는 아래와 같이 @DynamicUpdate 어노테이션을 사용하면 수정된 데이터에 대해서만 update를 실행하는 쿼리를 생성한다.

1
2
3
4
5
6
@Entity
@Table(name = "MEMBER")
@org.hibernate.annotations.DynamicUpdate
class Member{

}

상황에 따라 다르지만 필드가 30개가 넘어가면 위와 같이 @DynamicUpdate를 쓰는 것이 좋다고 한다.
그리고 그 이전에, 30개가 넘어가는 테이블이면 정규화가 제대로 되지 않는다는 고민이 선행되어야 할 것이다.

삭제

엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.

1
2
Member member = em.find(Member.class, "joont92");
em.remove(member);

em.detach + delete 쿼리라고 보면 된다.
메서드를 실행하면 해당 엔티티는 영속성 컨텍스트에서 detach 된다(그리고 delete 쿼리를 쓰기지연 SQL 저장소에 저장?)

병합

준영속 상태인 엔티티를 다시 영속상태로 만드는 행위를 말한다.
em.merge 메서드를 사용한다.

1
Member foundMember = em.merge(member);

member는 영속성 상태에서 제거된 준영속 상태이므로 식별자를 가지고 있다.
그러므로 위 메서드 실행 시, 해당 식별자로 조회쿼리가 날라가서 해당 엔티티가 영속화될 수 있는지(실제 존재하는지) 체크하게 된다.
있다면 영속성 컨텍스트에 저장하고, 새로운 엔티티를 리턴한다.
즉, foundMember와 member는 같지 않다.

기존 JPA 명세에 따르면, 전달받은 식별자로 해당 엔티티를 찾을 수 없을 경우(식별자가 전달되지 않은 경우도 마찬가지) IllegalArgumentException이 발생하게 된다.
하지만 hibernate 에서는 이럴 경우 그냥 새로 저장해주고, 영속성 컨텍스트에 등록하여 리턴해준다.(@GeneratedValue일 경우 식별자 generate, 아닐 경우 전달받은 식별자로 등록한다)

flush

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 행위이다.(커밋되는 것은 아니다)
쓰기지연 SQL에 저장된 SQL들을 발사!하고,
위에서 언급했듯이 영속성 컨텍스트에 들어있는 오브젝트와 스냅샷을 비교하여 업데이트 쿼리를 생성한 뒤, 업데이트 쿼리를 생성하여 발사!한다.

flush를 발생시키는 방법은 3가지 정도가 있다.

  1. em.flush() 메서드를 직접 호출

거의 사용할 일이 없다

  1. 트랜잭션 커밋 시 플러시 자동 호출

flush 하지 않고 commit 할 경우, SQL이 하나도 실행되지 않은 상태이기 떄문에 아무런 일도 일어나지 않는다.
JPA에서는 이런 상황을 방지하기 위해 commit시 flush를 자동으로 호출한다.

  1. JPQL 실행 시 플러시 자동 호출

JPQL은 호출시에 SQL로 변환되어 데이터베이스에서 조회해오는데, 이럴려면 레코드들이 이미 데이터베이스에 저장되어 있어야 한다.
persist와 JQPL 호출 작업을 한 트랜잭션 내에서 하는 행위를 방지하기 위해 위와 같이 처리한 듯 하다.

플러시 모드를 변경하려면 javax.persistence.FlushModeType을 사용하면 된다.

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시(default)
  • FlushModeType.COMMIT : 커밋할 때만 플러시

flush를 한다고 해서 영속성 컨텍스트에서 엔티티가 지워지는 것은 아니다!!
(이걸 신경쓸 일이 있곘느냐만…)

27살, 2번째 이직

Posted on 2018-11-04 | Edited on 2020-11-02 | In life | Comments:

2018년 6월 말. 다니고 있던 2번째 회사를 퇴사하게 되었습니다.

이번 이직 과정에서 인터넷에 공유된 여러 글들을 통해 많은 도움을 얻었고,
블로그에 이직 관련 글 올리신 분들이 나름 멋있어보여서(…)
이직 성공하면 글을 올려야겠다고 생각했었습니다 ㅎㅎ

그리고 성공적으로 이직을 하였고… 이제 출근한지 4주 정도 되었네요.

너무 간만에 일을 해서(4개월이나 쉬었어요 ㅠㅠ) 정신을 못 차리다가… 이제서야 글을 올립니다!

이번 이직 과정에서 개인적으로 매우 성장했다고 생각되어서, 미흡하지만 제 경험을 공유해보려고 합니다.
보시는 분들에게도 혹시나 좋은 영향을 줄 수 있으면 좋겠습니다. 😊

2018년 4월

이때가 퇴사를 결심하게 된 시기였습니다.
퇴사를 결심하고 미국행 비행기표를 끊어버린 시점이기도 하지요 ㅋ.ㅋ

부끄럽지만 아마도 처음으로 회사에 대해 진지하게 생각해본 때였던 것 같습니다.
이전까지는 별 생각없이 회사에서 붙여주고 조건만 나쁘지 않으면 바로 갔었거든요…
그래서 이때 회사가 2번째 회사였는데, 개발자로 면접을 본 경험은 딱 2번이었습니다.
진지하지도 못하고, 시야도 좁았던거죠.

하지만 시간이 지나고, 여러가지 경험을 함으로써 저에게도 나름의 가치관이 정립되기 시작했고…
원하는 기업의 형태도 생기게 되더라고요.

그래서 많은 고민을 했었고, 결국에는 그만두자는 결론을 내리게 되었습니다.
(사실은 3년 쯤 일하니까 1달 정도 쉬고 싶었던 것도 큽니다)

결론을 내림과 동시에 미국행 비행기도 끊어버렸습니다 ㅋㅋㅋ
처음으로 쉬어보는거 였거든요(두번째 회사로 올떄는 텀이 없었음)

출국이 6월 말이었기 떄문에 그 시점 이후로 2달 정도 일을 더 하며 마무리하고,
2018년 6월 중순에 최종적으로 퇴사하게 되었습니다.

원하는 회사

저 시점쯤에 생긴 원하는 회사의 조건은 아래와 같았습니다.

  1. 개발자가 많았으면 좋겠다

    항상 소규모 개발팀에만 있어서 이런 환상이 좀 존재했었습니다.
    개발자가 몇십명 있으면 진짜 어벤져스 느낌도 날 거같고…
    그렇게 같이 팀워크하면서 프로젝트 진행하고… 하면 재밌을 거 같단 생각이 들었습니다.
    (개인적으로 이전 회사에서 혼자서 프로젝트 하는것이 좀 힘들었습니다 ㅠ.ㅠ)

  2. 코드리뷰가 있으면 좋겠다

    코드리뷰 문화가 좋은것만 알고… 한번도 겪어보지를 못해서 꼭 이런 문화가 있는 기업이었으면 했습니다.

  3. 기술 스택이 높았으면 좋겠다

    무조건적으로 신기술을 사용했으면 좋겠다는 아니지만,
    좋은 것들은 다 썼으면 좋겠다고 생각했습니다!
    (Java8도 쓰고, JPA도 쓰고, AWS도 쓰고, MSA도 하고…!)

  4. IT가 주 수익모델인 IT 기업이면 좋겠다

    이래야만 개발 조직에 역량이 집중 될 거라고 생각합니다.

  5. 월급을 잘 주면…(많이 주면?!) 좋겠당…

    돈 많이 받고 싶은건 누구나 그러니까요… ㅎㅎㅎ
    (월급 밀리는 경험도 당해봐서 ㅠㅠ)

나의 현위치?

스펙은 그냥… 별 거 없습니다…

대학은 뭐 가자마자 거의 바로 관두고… 학은제로 학사 땄습니다
개발은 부산에서 국비지원 교육을 받으면서 처음 배우고, 수료하고 서울에 취업했습니다.

회사는 총 2개를 다녔고, 총 경력은 3년 정도였습니다.

  • 공공기관 SI/SM(1년 반 정도)

    사실… 여기는 경력이라고 하긴 좀 그렇습니다… 개발을 안했거든요…ㅋㅋ;;
    html이나 좀 고치고, cs 업무 같은거 했던거 같네요… 정말 최악 ㅠㅠ
    1년쯤 넘었을때 개발 스터디를 하나 시작하게 되었고, 거기서 다른 개발자분들을 만났고, 현실을 직시하고 바로 퇴사했습니다


  • 맛집 추천 서비스(여기도 1년 반 정도)

    여기서 많은걸 배우고, 많은 경험을 했습니다.
    사실상 제 경력은 여기 다 있다고 보는게 낫겠네요 ㅎㅎ
    백엔드, 프론트엔드를 같이 했었습니다.

미국 갔다와서 정리한번 하고,
취업준비 하려는데 경력도 별거 없고… 실력도 미흡해서 엄청 암울했었던 기억이 나네요 ㅠㅠ
게다가 백엔드 프론트엔드 같이 하다보니 뭔가 잡캐가 된 느낌이었고…

그래도 이직은 해야겠지요! 이미 회사가 없으니까요!!!
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

이력서를 써보자!

리프레쉬 할만큼 하고, 서울 돌아와서 어느정도 정리한 뒤 8월 말쯤부터 이력서를 쓰기 시작했습니다.

막상 쓰려니까 너무 막막하고… 왜 미리미리 안써놨나 생각이 들었습니다. ㅠㅠ
그래서 여기저기 관련 글들을 찾아 읽어보았고,
주니어 개발자를 위한 취업 정보에 올라와 있는 Outsider님, 구인본님, 이한별님의 이력서를 많이 참고하여 아주 마음에 드는 이력서를 작성할 수 있었습니다(다들 너무 감사드립니다 ㅠㅠ)

이력서 작성은 mac pages로 하였습니다.

첫 장에서 바로 제 연락처와 기술스택, 경력을 나열했습니다.
경력직 이직이라 기술스택에 관심이 많을거라 생각했거든요 ㅎㅎ
그리고… 개인적으로 스킬에 레벨을 쓰지는 않았습니다.
매우 주관적인 부분이기도 하고, 그런거 쓰지 말라는 글도 봐서…

그리고 두번째 장에 제 프로젝트에 대해 나름 상세하게 나열했습니다.
프로젝트명, 사용한 기술, 설명등을 작성했습니다.

세번째 장에는 기본적인 제 학력, 교육 수료 내용, 자격증 등을 작성했습니다.
그리고 추가적으로 외부활동 이라고 만들어서 제가 스터디하는 내용을 나열했는데, 이걸 마지막 페이지보다 더 위로 올려야 되나 고민했었습니다.
하지만 도저히 넣을 공간이 안나오더라고요 ㅎㅎ 그래서 저기 뒀습니다.

작성하고나니 정말 만족스러웠습니다.
딱딱한 틀에 갖춰진 이력서도 아닌 자유양식의 이력서를 처음 가져보기도 했고,
막상 써놓고 보니 “아 내가 생각보단 쓸모있는 사람이구나” 라는 생각도 들더라고요 ㅋㅋㅋㅋ

지원하자!

이력서를 최종 작성했으니 이제 지원해야곘지요 ㅎㅎ

대부분의 이력서는 원티드를 통해 지원했고요(좋은 기업도 많고… 돈도 주니까!)
잡플래닛도 간간히 이용했습니다.
잡플래닛에서 공고를 보고 해당 기업의 채용공고 사이트를 들어가 지원했었죠.

그리고 잡코리아는 인재등록만 해두고, 따로 지원하지는 않았습니다.

처음엔 인재등록 후 모든 기업에게 공개되도록 설정해놨었는데,
전화랑 메일이 너무 많이와서 컨트롤이 불가능하더라고요… 게다가 이상한 곳도 너무 많았습니다.
그래서 한 3일 정도 지켜보다가, 헤드헌터만 검색할 수 있도록 설정을 바꿔버렸습니다.
개인적으로 잡코리아에 인재등록 하실꺼면 헤드헌터만 볼 수 있도록 해놓는걸 추천합니다 ㅠㅠ

결과는?!

놀랍게도 아주 많은 곳에서 서류를 통과시켜 주셨습니다.

일단 방금도 잠시 언급했지만 잡코리아…
잡코리아 헤드헌터 님들한테 연락이 엄청 많이 왔습니다. 최소 매일 1통씩은 전화나 메일을 받았던 것 같네요.
좀 놀라웠고, "아 경력이 3년정도면 꽤나 귀해지는구나…"라는 생각도 들었습니다.

헤드헌터님들에게 받은 JD중 마음에 드는 기업들이 있으면 이력서를 드렸고, 대부분 이력서를 내자마자 바로 면접이 잡혔습니다.
그래서 거의 이력서 제출과 동시에 면접을 보러 다니기 시작했습니다…

그렇게 1주일 정도 지났을 쯤, 원티드에 지원한 것들도 슬슬 서류통과/서류탈락 이 결정되기 시작했습니다.
그리고 2주차부터는 면접이 너무 많아 엄청 바빴습니다…
(작으면 하루에 1개 정도 봤고, 날짜가 몰려서 3개까지 면접을 봤던 적도 있습니다 ㄷㄷ)

나의 면접 전략?!

전략이라니까 뭔가 거창하긴 한데, 제 입장에서는 많은 도움이 되었으므로 일단 써보겠습니다.

이번 면접에 대한 저의 전략은, 간단하지만 “면접을 분석하고, 나를 개선시키자” 였습니다.

그래서 저는 제가 보는 모든 면접을 녹음하고, 집에와서 다시 듣고, 그것을 정리했습니다.

일단 면접을 녹음하고(이게 범법 행위인가…) 집에와서 다시 들어보면,
생각보다 얻는 효과가 매우 큽니다.

일단 내가 붙을지 떨어질지 대략적으로 예측이 가능합니다.
딱 들어보면 “아 여긴 붙겠네”, "아 여긴 망했네"가 느껴지더라고요 제 입장에선.ㅋㅋ

그리고 중요한건, 이것을 하나하나 다 정리하는 행위입니다.
면접의 질문과 내가 한 답변을 정리하면
면접에서 주로 나오는 질문의 패턴들도 파악할 수 있게되고,
내가 어느 부분에서 부족한 답을 하고 있는지도 파악이 가능하게 됩니다.

그렇다면 다시 그 질문을 보면서
기술적인 질문이면 구글링을 통해 찾아보고, 철학적이거나 개인의 가치관적인 질문이면 이것에 대해 다시 생각해볼 수 있겠죠.
(개인적으로는 “왜 이런 질문을 한걸까?” 라는 생각을 해보면 더 좋았습니다)
이러면 다음 면접에서 이 질문에 좀 더 잘 대답할 수 있게됩니다.
당연한 소리지만, 면접이 개선이 되는것입니다! 그것도 아주 눈에 띄는 방향으로요.

그리고 여기서 한걸음 더 나아가면
이 모든 일련의 과정들을 진행함으로써
대부분의 기업들이 찾고있는 인재상이 뭔지도 알 수 있게 되고,
내가 어떤 사람인지도 알 수 있게 되고,
앞으로 내가 어떤 방향으로 나아가야 되는지도 알 게 됩니다.
개인적으로 저는 이 과정이 저를 엄청나게 성장시켜 주었다고 생각합니다.



예시를 한번 들어볼까요.

제가 면접에서 받았던 질문들을 예로 들어보면
알고리즘을 풀어보세요, A 아세요? B 아세요? 라는 단순 시험 형태의 질문보다는
나의 과거에 대해 묻는 질문이 더 많았습니다.(물론 기술적인 질문도 많았습니다…)

일단 제 과거를 수집하는 질문들로 부터 시작해서,

“저번 프로젝트에서 썼던 기술은 뭔가?”
“A라는 기술을 썼던데, 어떤식으로 활용했나?”
“저번 프로젝트에서 힘들었던 점은 뭔가?”

“왜” 라는 질문으로 이어지기 시작합니다.

“그 기술을 왜 쓴건가?”
“그 기술을 통해 얻는 장점이 뭔가?”

그리고 얼마나 주도적이었는가도 물어봅니다.

“그러한 문제를 해결해보려고 너는 어디까지 해보았는가?”
“구체적인 사례를 말해주겠나?”

물론 위 뿐 아니라 여러가지 유형들의 질문이 있었습니다. 하지만 대체적으로 저런 질문들이 핵심이었던것 같네요.

그리고 이런류의 질문을 집에와서 정리하고, 내 과거에 추가적으로 질문을 던지다보면,
앞으로 내가 어떤 사람이 되어야 하겠구나, 앞으로 어떻게 일을 해야겠구나 라는 것이 굉장히 명확해집니다.

이러한 기쁨!!과
최종 결과에 대한 초조함을 이끌고 약 한달정도 면접을 진행했던 것 같네요. ㅎㅎ

그래서 결과는?

저는 총 15군데 정도 지원했고, 12군데 정도에 면접을 보았으며, 5군데 정도에 최종 합격을 하였습니다.

제 생각보다 훨씬 큰 결과였고, 그중에 저를 정말 마음에 들어하셨던 곳도 있었습니다…
그리고 그 중 제가 가장 가고싶었던 기업에 최종 입사를 하게 되었고,
현재 입사한지 1달이 다 되어 가는 상황이네요.

개인적으로 제가 원하던 요구사항도 다 실현이 되었고…
옆에 계신 분들도 다 너무 뛰어나셔서 즐거운 마음으로 회사를 다니고 있습니다 ㅎㅎ
(기술 스택도 높고, 출퇴근도 자유고, 밥값도 주고, 운동도 공짜로 하게 해주고…!!!)

벌써 3번째 회사이긴 하지만, 개인적으로 이직을 잘 한것 같아 매우 만족합니다 😊 😊

음… 마무리…

네… 음…
마무리 하겠습니다.

  • 어떻게 쓰다보니 요우님의 포스트 포멧을 많이 따라한 것 같네요 ㅋㅋㅋㅋ (갠적으로 정말 잘 읽었습니다)
  • 이직은 정말 중요한 행위이다.
  • 이직을 준비하는 과정 자체가 개인을 굉장히 성장시킨다
  • 그래서 나는 성장했다!!
  • 이제 앞으로 더 성장할테다!!
  • 뭐 덜 쓴 부분이 있나…? 급히 써서…
  • 하지만 이 포스트가 누군가에게 아주 조금이라도 도움이 되었으면 좋곘습니다.
  • 끝!
Read more »

[db] transaction

Posted on 2018-07-13 | Edited on 2020-11-02 | In db | Comments:

트랜잭션이란

작업을 논리적 단위로 묶어서 원자성(atomic)을 보장하고자 할 때 사용하는 것이다.

원자성 : 논리적 작업 셋에 1개의 쿼리가 있든 2개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 적용되지 않는것을 보장해주는 것

MySQL에서는 InnoDB만 지원하고, 나머지(MyISAM 등)은 트랜잭션을 지원하지 않는다.

아래와 같은 쿼리가 있다(3번째 멤버가 중복된 이름이라 에러가 발생할 것이다)

1
INSERT INTO MEMBER VALUES(1,'joont'),(2,'junyoung'),(3,'duplicated name');
  • MyISAM

    쿼리 실행시에 오류가 발생하지만 3번 멤버만 저장되지 않을 뿐, 1,2번 멤버는 롤백되지 않고 저장된 상태 그대로 있다.
    이러면 나중에 데이터 정합성을 맞추기가 매우 힘들어진다.

  • InnoDB

    1,2,3번 모두 저장되지 않는다.

위처럼 꼭 여러개의 작업 단위를 묶지 않아도 된다.
트랜잭션이 성공적으로 수행되면 모두 commit되고, 실패하면 모두 rollback 한다는 사실이 중요한 것이다.

설정법

1
2
3
4
5
START TRANSACTION; -- 또는 BEGIN;

-- do somthing

COMMIT; -- 또는 ROLLBACK;

위처럼 트랜잭션을 시작하고, 커밋이나 롤백하는 시점까지가 하나의 트랜잭션이다.

AutoCommit이 설정되어 있으면 쿼리문장 하나하나가 곧 트랜잭션이고,
AutoCommit이 설정되어 있지 않으면 COMMIT, ROLLBACK, DDL 이후 시작부터 다시 이 문장을 실행 전까지가 자동적으로 트랜잭션 하나로 잡힌다.

나는 AutoCommit을 off로 설정하면 트랜잭션 테스트가 잘 안되서 AutoCommit을 on으로 설정한 뒤 테스트했다(on은 항상 트랜잭션이 끝남을 보장해주니까)

트랜잭션 설정 주의점

트랜잭션의 단위는 특별한 이유가 없으면 최소한의 단위로 해주는 것이 좋다.
가령 아래와 같은 트랜잭션 설정은 위험하다.

1
2
3
4
5
6
7
8
9
10
11
12
1. 처리 시작  
==> DB 커넥션 생성
==> START TRANSACTION;
2. 인가된 사용자인지 확인
3. 사용자가 작성한 컨텐츠 Validation
4. 첨부파일 서버에 업로드
5. 게시글과 첨부파일 내용을 DB에 저장
6. 게시물 등록에 대한 메일 발송
7. 메일 발송 내역을 DB에 저장
==> COMMIT;
==> DB 커텍션 반납
8. 처리 완료

보다시피 트랜잭션의 범위가 너무 크다.
DB에 데이터를 저장하는 구간은 5번과 7번밖에 없는데 인증, Validation, 첨부파일 업로드, 메일 전송과 같은 행위까지 전부 트랜잭션에 같이 포함되어 있다.
즉, 위의 모든 작업들이 끝나기 까지 해당 트랜잭션을 잡고있는 커넥션은 반환되지 않을 것이며,
소유하는 시간이 길어질수록 다른 쓰레드들이 커넥션을 가져가지 못하고 대기하는 상황이 벌어질 것이다.
(특히 첨부파일 업로드, 메일 전송과 같은 네트워크 작업을 트랜잭션으로 묶는것은 아주 위험하다. 네트워크 통신이 불가능하다면 트랜잭션 소유시간이 매우 길어지게 된다)

커넥션 뿐만 아니라 CUD가 발생했을 경우 해당 레코드에 대해서 메서드가 끝날때까지 락이 걸리기 때문에 동시처리 성능 또한 매우 떨어지게 된다.
아래와 같이 변경해주는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 처리 시작  
==> DB 커넥션 생성
2. 인가된 사용자인지 확인
3. 사용자가 작성한 컨텐츠 Validation
4. 첨부파일 서버에 업로드
==> START TRANSACTION;
5. 게시글과 첨부파일 내용을 DB에 저장
==> COMMIT;
6. 게시물 등록에 대한 메일 발송
==> START TRANSACTION;
7. 메일 발송 내역을 DB에 저장
==> COMMIT;
==> DB 커텍션 반납
8. 처리 완료

트랜잭션을 최소화하는 것이 좋다는 것을 강조하기 위해 7번을 따로 분리하였다.
상황에 따라 크게 달라질 수 있다.

Read more »
1…131415…19

JunYoung Park

182 posts
18 categories
344 tags
RSS
© 2020 JunYoung Park
Powered by Hexo v3.6.0
|
Theme – NexT.Muse v7.1.0