이 글은 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 듣고 정리한 내용입니다.
다대일에서 다
쪽이 연관관계 주인이다.
다대일 단방향
은 가장 많이 사용하는 연관관계다.외래 키가 있는 쪽을 연관관계의 주인으로 한다.
양쪽이 서로 참조되도록 개발한다.
일대다에서 일
쪽이 연관관계 주인이다.
일대다 단방향
모델은 권장하지 않는다. (실무에서 거의 쓰이지 않는다)DB 설계상 일대다 관계는 항상 다
쪽에 외래키
가 있다.
객체와 테이블의 차이 때문에 반대편 테이블의 외래 키(Member의 외래 키)를 관리하는 특이한 구조이다.
@JoinColumn
을 꼭 사용해야 한다. 그렇지 않으면 조인 테이블 방식을 사용해야 한다. (@JoinColumn을 사용하지 않으면 TEAM_MEMBER와 같은 중간에 테이블 하나가 추가된다)
Team 엔티티 클래스에 대해 java 코드로 작성하면 아래와 같다.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team") // Team은 일대다에 '일'에 속하므로 @One으로 시작한다.
@JoinColumn(name ="TEAM_ID") // **
List<Member> members = new ArrayList<Member>();
// 아래 부분 생략
}
일대다 단뱡향 매핑의 단점으로는 아래와 같다.
엔티티가 관리하는 외래 키가 다른 테이블에 있다.
연관관계 관리를 위해 추가로 UPDATE SQL을 실행해줘야 한다.
정리하면, 일대다 단방향 매핑보다는 다대일 양방향
매핑을 사용하자.
일대다 양방향
매핑은 공식적으로 존재하지 않는다.
만약 다
인 Member
테이블에서 조회하고 싶다면 아래와 같이 작성하면 된다.
읽기 전용 필드(@JoinColumn(insertable=false, updatable=false))을 사용해서 양방향 처럼 사용한다.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable=false, updatable=false) // 읽기 전용 필드를 사용
private Team team;
// 아래 부분 생략
}
다대일 양방향
매핑을 사용하자.주 테이블이나 대상 테이블 중에 외래 키
를 선택 가능하다.
둘 중 한군데에만 넣어도 상관없다.
외래 키
에 데이터베이스 유니크(UNI) 제약조건을 추가한다.
다대일(@ManyToOne) 단방향 매핑과 유사하다.
@ManyToOne
에서 @OneToOne
으로 바꾼 것 뿐이다.
Member 클래스
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
// 아래 부분 생략
}
다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인이다.
주인이 아닌 곳(반대편)은 mappedBy를 적용한다.
Locker 클래스
@Entity
public class Locker {
@Id
@GeneratedValue
private Long id;
private String name;
// 읽기 전용
@OneToOne(mappedBy = "locker") // Member 클래스에 Locker의 변수명을 의미한다.
private Member member;
// 아래 부분 생략
}
관계형 데이터베이스
는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
따라서 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어나가야 한다.
객체
는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능하다.
@ManyToMany
사용하고, @JoinTable
로 연결 테이블을 지정한다.
다대다 매핑은 단방향, 양방향 모두 가능하다.
결론부터 말하자면, 다대다 매핑는 실무에서 사용하지 않는다.
연결 테이블이 단순히 연결만 하고 끝나지 않는다.
주문시간, 수량 같은 데이터가 들어올 수 있다.
다대다 한계를 극복하기 위해 연결 테이블용 엔티티
를 추가한다. (연결 테이블을 엔티티로 승격)
@ManyToMany
-> @OneToMany
, @ManyToOne
으로 바꿔준다.
@ManyToMany
사용하지 않는다.@JoinColumn
은 외래 키를 매핑할 때 사용하는 어노테이션이다.@ManyToOne
은 다대일 관계 매핑할 때 사용하는 어노테이션이다.
다대일에서 ‘다’가 연관관계 주인이 되어야 한다.
@OneToMany
은 일대다 관계를 매핑할 때 사용하는 어노테이션이다.이 글은 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 듣고 정리한 내용입니다.
객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다 - 조영호(객체지향의 사실과 오해)
이번 글에서는 JPA에서 외래 키
로 조인을 사용하고, 객체
는 참조를 사용해서 연관관계를 매핑하는 방법에 대해서 알아보자.
예를 들어, 회원과 팀이 있고,
회원은 하나의 팀에만 소속될 수 있고,
회원(다)과 팀(일)은 다대일 관계라고 시나리오를 가정해보자.
이때, 객체를 테이블에 맞추어서 모델링을 하면, Member, Team 두개의 테이블을 통해 식별자(member.getId(), team.getId())로 조회해야 한다.
이처럼 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
따라서 객체지향 설계로 협력 관계를 만들기 위해 아래와 같이 생각해서 매핑한다.
테이블은 외래 키
로 조인을 사용해서 연관된 테이블을 찾는다.
객체는 참조
를 사용해서 연관된 객체를 찾는다.
객체 지향적인 모델링(객체 연관관계 사용)을 하면 아래 그림과 같다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
// @Column(name ="TEAM_ID")
// private Long teamId;
@ManyToOne // User: M이므로 '다'에 속하기 때문에 Many~로 시작한다.
@JoinColumn(name = "TEAM_ID") // 조인할 테이블의 식별키를 작성한다. => 외래 키
private Team team;
// 아래 Getter 생략
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne // Member: M이므로 '다'에 속하기 때문에 Many~로 시작한다.
@JoinColumn(name = "TEAM_ID") // 조인할 테이블의 식별키를 작성한다. => 외래 키
private Team team;
// 아래 부분 생략
}
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // Team은 일대다에 '일'에 속하므로 @One으로 시작한다.
List<Member> members = new ArrayList<Member>();
// 아래 부분 생략
}
(mappedBy = "team")
은 Member
엔티티에 있는 Team의 변수명인 team과 연관되었다는 걸 의미한다.객체와 테이블 간에 연관관계를 맺는 차이를 이해해보자.
객체
는 연관관계가 2개이고, 테이블
은 연관관계가 1개로 구성되어 있다.
객체) 회원 -> 팀 연관관계 1개(단방향), 팀 -> 회원 연관관계 1개(단방향)
테이블) 회원 <-> 팀의 연관관계 1개(양방향)
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
다.
따라서 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다.
이때, 둘 중 하나로 외래 키를 관리해야 한다.
양방향 매핑에는 다음과 같은 규칙이 있다.
객체의 두 관계중 하나를 연관관계의 주인
으로 지정해야 한다.
연관관계의 주인만이 외래 키
를 관리(등록, 수정)할 수 있다.
주인
은 mappedBy 속성을 사용하지 않는다.
주인이 아닌쪽
은 읽기만 가능하므로 mappedBy 속성
으로 주인을 지정해준다.
결론부터 말하면, 외래 키
의 위치를 기준으로 연관관계 주인을 정하면 된다. (연관관계 주인 => 외래 키가 있는 곳)
여기서는 Member.team
이 연관관계의 주인이다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**중요**
em.persist(member);
단방향 매핑만으로도 이미 연관관계 매핑은 완료된다.
양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐이다.
JPQL에서 역방향으로 탐색할 일이 많을 때 사용한다.
정리하면, [1] 처음에는 단방향 매핑으로 잡은 뒤, [2] 필요할 때 양방향으로 추가해준다. (단방향 -> 양방향으로 바꿀 때 테이블에 영향을 주지 않는다.)
이 글은 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 듣고 정리한 내용입니다.
@Entity
가 붙은 클래스는 JPA가 관리하며, 엔티티
라 부른다.
JPA를 사용해서 테이블과 매핑할 클래스는 @Entity
가 필수이다.
몇 가지 주의점이 있다.
기본 생성자가 필수이다. (파라미터가 없는 public 또는 protected 생성자)
final 클래스, enum 클래스, interface, inner 클래스는 사용할 수 없다.
저장할 필드에 final 사용할 수 없다.
@Entity 속성 정리
속성: name
: JPA에서 사용할 엔티티 이름을 지정한다.
기본값: 클래스 이름을 그대로 사용한다.
@Entity(name = "User")
같은 클래스 이름이 없으면 가급적 기본값을 사용한다.
@Table
은 엔티티와 매핑할 테이블을 지정한다.
속성 1) name
: 클래스 이름이 MySQL 예약어와 겹치면 매핑되는 테이블 이름은 복수형
으로 지정한다.
기본값은 엔티티 이름을 사용한다.
ex) Entity Class 이름 = User -> @Table(name = "users")
속성 2) catalog
: 데이터베이스 catalog를 매핑한다.
속성 3) schema
: 데이터베이스 schema를 매핑한다.
속성 4) uniqueConstraints
: DDL 생성 시에 유니크 제약 조건을 생성한다.
User 클래스(엔티티)
package woorifisa.goodfriends.backend.user.domain;
import woorifisa.goodfriends.backend.global.common.BaseTimeEntity;
import woorifisa.goodfriends.backend.user.exception.InvalidUserException;
import javax.persistence.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Table(name = "users")
@Entity
public class User extends BaseTimeEntity {
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-z0-9._-]+@[a-z]+[.]+[a-z]{2,3}$");
private static final int MAX_NICKNAME_LENGTH = 20;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "nickname", nullable = false)
private String nickname;
@Column(name = "profile_image_url", nullable = false)
private String profileImageUrl;
protected User() {
}
public User(String email, String nickname, String profileImageUrl) {
validateEmail(email);
validateNickname(nickname);
this.email = email;
this.nickname = nickname;
this.profileImageUrl = profileImageUrl;
}
private void validateEmail(final String email) {
Matcher matcher = EMAIL_PATTERN.matcher(email);
if (!matcher.matches()) {
throw new InvalidUserException();
}
}
private void validateNickname(final String nickname) {
if(nickname.isEmpty() || nickname.length() > MAX_NICKNAME_LENGTH) {
throw new InvalidUserException(String.format("이름은 1자 이상 %d자 이하여야 합니다.", MAX_NICKNAME_LENGTH));
}
}
// getter
}
DDL이란 데이터베이스를 정의하는 언어이며, 데이터를 생성, 수정, 삭제하는 등의 데이터 전체의 골격을 결정하는 역할을 하는 언어이다.
테이블 관련 명령어 : create(생성), alter(수정), drop(삭제), truncate(초기화)
DDL을 애플리케이션 실행 시점에 자동으로 생성해준다. (테이블 -> 객체 중심)
데이터베이스 방언
(DB Dialect) 을 활용해서 데이터베이스에 적절한 DDL을 생성한다.
Hibernate의 경우엔 persistence.xml
에서 hibernate.dialect 설정 값을 변경하면 된다.
예를 들어, H2 Dialect 설정 코드는 <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
이다.
```markdown
SQL은 다음과 같이 표준 SQL인 ANSI SQL이 있으며, 이외에 각 DBMS Vendor(벤더, 공급업체)인 MS-SQL, Oracle, MySQL, PostgreSQL 에서 자신만의 기능을 추가한 SQL이 있다.
이 글은 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 듣고 정리한 내용입니다.
웹 애플리케이션을 개발하면서 고객으로부터 요청이 오면, 엔티티 매니저 팩토리를 통해 엔티티 매니저를 생성한다.
엔티티 매니저는 데이터베이스 커넥션 풀을 사용해서 DB를 사용하게 된다.
영속성 컨텍스트
란 엔티티를 영구 저장하는 환경이라는 뜻을 가진 논리적인 개념으로 어플리케이션과 DB 사이에서 객체를 보관하는 가상의 DB같은 역할을 한다.
즉, 애플리케이션에서 DB에 저장하기 전에 사용을 하는 임시 저장 공간이라고 이해를 하면 편할 것이다.
EntityManager.persist(entity);
엔티티 매니저를 통해서 영속성 컨텍스트
에 접근한다.
엔티티 매니저
(Entity Manager)란 영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공하는 것을 의미한다.
엔티티 매니저
(Entity Manager)는 엔티티를 저장하고, 수정하고, 삭제하고 조회하는 등 엔티티와 관련된 모든 일을 처리한다.
영속성 컨텍스트를 통해 데이터의 상태 변화를 감지하고 필요한 쿼리를 자동으로 수행한다.
비영속
상태라고 한다.영속
상태라고 한다.import javax.persistence.Persistence;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin()
try {
// 비영속
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 영속
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
ex.close();
}
}
}
Member 엔티티를 영속성 컨텍스트에서 분리한 상태를 준영속
상태라고 한다.
준영속 상태로 만드는 방법
em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
em.clear() : 영속성 컨텍스트를 완전히 초기화
em.close() : 영속성 컨텍스트를 종료
객체를 삭제한 상태를 삭제
라고 하며, 코드에서는 em.remove(member);
를 의미한다.
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
영속성 컨텍스트 내부에는 엔티티 인스턴스를 캐싱하는 1차 캐시
가 있다.
일반적으로 트랜잭션 내에서 유효한 생명주기를 갖는다.
member1
를 조회하면, JPA는 DB가 아닌 1차 캐시에 저장된 값을 가져온다.그런데 만약 member2
를 조회할 때, 1차 캐시에 없다면, DB를 조회한다. 그런 다음에 해당 값을 1차 캐시에 저장하고 값을 반환한다.
사실, 엔티티 매니저라는 건 데이터베이스 트랜잭션이 끝날 때 종료된다. ⇒ 비즈니스가 끝나면, 해당 엔티티 매니저가 사라져버린다.
차 캐시라는건 데이터베이스 하나의 트랜잭션 안에서만 효과가 있기 때문에 성능에 대한 장점은 없다.
영속성 컨텍스트는 영속성 컨텍스트 안에서 영속 엔티티들의 동일성을 보장한다.
따라서 같은 식별자를 통해 1차 캐시에서 얻은 엔티티 인스턴스는 ==
연산을 통해 동일성을 보장한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
JPA가 영속 엔티티의 동일성을 보장한다. 마치 자바 컬렉션에서 똑같은 레퍼런스 객체를 꺼내오는 것과 같다. 1차 캐시가 있기 때문에 가능한 것이다.
같은 트랜잭션 안에서 비교할 때에만 동일하다는 것이다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA); // 1
em.persist(memberB); // 2
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // 3 [트랜잭션] 커밋
em.persist(memberA);
[1] em.persist(memberA);
을 통해 memberA가 1차 캐시에 들어간다.(집어 넣는다)
[2] 그러면서 동시에 JPA가 이 엔티티를 분석해서 INSERT SQL 쿼리를 생성한다.
[3] 그래서 쓰기 지연 SQL 저장소에 쌓아둔다.
em.persist(memberB);
[4] 그런 다음에 em.persist(memberB);
을 하면, memberB도 1차 캐시에 들어간다.(집어 넣는다)
[5] 그리고 이때, INSERT SQL 쿼리를 생성해서 쓰기 지연 SQL 저장소에 차곡차곡 쌓아둔다.
transaction.commit();
[6] 그리고 transaction.commit();
을 통해 커밋하는 순간 쓰기 지연 SQL 저장소
에 있던 데이터들이 flush가 되면서, 데이터베이스
에 INSERT SQL을 보낸다.
[7] 그리고 실제 데이터베이스가 commit 된다.
변경 감지
란 JPA를 사용하여 데이터를 수정하려면 Entity
를 조회하여 조회된 Entity
데이터를 변경만 하면 데이터베이스
에 자동으로 반영이 되도록 하는 기능이다.[1] 데이터베이스에 커밋하면, 내부적으로 flush가 발생하고
[2] JPA가 엔티티하고 스냅샷을 비교한다.
엔티티 인스턴스 값
을 읽어온 그 시점의 최초 상태를 영속성 컨텍스트
에 저장하는 데, 이를 스냅샷
이라 부른다.만약 MemberA에 대한 데이터가 변경되면 쓰기 지연 SQL 저장소
에 [3] UPDATE SQL 쿼리를 생성한다.
그리고 [4] UPDATE SQL 쿼리를 flush를 통해 데이터베이스에 반영하고
[5] commit 하게 된다.
flush
란 영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것을 말한다.
플러시가 발생하면, 다음 3가지가 일어난다.
변경 감지
수정된 엔티티의 쓰기 지연 SQL 저장소에 등록
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시하는 방법은 3가지가 있다.
em.flush() - 직접 호출 -> 쿼리가 DB에 반영되어 버린다.
트랜잭션 커밋 - 플러시 자동 호출
JPQL 쿼리 실행 - 플러시 자동 호출
플러시 모드 옵션은 기본적으로 AUTO(em.setFlushMode(FlushModeType.AUTO
)로 사용된다. (거의 사용할 일은 없다)
FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러스 (기본값)
FlushModeType.COMMIT : 커밋할 때만 플러시
정리하면, 플러시는
영속성 컨텍스트를 비우지 않고, 변경 내용을 데이터베이스에 동기화하고
트랜잭션 커밋 직전에만 동기화하면 된다.
이 글은 함께 자라기 책을 읽고 저의 생각과 같이 정리한 내용입니다.
책을 2월에 사고 난 뒤 9개월이 지난 지금에서야 다 읽게 되었다.
2월 당시에는 팀 프로젝트를 하기 전이라, 협업
에 대해 크게 흥미가 없어서 그런지 집중이 잘 안됐다.
하지만 3월부터 10월까지 진행했던 히빗과 8월부터 9월까지 진행했던 굿프렌즈 팀 프로젝트를 경험한 이후에, 이 책을 다시 읽어보자는 생각이 들었다.
그래서 기존에 중반까지 읽었던 내용을 주말 동안 다시 복습 겸? 읽으면서 완독을 했다.
확실히 협업과 관련된 경험을 하고 나서 읽으니까, 공감된 부분들도 많았고 더 관심 있게 읽으면서 빠르게 완독할 수 있었다.
아래는 책을 읽으면서 개인적으로 느낀 인상 깊었던 부분 몇 개를 내 생각과 같이 정리해 보려고 한다.
“소프트웨어 개발에서 개발에서 점차 경력 연수를 중시하는 문화가 사라질 것이다. 따라서 개발자들은 자신의 경력 연차 외에 다른 것에도 신경을 써야 한다.”
몇 년 전까지만 해도 안데쉬 에릭손의 1만 시간 법칙
이 유행이었다. 그런데 이 법칙에서 나오는 시간을 본인의 경력
과 연관 지어선 안된다는 의미이다.
예를 들어, 일상생활에 우리는 어렸을 때부터 하루 세 번 3분씩 이를 닦는다. 하지만 이를 매일 닦는다고 10년, 20년 이후에도 이를 닦는 경력이 실력과 관련이 없다는 것을 누구나 알 수 있다.
여기서 말하는 의도적 수련
(deliberate practice)이란 자신의 기량을 향상시킬 목적으로 반복적으로 하는 수련을 의미한다.
국내 여러 스포츠 종목에서 국가대표로 지내는 선수분들은 세계 대회에 출전해서 메달을 따기 위해 매일 본인의 약점을 개선하고, 강점을 더 강화하는 애쓰는 수련을 하고 있다.
이 글을 읽고, ‘내가 공부한 것이 의도적인 수련인가?’라는 생각을 하게 되었다. 단순히 남이 작성한 글을 읽거나 강의를 듣는 시간이 아니라, 누군가한테 설명할 정도의 수준까지 내 것으로 만드는 시간이 의도적인 수련
이라고 생각한다.
정리하자면, 단순히 인풋만 하는 게 아니라 아웃풋까지 이어 나가는 과정과 결과가 중요하다고 생각한다.
유튜브 채널: 개발바닥 - 용기를 잃지않는 개발자 이력서 (55초 ~)
위의 내용과 완전히 일치하진 않지만, 최근 개발바닥 채널에서 “용기를 잃지않는 개발자 이력서”에서도 향로님이 말씀해주신 내용을 가져와 봤다.
나는 ~~을 위해 이걸 했고, 그래서 그 결과가 ~~이다 이어져야 개인의 실력이 성장하지 않을까 생각한다. 흔히 이력서를 작성할 때에도 Why - What - How - Result
과 같이 작성해야 보는 사람에게도 더 읽기 쉬워진다.
IT 기업에서 종사하시는 여러 직무의 분들이 연말에 한 해를 되돌아보면서 회고라는 글을 작성한다.
일 년 회고를 할 때 항상 되짚어 보는 것 중 하나가 나 자신에게 얼마나 투자를 했냐라는 것이다.
자기개발이 중요한 이유는 현재 나에게 무엇을 투자했느냐가 1년, 혹은 2년 후의 나를 결정한다고 느끼기 때문이다.
2022년 2학기 때 대학교에서 ‘운영체제’라는 과목을 수업하시는 교수님이 하신 말씀이 기억이 문득 생각났다.
교수님이 이전 삼성전자에 재직할 당시에, ‘전체 사원들 중 10% 이하 만이 자기개발을 하고, 나머지 90%는 현재 삶에 만족하는 경우가 많다’ 라고 하셨다.
이 책에서도 2시간 이상 자기개발에 투자하는 사람이 13.2%밖에 되지 않았다. (물론 2012년에 조사한 자료라, 현재와 다를 수 있다)
평소 자기개발을 열심히 하시면서, 블로그를 운영하시는 여러 현직자분들이 좋은 회사에 이직한 것을 블로그를 통해 많이 봤다.
나는 회사에서 일을 해본 경험이 적고, 이직 경험도 없어서 크게 와닿지는 않지만, 꾸준히 자기개발을 하면서 그러한 경험을 맛보는 기회를 언젠간 찾아오지 않을까 기대해 본다.
위 그림은 실력
과 작업 난이도
를 기준으로 심리 상태를 그린 도식이다.
주목해야 할 부분은 몰입
에 해당하는 영역인데, 이때 최고 수준의 집중력을 보이고, 그 덕분에 퍼포먼스나 학습 능력이 최대치가 될 수 있다고 한다.
해당 영역이 아닌, 실력이 작업 난이도를 초과하면 지루함을 느끼는 영역이나 실력보다 높은 난이도의 작업을 하면 불안함(혹은 두려움)을 느낀다고 한다.
‘팬시님! 현 프로젝트에 통합 및 인수 테스트를 작성해 주세요~ ‘와 같은 작업이 왔을 때 어렵다고 느껴지면, 우선 단위 테스트 작성하는 방법부터 익히도록 하자.
실수 관리 문화
에서는 실수가 나쁜 결과를 내기 전에 빨리 회복하도록 돕고, 실수를 공개하고, 실수에 대해 서로 이야기하고 거기에서 배우는 분위기가 생긴다.
회사 문화가 실수 관리에 가까울수록 그 기업의 혁신 정도가 더 높고, 회사의 수익성이 더 높다는 연구 자료가 있다.
즉, 실수 관리를 하는 문화일수록 학습
을 더 잘한다.
팀 프로젝트를 하다 보면 반드시는 아니지만, 실수가 종종 나타나는 경우가 있다. 그럴 때마다 실수를 한 팀원에게 따끔한 충고보단 따뜻한 조언을 하는 게 중요하다고 생각한다.
똑같은 실수에 대해 반복적이면 안 되겠지만, 처음 하는 실수에 대해서는 누구에게나 생길 수 있고, 그런 상황이 발생할 때 어떻게 해야 나은 방향으로 개선할 수 있을지 스스로에게 질문을 하면서 답을 찾아가 보자.
새로운 기술을 개인 또는 외부 스터디 활동을 통해서 배웠다고 회사에 바로 적용하기가 쉽지 않다.
아무리 기술적인 실천법이라고 해도 그 기술은 사회적 맥락속에서 실천되어야 하며, 그 기술의 성공을 위해서는 사회적 자본과 사회적 기술이 함께 필요하다.
이 책에서는 신뢰 구축을 보다 잘하는 사람을 사회적 자본이 좋은 사람들이라고 합니다. 그리고 사회적 자본이 좋은 사람들이 통상 사회적 기술이 뛰어나다고 합니다.
뛰어난 개발자일수록 동료와의 협업에 시간을 투자하는 부분으로 커뮤니케이션의 중요성을 강조합니다.
TDD(테스트 주도 개발)이라는 프로그래밍 기법을 가져왔다면, 해당 기법 전체를 단번에 적용하는 것보다는 팀원들과 지속적으로 협업하면서 기초적인 개념부터 천천히 도입하는 방향으로 가는게 좋다고 생각한다.
실제로 이전 팀 프로젝트에서 DDD 패턴을 도입했을 때, 해당 개념의 전체적인 것을 적용하지 않고 일부의 개념만 도입하고 이후에 추가하는 식으로 진행했었다.
신뢰 자산이 높은 조직은 커뮤니케이션 효율이나 생산성이 높다.
특히 애자일을 제대로 하려는 조직이라면 이 부분에 많은 노력을 들이고 있다.
이 책에서는 신뢰를 쌓는 데에 널리 사용되는 한 가지 방법은 투명성, 공유, 인터랙션이라고 한다.
자신이 한 작업물을 투명하게 서로 공유하고 그에 대해 피드백을 주고받으며 인터랙션을 하는 것이다.
개발자끼리 팀 프로젝트를 진행했을 때, 기획부터 같이 진행한 적이 있었다. 그때 각자만의 아이디어를 공유할 때 한 사람당 여러 개의 아이디어를 공유했는데, 여기서도 그와 관련된 글이 있다.
나의 아이디어가 1개밖에 없다면, 아아디어=나
가 되어서 상대방이 나에게 뭐라고 하기가 쉽지 않다.
반면에 복수로 공유하면, 그런 불안감이 상대적으로 덜하고, 부정적인 피드백을 수용하려는 마음도 있다.
이 책에서는 복수 공유를 한 그룹이 단수로 공유하는 것보다(개인당 아이디어가 1개) 신뢰도가 높아지고 성과도 더 좋았다고 합니다.
품질 전문가 제럴드 와인버그는 품질을 다음과 같이 정의했습니다.
“품질 이란 누군가에게 가치가 되는 것이다.”
사실 품질은 사람을 빼놓고 이야기할 수가 없다.
고품질을 얻으려고 노력하는 사람들은 ‘인간’에 대한 이해가 필수적이다.
설득
도 마찬가지이다. 대부분의 사람들이 설득을 하기 위해 객관성이 필요하다고 하는데, 사실 객관성을 정의하는 것도 사람이기 때문에, 이 개념 자체가 매우 주관적이라는 것이다.
회사에는 직원과 사장이 있고, 중요한 부분을 결정하고 실행하기 전에는 직원이 사장으로부터 승인을 받아야 하는 경우가 있다.
사장도 사람이기 때문에, 사장을 설득하라면 어쩔 수 없이 객관적인 자료뿐 아니라 감정적이고 직관적인 부분도 필요하다.
이 책에서는 남을 설득하려면 논리성과 객관성에 대한 환성을 버려야 하고, 그래야 현실적으로 설득이 가능하다고 한다.
결국, 설득에 성공하려면 우선 그 사람을 이해하는 것에서 출발해야 한다. 그런 이유로 설득을 하기 위해 ‘객관적’인 자료뿐 아니라 그 이상으로 상대를 이해하는데 많은 시간을 투자해야 한다.
상대방에게 신뢰감을 잃어버리면, 아무리 좋은 ‘객관적’인 자료와 근거가 있더라도 소용없다.
이 책에서는 심리적 안전감
을 내 생각이나 의견, 질문, 걱정, 혹은 실수가 드러났을 때 처벌받거나 놀리지 않을 거라는 믿음을 말한다고 한다.
이 개념은 정말 중요하다고 생각한다. 어떤 프로젝트를 진행할 때, 팀원 간의 의견을 주고 받는게 없다면, 이러한 심리적 안전감이 떨어질 것이고, 갈수록 상대방에 대한 이해가 낮아질 것이라고 본다.
히빗 프로젝트를 진행하면서, 매주 회의를 팀원들과 같이 진행하면서 모르는 부분이나 궁금한 부분들을 적극적으로 물어봤고, 나에게 질문이 들어오는 경우에도 최대한 자세히 알려주려고 노력했다.
개발을 하면서, 어려운 부분을 마주쳤을 때 혼자서 끙끙 앓는 것보다는 혼자서 어느 정도의 시간을 투자한 뒤에 동일 직군의 팀원에게 물어보는게 팀 전체적으로 더 도움이 된다고 생각한다.
이 책의 제목인 함께 자라기
는 함께(협력)과 자라기(학습)을 내포하고 있다.
협력과 학습에 대한 중요성을 여러 가지 상황을 예시로 들면서, 좋은 해결 방안을 알려주고 있는 내용이었다.
이후에 팀 프로젝트를 진행한다면, 이 책을 다시 한번 복기해봐야 겠다는 생각이 들 정도로, 나에게 도움을 많이 준 책이다.