Develop/Springboot

[JPA] 다양한 연관관계 매핑 - @OneToMany @ManyToOne @OneToOne @ManyToOne | [JPA] Various Association Mapping - @OneToMany @ManyToOne @OneToOne @ManyToOne

인프런에서 에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 쓴 정리 글입니다.https://www.inflearn.com/course/ORM-JPA-Basic 자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 서버 데이터베이스 프레임워크 및 라이브러리 프로그래밍 언어 서비스 개발 Java JPA 스프링 데이터 JPA 온라인 강의www.inflearn.com평소에 Spring Data JPA 를 썼는데, 김영한님은 JPA 자체를 강의하시더라구요.김영한님 강의 바탕으로 Spring Data ..

[JPA] 다양한 연관관계 매핑 - @OneToMany @ManyToOne @OneToOne @ManyToOne | [JPA] Various Association Mapping - @OneToMany @ManyToOne @OneToOne @ManyToOne

728x90

인프런에서 에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 쓴 정리 글입니다.

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 서버 데이터베이스 프레임워크 및 라이브러리 프로그래밍 언어 서비스 개발 Java JPA 스프링 데이터 JPA 온라인 강의

www.inflearn.com

평소에 Spring Data JPA 를 썼는데, 김영한님은 JPA 자체를 강의하시더라구요.

김영한님 강의 바탕으로 Spring Data JPA로 강의 소스를 테스트해보고 개념을 기록하기 위해 포스팅을 하게되었습니다.



다양한 연관관계 매핑

1.연간관계 매핑시 고려사항 3가지

1-1. 다중성

  • 다대일 [N:1] : @ManyToOne
  • 일대다 [1:N] : @OneToMany
  • 일대일 [1:1] : @OneToOne
  • 다대다 [N:M] : @ManyToMany

1-2. 단방향, 양방향

1-3. 연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
  • 객체 양방향 관계는 A->B, B-> A처럼 참조가 2군데
  • 객체 양방향 관계는 참조가 2군데 있다. 둘중 테이블의 외래 키를 관리하는 곳을 지정해야함
    A를 바꿀때 B도 같이 바꿀지 / B를 바꿀때 A도 같이 바꿀지
  • 연관관계의 주인 : 외래 키를 관리하는 참조
  • 주인의 반대편 : 외래 키에 영향을 주지 않음

2. 다대일 [N:1]

연관관계의 주인 : N이다

2-1. 다대일 단방향

Member : N - Team : 1

Member에서 Team을 참조한다.

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 외래키
    private Team team;

}
@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
}

외래키가 있는 곳에 참조를 걸고 연관관계 매핑을 한다.

DB입장에서 보면 당연히 N에서 FK가 있어야한다.

반대로 Team이라면, list가 들어가니까 설계가 안맞다.

  • 가장 많이 사용한다
  • 다대일의 반대는 일대다 이다.

2-2. 다대일 양방향

Member : N - Team : 1

Member에서 Team을 참조한다. Team에서도 Member를!

연관관계 주인이 FK 관리한다.
반대쪽은 어차피 읽기만 가능하기 때문에 Team에서 List를 추가하기만 하면 된다.

이때 mappedBy로 연관관계의 주인을 읽을 것이라는 것 명시가 중요

// Team 클래스
    @OneToMany(mappedBy = "team") //참조를 당하는 쪽에서 읽기만 가능! 
    private List<Member> members = new ArrayList<>();
  • 외래키가 있는 쪽이 연관관계의 주인
  • 양쪽을 서로 참조하도록 개발

3. 일대다 [1:N]

3-1. 일대다 단방향

권장하지 않는다.

Team을 중심으로! : Team에서 외래키를 관리

Team은 Member를 알고싶은데 Member는 Team을 알고싶지 않음.

DB입장 : Member에 FK 걸어야한다.

Team의 List 바꾸었을 때 DB의 Mebmer중에 어떤 것의 TEAM_ID를 바꿔야한다.

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

}
@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
}

DB에는 잘 들어가는데 UpdateQurey가 나가는 등 query가 많이 나간다.

team에서 Member list를 저장할 때, Member테이블에도 team_id를 update해줘야한다.

Team을 건드렸는데 Member 테이블에 영향이간다 > 이해, 추적에서 조금 어렵다.

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용 (중간에 테이블 하나 추가) team_member라는 중간테이블이 생겨버린다 : team_id와 member_id를 갖고있다. 단점 : 테이블이 1개 더들어가서 운영이 어렵다.
  • 일대다 단반향 매핑의 단점
    • 엔티티가 관리하는 외래키가 다른 테이블에 있음
    • 연관관계 관리를 위해 추가로 UPDATE SQL 실행
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자 : 객체관계를 조금 포기!

3-2. 일대다 양방향

약간 야매로 된다ㅋㅋㅋ

// Member 클래스
    @ManyToOne
    @JoinColumn(name="TEAM_ID", insertable = false, updatable = false) //중요!!
    private Team team;

근데 이러면 Team Member모두 @JoinColumn이 붙어서 둘다 연관관계의 주인이된다.

그래서 JoinColumn의 옵션을 사용해서 mapping은 되어있고 값은 다 쓰는데 insertable, updatable을 막아 read 전용으로 만든다.

관리는 Team으로하고 Member는 읽기만한다.

  • 이런 매핑은 공식적으로 존재 X
  • @JoinColumn(insertable=false, updatable=false)
  • 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
  • 다대일 양방향을 사용하자

일대다 일대일 [1:1]

다대다 [N:M]

This is a summary post based on Kim Young-han's Java ORM Standard JPA Programming - Basics course on Inflearn.

https://www.inflearn.com/course/ORM-JPA-Basic

 

Java ORM Standard JPA Programming - Basics - Inflearn

For those who are new to JPA or use JPA in practice but lack the fundamental theory — this course helps you build a solid foundation so that even beginners can confidently use JPA in real-world projects. Beginner Web Development Server Database Frameworks & Libraries Programming Languages Service Development Java JPA Spring Data JPA Online Course

www.inflearn.com

I've been using Spring Data JPA, but Kim Young-han actually teaches JPA itself.

Based on his lectures, I decided to write this post to test the course material with Spring Data JPA and document the concepts.



Various Association Mappings

1. Three Things to Consider When Mapping Associations

1-1. Multiplicity

  • Many-to-One [N:1] : @ManyToOne
  • One-to-Many [1:N] : @OneToMany
  • One-to-One [1:1] : @OneToOne
  • Many-to-Many [N:M] : @ManyToMany

1-2. Unidirectional vs. Bidirectional

1-3. Owner of the Association

  • Tables establish an association between two tables with a single foreign key
  • In a bidirectional object relationship, there are two references: A→B and B→A
  • Since there are two references in a bidirectional object relationship, you need to designate which side manages the foreign key
    When you change A, should B also change? / When you change B, should A also change?
  • Owner of the association: the reference that manages the foreign key
  • Inverse side: does not affect the foreign key

2. Many-to-One [N:1]

Owner of the association: N side

2-1. Many-to-One Unidirectional

Member : N - Team : 1

Member references Team.

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 외래키
    private Team team;

}
@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
}

You place the reference and map the association where the foreign key exists.

From the DB's perspective, the FK naturally belongs on the N side.

If it were on the Team side instead, you'd need a list, and the design wouldn't make sense.

  • This is the most commonly used mapping
  • The inverse of Many-to-One is One-to-Many

2-2. Many-to-One Bidirectional

Member : N - Team : 1

Member references Team. And Team also references Member!

The owner of the association manages the FK.
Since the inverse side can only read anyway, you just need to add a List to Team.

Here, it's important to specify with mappedBy that this side reads from the association owner

// Team 클래스
    @OneToMany(mappedBy = "team") //참조를 당하는 쪽에서 읽기만 가능! 
    private List<Member> members = new ArrayList<>();
  • The side with the foreign key is the owner of the association
  • Develop so that both sides reference each other

3. One-to-Many [1:N]

3-1. One-to-Many Unidirectional

This is not recommended.

Centered around Team: Team manages the foreign key

Team wants to know about Member, but Member doesn't want to know about Team.

From the DB's perspective: the FK must be on the Member table.

When you modify Team's List, you have to update the TEAM_ID of some row in the Member table in the DB.

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

}
@Entity
@Getter
@Setter
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
}

The data goes into the DB fine, but extra queries like UPDATE queries are fired.

When saving the Member list from Team, the team_id in the Member table also needs to be updated.

You touched Team, but it affects the Member table — this makes it a bit harder to understand and trace.

  • In One-to-Many unidirectional, the One (1) side is the owner of the association
  • In a One-to-Many table relationship, the foreign key is always on the Many (N) side
  • Due to the mismatch between objects and tables, this results in an unusual structure where you manage the foreign key of the opposite table
  • You must use @JoinColumn. Otherwise, it uses a join table strategy (adds an intermediate table) — a middle table like team_member gets created with team_id and member_id. The downside: having one extra table makes operations harder.
  • Downsides of One-to-Many unidirectional mapping
    • The foreign key managed by the entity is in a different table
    • Additional UPDATE SQL is executed to manage the association
  • Use Many-to-One bidirectional mapping instead of One-to-Many unidirectional — sacrifice a bit on the object relationship side!

3-2. One-to-Many Bidirectional

This kinda works as a hacky workaround lol

// Member 클래스
    @ManyToOne
    @JoinColumn(name="TEAM_ID", insertable = false, updatable = false) //중요!!
    private Team team;

But this way, both Team and Member have @JoinColumn, making both of them owners of the association.

So you use JoinColumn options — the mapping exists and values are all there, but you block insertable and updatable to make it read-only.

Management is done through Team, and Member only reads.

  • This mapping doesn't officially exist
  • @JoinColumn(insertable=false, updatable=false)
  • A way to use it like bidirectional by using a read-only field
  • Just use Many-to-One bidirectional

One-to-Many One-to-One [1:1]

Many-to-Many [N:M]

댓글

Comments

Develop/Springboot

[JPA] 연관관계 매핑 | [JPA] Association Mapping

인프런에서 에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 쓴 정리 글입니다.https://www.inflearn.com/course/ORM-JPA-Basic 자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 서버 데이터베이스 프레임워크 및 라이브러리 프로그래밍 언어 서비스 개발 Java JPA 스프링 데이터 JPA 온라인 강의www.inflearn.com평소에 Spring Data JPA 를 썼는데, 김영한님은 JPA 자체를 강의하시더라구요.김영한님 강의 바탕으로 Spring Data ..

[JPA] 연관관계 매핑 | [JPA] Association Mapping

728x90

인프런에서 에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 쓴 정리 글입니다.

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 서버 데이터베이스 프레임워크 및 라이브러리 프로그래밍 언어 서비스 개발 Java JPA 스프링 데이터 JPA 온라인 강의

www.inflearn.com

평소에 Spring Data JPA 를 썼는데, 김영한님은 JPA 자체를 강의하시더라구요.

김영한님 강의 바탕으로 Spring Data JPA로 강의 소스를 테스트해보고 개념을 기록하기 위해 포스팅을 하게되었습니다.


 


연관관계 매핑 기초

1. 단방향 연관관계

<아직 안들음>

2. 양방향 연관관계와 연관관계의 주인 : 기본

양방향 연관관계 -> 양쪽으로 참조한다.

객체 : 참조를 활용
테이블 : FK를 이용한 join

객체-테이블 사이 패러다임 차이를 봐야한다.

2-1. 테이블 연관관계

단방향과 양방향과 차이가 없다.

TEAM->MEMBER 알고싶든, MEMBER -> TEAM알고싶든 Foreign Key로 join해서 알 수 있다.
양방향 단방향 상관없이 FK로 모든 연관관계 알 수 있다.

2-2. 객체 연관관계

Member에서 Team변수를 갖고있으면 Team으로 갈 수 있다.
Team에서는 List를 갖고있어야 Member로 갈 수 있다.

 

멤버변수로 다른 객체를 갖고있어야 서로에게 접근이 가능하다.

 

[참고] : List 멤버변수를 사용할 땐 꼭 new ArrayList<>() 이용해서 초기화를 해주자!
add() 할 때 NullPointError가 안뜨게!

@Entity
@Getter
@NoArgsConstructor
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    @Builder
    private Team(String name) { //여기 그냥 members도 param으로 넣었다가 에러 팡!
        this.name = name;
    }
}
@Entity
@Getter
@NoArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name= "TEAM_ID")
    private Team team;

    @Builder
    private Member(String username, Team team) {
        this.username = username;
        this.team = team;
    }
}

궁금한 것

EntitiyTransaction tx = em.getTrasaction();
em.persist(team);
em.flush();
em.clear();

반대 방향으로도 그래프 탐색이 가능해 진다.

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MemberTest {
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TeamRepository teamRepository;

    @Before
    public void setUp() throws Exception {
        Team team = Team.builder()
                .name("TeamA")
                .build();

//        teamRepository.save(team);

        Member member = Member.builder()
                .username("member1")
                .team(team)
                .build();

        memberRepository.save(member);
    }

    @Test
    public void 잘_저장되었는지_불러오기() {
        Member member = memberRepository.findAll().get(0);
        String username = member.getUsername();
        assertThat(username).isEqualTo("member1");

        Team team = member.getTeam();
        assertThat(team.getName()).isEqualTo("TeamA");

        List<Member> members = team.getMembers();
        for (Member m : members) {
            assertThat(m.getUsername()).startsWith("member");
        }

    }

강좌랑 cascade 부분만 달라서 왜 그런가 하고 생각해 봤는데 강좌에서는 save를 두번 했었다.
강좌코드 대로 코딩하고 테스트한 결과는 아래

@Entity
@Getter
@NoArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

//    @ManyToOne(cascade = CascadeType.ALL)
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @Builder
    private Member(String username, Team team) {
        this.username = username;
        this.team = team;
    }
}
@Entity
@Getter
@NoArgsConstructor
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    @Builder
    private Team(String name) { //여기 그냥 members도 param으로 넣었다가 에러 팡!
        this.name = name;
    }
}

테스트코드

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MemberTest {
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TeamRepository teamRepository;

    @Before
    public void setUp() throws Exception {
        Team team = Team.builder()
                .name("TeamA")
                .build();

        teamRepository.save(team);

        Member member = Member.builder()
                .username("member1")
                .team(team)
                .build();

        memberRepository.save(member);
    }

    @Test
    public void 잘_저장되었는지_불러오기() {
        Member member = memberRepository.findAll().get(0);
        String username = member.getUsername();
        assertThat(username).isEqualTo("member1");

        Team team = member.getTeam();
        assertThat(team.getName()).isEqualTo("TeamA");

        List<Member> members = team.getMembers();
        for (Member m : members) {
            assertThat(m.getUsername()).startsWith("member");
        }

    }
}

Member 저장할 때 Team을 저장하도록 cascade 설정을 하지 않고,

TestCode 작성시에 Member save , Team save를 각각 해줬다

Team이 이미 save가 된 상태에서 Member를 save할 경우인데,

어차피 DB에서는 Member에 FK가 있기 때문에 매핑이 가능해진다!

객체에서의 매핑은 이미 Team, Member 모두 각자의 참조객체를 갖고있기 때문에 가능하고!

내가 처음에 작성한 코드의 경우에는 member만 저장해서 team도 같이 저장하는 것이었기 때문에 Member를 저장할 때 cascade 옵션을 줘야했다.

따라서 Member repository에 저장하더라도, Team의 Insert를 먼저 실행 후에, Member insert를 진행하여 Member Table의 FK에 Team을 저장해준다.

Hibernate: insert into team (member_id, name) values (null, ?)
Hibernate: insert into member (member_id, team_id, username) values (null, ?, ?)

Hibernate: select member0_.member_id as member_i1_0_, member0_.team_id as team_id3_0_, member0_.username as username2_0_ from member member0_

Q. 양방향 매핑이 좋은가?

A. 객체는 사실 단방향이 좋다! -> 신경쓸게 많음

2-3. 객체와 테이블이 관계를 맺는 차이

2-3-1. 객체의 연관관계 - 2개

​ Member -> Team 연관관계 1개 (단방향) - Team 레퍼런스 객체

​ Team -> Member 연관관계 1개 (단방향) - Member 레퍼런스 객체

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다
  • 객체를 양방향으로 참조하려면 단방향 연관 관계를 2개 만들어야 한다.
class Member{
    Team team;    // TEAM -> Member (team.getMember())
}
class Team{
    Member member;    // MEMBER -> TEAM (member.getTeam())
}

2-3-2. 테이블의 연관관계 - 1개

​ Team <-> Member 연관관계 1개 (양방향) - FK하나로 양쪽의 연관관계 알 수 있음 (join)

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽으로 조인할 수 있다.)
SELECT * 
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

SELECT * 
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

2-4. 연관관계의 주인

딜레마가 생긴다 > solution : 둘중 하나로 외래키를 관리한다!

  • Team에 있는 List 로 FK를 관리할지
  • Member에 있는 Team으로 FK를 관리할지

2-4-1. 양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리 (등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

mappedBy : 나는 누군가에 의해서 매핑이 되었어! 나는 주인이 아니야!

public class Team {
    @OneToMany(mappedBy = "team") 
    private List<Member> members = new ArrayList<>();
}

public class Member { 
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

mappedBy : 나는 team에 의해서 관리가 된다 : Member 객체의 team 변수에 의해서 관리된다.

@JoinColumn의 Team: 나는 앞으로 Team을 관리할꺼야

2-4-2. 누구를 주인으로?

  • 외래키가 있는 곳을 주인으로 정해라
  • 여기서는 Member.team이 연관관계의 주인!

성능 이슈!

Member의 경우에는 insert 쿼리 하나인데

Team의 경우에는 insert 쿼리 + update 쿼리

DB 입장에서 외래키가 있는 곳이 무조건 N

= N 이 있는 곳이 무조건 주인

= @ManyToOne 이 무조건 주인

3. 양방향 연관관계와 연관관계의 주인 : 주의점, 정리

3-1. 양방향 매핑시 가장 많이 하는 실수

  • 연관관계의 주인에 값을 입력하지 않음
    @RunWith(SpringRunner.class)
    @DataJpaTest
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    public class FailTest {
        @Autowired
        MemberRepository memberRepository;
    
        @Autowired
        TeamRepository teamRepository;
    
        @Autowired
        EntityManager entityManager;
    
        @Test
        public void 일차캐싱에_따른_저장_테스트() {
    
            Team team = Team.builder()
                    .name("TeamA")
                    .build();
    
            teamRepository.save(team);
    
            Member member = Member.builder()
                    .username("member1")
                    .team(team)
                    .build();
    
    //        team.getMembers().add(member);
    
            memberRepository.save(member);
    
            // 주인(Member)이 연관관계를 설정하지 않음!!
            // 역방향(주인이 아닌 방향)만 연관관계 설정
    //        entityManager.clear();
    
            Team findTeam = teamRepository.findAll().get(0);
            List<Member> members = findTeam.getMembers();
    
            assertThat(members).isEmpty();
        }
    }
  • entityManager.clear(); 을 안했을 경우
    : 1차 캐시를 해서 영속성 컨텍스트가 되어있는 상태 값 세팅 연관관계가 되어있는걸 그냥 가져온다.
    이렇게 실행하면 DB에서 select 쿼리가 안 나간다.
  • Team이 그냥 영속성 컨텍스트에 들어가있어서, team에는 현재 member가 없는상태.
    그러다보니 1차 캐싱으로 인해 아무것도 안들어가 있음!
  • 객체지향적으로 양쪽다 값을 입력해야 한다!

3-2. 양방향 연관관계 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 성정하자
  • 연관관계 편의 메소드를 생성하자
  • 양방향 매핑시에 무한루프를 조심하자
    예 ) toString(), lombok, JSON 생성 라이브러리
Team team = Team.builder()
    .name("TeamA")
    .build();

teamRepository.save(team);

Member member = Member.builder()
    .username("member1")
    .team(team)
    .build();

team.getMembers().add(member);

이런식으로 Member에 한줄을 넣어주기 보다! 연관관계 편의 메소드를 생성하자

 

Member에서 team을 set 해줄때 설정해버린다. - 하나면 세팅해도 두개가 같이 세팅이 되게!

@Builder
private Member(String username, Team team) {
    this.username = username;
    this.team = team;
    team.getMembers().add(this);
}

편의 메소드는 일에 넣어도 되고, 다에 넣어도 된다 : 상황을 보고 만들기를 추천한다.

 

@ToString / toString() 메소드

//Team 클래스
@Override
public String toString() {
    return "Team{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", members=" + members +
        '}';
}

//Member 클래스
@Override
public String toString() {
    return "Member{" +
        "id=" + id +
        ", username='" + username + '\'' +
        ", team=" + team +
        '}';
}

JSON 생성 라이브러리 : entity를 바로 Controller에서 바로 response 해버릴때 문제가 생긴다.

Member > Team > Member > Team > Member > Team > ...

  1. lombok에서 toString을 쓰지마라
  2. Controller에는 절대 Entity를 반환하지 마라.

4. 정리

4-1. 양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도됨 (테이블에 영향을 주지 않음)

JPA에서의 설계는 단방향만으로도 객체와 테이블의 매핑이 완료되어야한다.

테이블은 한번 만들면 굳어지는 것!

4-2. 연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함

This is a summary post based on Kim Young-han's Java ORM Standard JPA Programming - Basics course on Inflearn.

https://www.inflearn.com/course/ORM-JPA-Basic

 

Java ORM Standard JPA Programming - Basics - Inflearn

For those who are new to JPA or use JPA in practice but lack foundational theory — this course helps you build a solid understanding of JPA basics so that even beginners can confidently use JPA in real-world projects. Beginner Web Development Server Database Frameworks & Libraries Programming Languages Service Development Java JPA Spring Data JPA Online Course

www.inflearn.com

I've been using Spring Data JPA, but Kim Young-han actually teaches JPA itself.

Based on his lectures, I decided to write this post to test the course material using Spring Data JPA and document the concepts.


 


Association Mapping Basics

1. Unidirectional Association

<Haven't watched this part yet>

2. Bidirectional Association and the Owner of the Relationship: Basics

Bidirectional association -> references go both ways.

Object: uses references
Table: uses FK joins

We need to look at the paradigm difference between objects and tables.

2-1. Table Associations

There's no difference between unidirectional and bidirectional.

Whether you want to know TEAM->MEMBER or MEMBER->TEAM, you can find out by joining with the Foreign Key.
Regardless of whether it's bidirectional or unidirectional, you can figure out all associations with just the FK.

2-2. Object Associations

If Member has a Team variable, it can navigate to Team.
Team needs to have a List to navigate to Member.

 

Objects need to hold references to each other as member variables to be able to access one another.

 

[Note]: When using a List member variable, always initialize it with new ArrayList<>()!
This prevents NullPointerError when calling add()!

@Entity
@Getter
@NoArgsConstructor
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    @Builder
    private Team(String name) { //여기 그냥 members도 param으로 넣었다가 에러 팡!
        this.name = name;
    }
}
@Entity
@Getter
@NoArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name= "TEAM_ID")
    private Team team;

    @Builder
    private Member(String username, Team team) {
        this.username = username;
        this.team = team;
    }
}

Something I was curious about

EntitiyTransaction tx = em.getTrasaction();
em.persist(team);
em.flush();
em.clear();

Now graph traversal is possible in the reverse direction as well.

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MemberTest {
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TeamRepository teamRepository;

    @Before
    public void setUp() throws Exception {
        Team team = Team.builder()
                .name("TeamA")
                .build();

//        teamRepository.save(team);

        Member member = Member.builder()
                .username("member1")
                .team(team)
                .build();

        memberRepository.save(member);
    }

    @Test
    public void 잘_저장되었는지_불러오기() {
        Member member = memberRepository.findAll().get(0);
        String username = member.getUsername();
        assertThat(username).isEqualTo("member1");

        Team team = member.getTeam();
        assertThat(team.getName()).isEqualTo("TeamA");

        List<Member> members = team.getMembers();
        for (Member m : members) {
            assertThat(m.getUsername()).startsWith("member");
        }

    }

The only difference from the course was the cascade part, and when I thought about why, it's because the course called save twice.
Here's the test result after coding it exactly like the course:

@Entity
@Getter
@NoArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

//    @ManyToOne(cascade = CascadeType.ALL)
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @Builder
    private Member(String username, Team team) {
        this.username = username;
        this.team = team;
    }
}
@Entity
@Getter
@NoArgsConstructor
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    @Builder
    private Team(String name) { //여기 그냥 members도 param으로 넣었다가 에러 팡!
        this.name = name;
    }
}

Test code

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MemberTest {
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TeamRepository teamRepository;

    @Before
    public void setUp() throws Exception {
        Team team = Team.builder()
                .name("TeamA")
                .build();

        teamRepository.save(team);

        Member member = Member.builder()
                .username("member1")
                .team(team)
                .build();

        memberRepository.save(member);
    }

    @Test
    public void 잘_저장되었는지_불러오기() {
        Member member = memberRepository.findAll().get(0);
        String username = member.getUsername();
        assertThat(username).isEqualTo("member1");

        Team team = member.getTeam();
        assertThat(team.getName()).isEqualTo("TeamA");

        List<Member> members = team.getMembers();
        for (Member m : members) {
            assertThat(m.getUsername()).startsWith("member");
        }

    }
}

Without setting cascade on Member to also save Team,

I called save separately for both Member and Team in the test code.

Since Team is already saved before saving Member,

the mapping works because the FK exists in the Member table in the DB!

On the object side, the mapping works because both Team and Member already hold their own reference objects!

In my original code, I was saving only member and having team saved along with it, so I needed the cascade option on Member.

Therefore, even though we're saving through the Member repository, it executes the Team's INSERT first, then proceeds with the Member INSERT, storing the Team in the Member table's FK.

Hibernate: insert into team (member_id, name) values (null, ?)
Hibernate: insert into member (member_id, team_id, username) values (null, ?, ?)

Hibernate: select member0_.member_id as member_i1_0_, member0_.team_id as team_id3_0_, member0_.username as username2_0_ from member member0_

Q. Is bidirectional mapping better?

A. Actually, unidirectional is better for objects! -> There's a lot more to worry about

2-3. Differences in How Objects and Tables Form Relationships

2-3-1. Object Associations - 2

​ Member -> Team: 1 association (unidirectional) - Team reference object

​ Team -> Member: 1 association (unidirectional) - Member reference object

  • A bidirectional relationship in objects is not truly bidirectional — it's actually two separate unidirectional relationships
  • To reference objects bidirectionally, you need to create two unidirectional associations.
class Member{
    Team team;    // TEAM -> Member (team.getMember())
}
class Team{
    Member member;    // MEMBER -> TEAM (member.getTeam())
}

2-3-2. Table Associations - 1

​ Team <-> Member: 1 association (bidirectional) - A single FK lets you know both sides of the relationship (join)

  • Tables manage the association between two tables with a single foreign key
  • The MEMBER.TEAM_ID foreign key alone provides a bidirectional association (you can join from either side.)
SELECT * 
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

SELECT * 
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

2-4. The Owner of the Relationship

A dilemma arises > solution: manage the foreign key from one side!

  • Should we manage the FK through the List in Team?
  • Or through the Team in Member?

2-4-1. Bidirectional Mapping Rules

  • Designate one of the two object relationships as the owner of the relationship
  • Only the owner of the relationship can manage the foreign key (create, update)
  • The non-owner side can only read
  • The owner does NOT use the mappedBy attribute
  • The non-owner uses mappedBy to specify the owner

mappedBy: "I'm mapped by someone else! I'm not the owner!"

public class Team {
    @OneToMany(mappedBy = "team") 
    private List<Member> members = new ArrayList<>();
}

public class Member { 
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

mappedBy: "I'm managed by team" — meaning it's managed by the team variable in the Member object.

@JoinColumn's Team: "I'm going to manage Team from now on"

2-4-2. Who Should Be the Owner?

  • Make the side where the foreign key exists the owner
  • In this case, Member.team is the owner of the relationship!

Performance issue!

For Member, it's just one INSERT query,

but for Team, it's an INSERT query + an UPDATE query

From the DB's perspective, the side with the foreign key is always the N (many) side

= The N side is always the owner

= @ManyToOne is always the owner

3. Bidirectional Association and the Owner of the Relationship: Pitfalls and Summary

3-1. The Most Common Mistake in Bidirectional Mapping

  • Not setting a value on the owner of the relationship
    @RunWith(SpringRunner.class)
    @DataJpaTest
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    public class FailTest {
        @Autowired
        MemberRepository memberRepository;
    
        @Autowired
        TeamRepository teamRepository;
    
        @Autowired
        EntityManager entityManager;
    
        @Test
        public void 일차캐싱에_따른_저장_테스트() {
    
            Team team = Team.builder()
                    .name("TeamA")
                    .build();
    
            teamRepository.save(team);
    
            Member member = Member.builder()
                    .username("member1")
                    .team(team)
                    .build();
    
    //        team.getMembers().add(member);
    
            memberRepository.save(member);
    
            // 주인(Member)이 연관관계를 설정하지 않음!!
            // 역방향(주인이 아닌 방향)만 연관관계 설정
    //        entityManager.clear();
    
            Team findTeam = teamRepository.findAll().get(0);
            List<Member> members = findTeam.getMembers();
    
            assertThat(members).isEmpty();
        }
    }
  • When entityManager.clear() is not called:
    Since first-level caching is active and the persistence context still holds the values, it just fetches the existing association state.
    When executed this way, no SELECT query is actually sent to the DB.
  • Team is just sitting in the persistence context, and at this point, it has no members.
    Because of first-level caching, nothing is populated!
  • From an object-oriented perspective, you should set values on both sides!

3-2. Bidirectional Association Precautions

  • Always set values on both sides, considering the pure object state
  • Create convenience methods for managing associations
  • Watch out for infinite loops in bidirectional mapping
    e.g.) toString(), Lombok, JSON serialization libraries
Team team = Team.builder()
    .name("TeamA")
    .build();

teamRepository.save(team);

Member member = Member.builder()
    .username("member1")
    .team(team)
    .build();

team.getMembers().add(member);

Rather than adding a separate line for Member like this, create a convenience method for the association!

 

Set it up when setting the team on Member — so that setting one side automatically sets both!

@Builder
private Member(String username, Team team) {
    this.username = username;
    this.team = team;
    team.getMembers().add(this);
}

The convenience method can go on either the One side or the Many side — I recommend deciding based on the situation.

 

@ToString / toString() method

//Team 클래스
@Override
public String toString() {
    return "Team{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", members=" + members +
        '}';
}

//Member 클래스
@Override
public String toString() {
    return "Member{" +
        "id=" + id +
        ", username='" + username + '\'' +
        ", team=" + team +
        '}';
}

JSON serialization libraries: Problems occur when you return an entity directly from the Controller as a response.

Member > Team > Member > Team > Member > Team > ...

  1. Don't use toString from Lombok
  2. Never return an Entity directly from a Controller.

4. Summary

4-1. Bidirectional Mapping Summary

  • Unidirectional mapping alone already completes the association mapping
  • Bidirectional mapping simply adds the ability to query in the reverse direction (object graph traversal)
  • In JPQL, you often need to traverse in the reverse direction
  • Do unidirectional mapping well first, then add bidirectional when needed (it doesn't affect the table)

In JPA design, object-to-table mapping should be complete with just unidirectional associations.

Once a table is created, it's set in stone!

4-2. Criteria for Choosing the Owner of the Relationship

  • Don't choose the relationship owner based on business logic
  • The relationship owner should be determined based on where the foreign key is located

댓글

Comments

Dev Book Review

[객체지향의 사실과 오해] 2장 : 이상한 나라의 객체

※ 제가 책 내용을 이해하기 위한 정리와 함께 개인 주관이 들어가 있습니다 :) 1. 객체지향과 인지능력 객체 = 인간이 분명하게 인지하고 구별 할 수 있는 물리적인, 개념적 중계 객체지향 세계 != 현실세계 2. 객체, 그리고 이상한 나라 2-1. 행동과 상태 앨리스의 행동에 따라 상태가 변한다 상태를 결정하는 것 > 행동 행동의 결과를 결정하는 것 > 상태 => 행동의 결과는 상태에 의존적이다. 행동의 순서도 중요하다 : 순서가 올바라야 목적을 달성할 수 있다. 2-2. 앨리스의 행동과 상태 앨리스는 상태를 갖는다. 상태는 변경가능하다. 앨리스의 상태를 변경 시키는 것은 앨리스의 행동이다 행동의 결과는 상태에 의존적이며 상태를 이용해 서술가능하다. 행동의 순서가 결과에 영향을 미친다. 앨리스는 어떤 ..

[객체지향의 사실과 오해] 2장 : 이상한 나라의 객체

728x90

※ 제가 책 내용을 이해하기 위한 정리와 함께 개인 주관이 들어가 있습니다 :) 

1. 객체지향과 인지능력

객체 = 인간이 분명하게 인지하고 구별 할 수 있는 물리적인, 개념적 중계

객체지향 세계 != 현실세계

 

2. 객체, 그리고 이상한 나라

2-1. 행동과 상태

앨리스의 행동에 따라 상태가 변한다

상태를 결정하는 것 > 행동
행동의 결과를 결정하는 것 > 상태

 

=> 행동의 결과는 상태에 의존적이다.

행동의 순서도 중요하다 : 순서가 올바라야 목적을 달성할 수 있다.

 

2-2. 앨리스의 행동과 상태

  1. 앨리스는 상태를 갖는다. 상태는 변경가능하다.
  2. 앨리스의 상태를 변경 시키는 것은 앨리스의 행동이다
    • 행동의 결과는 상태에 의존적이며 상태를 이용해 서술가능하다.
    • 행동의 순서가 결과에 영향을 미친다.
  3. 앨리스는 어떤 상태여도 식별가능하다.

 

3. 객체 그리고 소프트웨어 나라

 

3-1. 상태(state)

어떤 행동의 결과는 과거에 어떤 행동을 했는가? (의존적이다)
행동의 과정과 결과를 판단하기 위함

 

[ hard ] : 이전 행동의 이력을 모두 합하여 현재 다음행동이 가능한지 판단

[ easy ] : 현재 상태를 보고 다음 행동 여부 판단이 훨씬 간단하다

 

현재 기반 행동 방식 이해가능

 

숫자, 문자열, 양 -> 객체 X 객체의 특성 O

 

 

객체의 상태 : 모든 멤버 변수

  • 객체의 프로퍼티 (property) : 상태를 구성하는 모든 요소 : 정적이다.
  • 프로퍼티 값 (property value) : 행동의 결과로 상태 요소 변경 : 동적이다.

프로퍼티의 종류

  • 링크(link) : 객체와 객체사이 의미있는 연결
    - link 통해서만 메세지 주고 받기가 가능하다
  • 속성(attribute) : 링크와 달리 객체를 구성하는 단순 값

객체의 프로퍼티와 프로퍼티 값

public class Example{
	private String name;
    
    public Example(String param){
    	this.name = param
    }
}

여기서 프로퍼티는 java의 멤버변수인 name을 의미하고

프로퍼티 값은 멤버변수인 name에 들어가는 계속해서 변하는 값을 의미하는 것 같다.

위 코드에서는 Example 생성자에서 인자로 들어온 param이 name의 프로퍼티 값이 된다.

 

링크 프로퍼티와 속성 프로퍼티

class Champion{
	private String name;
	private Ability ability;
}

class Ability{
	private String skill;
}

이렇게 주어져 있을 때.

Champion 객체의 name은 속성 프로퍼티

Champion 객체의 Ability는 링크 프로퍼티를 의미 하는 것 같다.

링크 프로퍼티는 reference object를 말하는 것 같다.

 

3-2. 행동(behavior)

= 상태를 변경시킨다.
= 행동이 부수효과(side effect)를 초래한다.

3-2-1. 상태와 행동

  • 객체의 행동은 상태에 영향 받는다. -> 상호작용이 현재 상태에 어떤 방식으로 의존하는가
  • 객체의 행동은 상태를 변경시킨다. -> 상호작용이 어떻게 현재 상태를 변경시키는가

 

3-2-2. 협력과 행동

상호작용이란?

= 다른 객체와의 협력

= 다른 객체에 요청을 보내기 (메세지 따라 행동 -> 자신의 상태 변경)
= 객체의 행동
= 다른 객체의 상태를 변경하는 것도 가능하다.

  1. 객체 자신의 상태 변경
  2. 행동 내에서 협력하는 다른 객체에 대한 메시지 전송

3-2-3. 상태 캡슐화

캡슐화의 역할

  1. 감추기 : private member 변수를 의미하는 듯 (상태)
  2. 노출하기 : public으로 정의한 메소드를 의미하는 듯 (행동) - 다른객체에 접근 할 수 있는 유일한 방법

상태의 변경 여부는 그객체의 자율에 맞긴다

캡슐화 > 자율성 향상 > 지능 향상 > 협력을 유연하고 간결하게

 

3-3. 식별자(identity)

객체 = 인간의 인지 능력을 이용해 식별 가능한 경계를 가진 모든 사물

식별자란? : 객체를 구분 가능한 특정 property

3-3-1. 값(value)

  • 식별자가 없다
  • 변하지 않는 값을 모델링
  • 불변상태 (immutable state) > 불변하니까 필요하지 않다.
  • 상태가 같은지로 판단한다.
  • 동등성 (equality) : 상태를 이용해 두 값이 같은지 판단가능

== 으로 비교해서 같은게 value인 것 같다!

 

3-3-2. 객체(object)

  • 식별자가 있다
  • 시간에 따라 변경되는 상태를 포함한다
  • 가변상태 (mutable state)
  • 두 객체의 상태가 모두 같아도 두 객체는 다르다 : 식별자가 필요한 이유
  • 동일성 (identical) : 식별자 기반으로 객체가 같은지 판단가능
  • 식별자가 상태에 독립적이다.
...더보기

.equal() 으로 비교해서 같은게 object인 것 같다!

만약 상태로 객체를 구분할 때
행동에 따라 상태가 변하면 그건 다른 객체일 것.

객체 (object) 값 객체 (value object)

= 참조 객체 (reference object)
= 엔티티 (entity)
= 식별자를 지닌 객체

= 식별자를 가지지 않는 값
  1. 객체는 상태를 가지며 상태는 변경 가능하다.
  2. 객체의 상태를 변경시키는 것은 객체의 행동이다.
    • 행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다.
    • 행동의 순서가 실행 결과에 영향을 미친다.
  3. 객체는 어떤 상태에 있더라도 유일하게 식별 가능하다.

 

4. 기계로서의 객체

객체 상태 조회 객체 상태 변경
쿼리 (query) 명령 (command)

 

기계 버튼

버튼 : 상태조회 / 변경 = 객체 행동 유발 위해 메시지 전송

사용자는 버튼으로 객체 접근 = 인터페이스

 

5. 행동이 상태를 결정한다.

  1. 선 상태 > 후 행동 (bad)
    • 상태가 공용 인터페이스 그대로 노출 가능성 ↑
    • 객체가 협력자 X 고립된 섬 O
    • 객체 재 사용성 저하
  2. 선 행동 > 후 상태 (good)
    • 어떤 행동 - 어떤 객체에 적합 (적합성 결정)
    • 객체의 행동 - 협력에서 완수해야하는 책임 > 책임-주도 설계 : Responseibility-Driven Design (RDD)

'선 상태 > 후 행동'의 의미

 

ㄱ. 공용 인터페이스 그대로 노출가능성이 높아진다

 

멤버 변수를 public으로 놓았을 때를 이야기 하는거 같다.

이 경우 다른 객체에서 멤버변수에 접근해 다른 값으로 그냥 할당할 수 있다.

즉, 멤버변수의 값을 다른 객체에서 바꿀 수 있는 것은 다른 객체가 외부 객체의 상태를 바꿀 수 있다는 말이 된다.

 

그러다보면, public으로 객체의 상태를 변화하는거에 익숙한 메소드, 인터페이스를 만들게 되어,

상태의 노출이 심해지는 사태를 가리키고자 함축한 한 줄인 것 같다.

 

ㄴ. 객체가 고립된 섬이 된다 

 

상태만 넣어두면 슈퍼객체가 된다!!

 

객체 지향을 따르면 여러 객체가 나눠가져야 하는 상태를 한 객체에 몰아넣는다!!

public class Alice{
	int age;
}

public class Juice{
	int amount;
}

원래는 Alice와 Juice가 분리되어, Alice가 Juice를 마시면, method를 이용해서 Juice의 상태 값을 바꾸는게 올바른 설계라면

public class Alice{
	int age;
	int juiceAmout;
}

 Alice 객체 안에 juice의 양을 적어두는 설계를 이야기 하는 것 같다!!

 

찬인 ) 앨리스의 어깨? 팔?에 주스 달려있는거 같은 느낌인데??ㅋㅋㅋㅋ

6. 은유와 객체

6-1. 통념

'객체 지향이란 현실 세계의 모방'

현실세계의 추상화 : 자신이 원하는 특성만 취한다.

현실을 간추리고 요약하여 모방한다.

 

6-2. 의인화 - anthropomorphism

SW 객체 : 추가적인 능력! 현실보다 더 많은 일 가능

 

6-3. 은유 - metaphor

현실 객체 특징 ⊂ SW 객체 특징

프로그램 객체는 현실 객체의 은유이다.

  1. 표현적 차이 (representation gab)
  2. 의미적 차이 (senmantic gab)

차이 = SW 생각하는 모습, 실제 SW 표현의 차이

은유 관계의 실체 객체이름을 SW 객체 이름으로 사용하라 > 표현적 차이 ↓ > 이해 good > 유지보수 good

 

깔끔하게 현실세계 무시하라! = 나만의 새로운 SW 세계 창조하기

 

의인화랑 은유랑 왜 분리한건지 혼돈이었는데

결국은 같은 말을 하고싶었던거겠지 하고 수긍했다ㅋㅋ

댓글

Comments

Develop/Web

web & server - DSC Ewha 세션 | web & server - DSC Ewha Session

DSC Ewha에서 진행하는 미니 세미나에서 개발을 처음시작하는 멤버분들께 요청 응답 구조 및 서버의 개념을 설명했습니다.그리고 제가 알고있는 개발 프레임워크 및 스택들에 대해서 간단히 설명을 드렸습니다. Web and server from 민정 김At a mini seminar held by DSC Ewha, I explained the concept of request-response architecture and servers to members who were just getting started with development.I also gave a brief overview of the development frameworks and stacks that I'm familiar with...

web & server - DSC Ewha 세션 | web & server - DSC Ewha Session

728x90

DSC Ewha에서 진행하는 미니 세미나에서 개발을 처음시작하는 멤버분들께 요청 응답 구조 및 서버의 개념을 설명했습니다.

그리고 제가 알고있는 개발 프레임워크 및 스택들에 대해서 간단히 설명을 드렸습니다.

 

At a mini seminar held by DSC Ewha, I explained the concept of request-response architecture and servers to members who were just getting started with development.

I also gave a brief overview of the development frameworks and stacks that I'm familiar with.

 

댓글

Comments

Develop/git-github

git & github cooperation - DSC Ewha 세션 | git & github cooperation - DSC Ewha Session

마지막 미니세미나로 git과 github를 이용해서 협업을 하는 방법을 설명했습니다.제가 처음 협업을 시작할 때, github의 PR을 이용한 방법으로 개념을 잡기 시작했어서, 그 방법을 소개 드렸습니다. Git cooperation from 민정 김 For the last mini seminar, I explained how to collaborate using git and GitHub.When I first started collaborating, I learned the concepts through the method of using GitHub PRs, so that's the approach I introduced. Git cooperation from 민정 김

git & github cooperation - DSC Ewha 세션 | git & github cooperation - DSC Ewha Session

728x90

마지막 미니세미나로 git과 github를 이용해서 협업을 하는 방법을 설명했습니다.

제가 처음 협업을 시작할 때, github의 PR을 이용한 방법으로 개념을 잡기 시작했어서, 그 방법을 소개 드렸습니다.

 

 

For the last mini seminar, I explained how to collaborate using git and GitHub.

When I first started collaborating, I learned the concepts through the method of using GitHub PRs, so that's the approach I introduced.

 

 

댓글

Comments

Develop/git-github

git & github basic - DSC Ewha 세션 | git & github basic - DSC Ewha Session

DSC Ewha에서 진행하는 미니세미나에서 세번의 발표를 하게되었습니다.두번째 세션으로 git과 github의 기초개념과 가장 메인이되는 커맨드를 알려드렸습니다 :) Git basic from 민정 김 I gave three presentations at the mini seminar held by DSC Ewha.In the second session, I covered the basic concepts of git and github, along with the most essential commands :) Git basic from 민정 김

git & github basic - DSC Ewha 세션 | git & github basic - DSC Ewha Session

728x90

DSC Ewha에서 진행하는 미니세미나에서 세번의 발표를 하게되었습니다.
두번째 세션으로 git과 github의 기초개념과 가장 메인이되는 커맨드를 알려드렸습니다 :)

 

 

I gave three presentations at the mini seminar held by DSC Ewha.
In the second session, I covered the basic concepts of git and github, along with the most essential commands :)

 

 

댓글

Comments

Algorithm

백준 알고리즘 9095 1, 2, 3 더하기 | Baekjoon Algorithm 9095: Adding 1, 2, 3

​​문제를 보자마자 0-1 knapsack 문제랑 비슷한 것 같아 DP. 문제 이겠구나 싶어서규칙을. 찾으려고 저렇게 끄적였습니다ㅋㅋㅋ 처음 저 숫자들의 합만 봤을 때는 뭔가 생각이 반복이 되고 더하기가 반복이 되면서. 뭔가 했는데전부 다 더한 값을 보니 점화식이 보이더군요역시 DP 문제는 규칙만 찾으면 코드는 짧은 것 같습니다.https://www.acmicpc.net/problem/9095 9095번: 1, 2, 3 더하기문제 정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다. 1+1+1+1 1+1+2 1+2+1 2+1+1 2+2 1+3 3+1 정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램..

백준 알고리즘 9095 1, 2, 3 더하기 | Baekjoon Algorithm 9095: Adding 1, 2, 3

728x90

​​문제를 보자마자 0-1 knapsack 문제랑 비슷한 것 같아 DP. 문제 이겠구나 싶어서
규칙을. 찾으려고 저렇게 끄적였습니다ㅋㅋㅋ

 

처음 저 숫자들의 합만 봤을 때는 뭔가 생각이 반복이 되고 더하기가 반복이 되면서. 뭔가 했는데
전부 다 더한 값을 보니 점화식이 보이더군요

역시 DP 문제는 규칙만 찾으면 코드는 짧은 것 같습니다.

https://www.acmicpc.net/problem/9095

 

9095번: 1, 2, 3 더하기

문제 정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다. 1+1+1+1 1+1+2 1+2+1 2+1+1 2+2 1+3 3+1 정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오. 입력 첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다. 출력 각

www.acmicpc.net


근데 진짜로 왜 이런 규칙이 생기는지는 아무리 생각해도 모르겠더군요
아직 DP 문제 알고리즘은 많이 안풀어봐서 그런지 어려운 것 같습니다

그래서 같이 스터디 하시는 분들께 여쭤봤습니다! 그러고 바로 해결!!

 



먼저
N이 1, 2, 3인 경우에는 기본 값으로 주어집니다.
변수 n을 표현 할 수 있는 경우의 수A(n)라 하면

[기본 값]

A(1) = 1
A(2) = 2
A(3) = 4

[N=4 일 때]

1 + 3을. 표현 할 수 있는 경우의 수 [=A(3)]
2 + 2를 표현 할 수 있는 경우의 수 [=A(2)]
3 + 1을 표현 할 수 있는 경우의 수 [=A(1)]

즉 A(4) = A(3) + A(2) + A(1) = 4 + 2 + 1 = 7입니다.

각각의 경우를 자세히 보면

1 + A(3) 2 + A(2) 3 + A(1)
1 + 3
1 + 1 + 2
1 + 2 + 1
1 + 1+ 1+ 1
2 + 2
2 + 1 + 1
3 + 1


마찬가지로

[N=5 일 때]

1 + 4를. 표현 할 수 있는 경우의 수 [=A(4)]
2 + 3을 표현 할 수 있는 경우의 수 [=A(3)]
3 + 2를 표현 할 수 있는 경우의 수 [=A(2)]

각각의 경우를 자세히 보면

1 + A(4) 2 + A(3) 3 + A(2)
1 + 1 + 3
1 + 1 + 1 + 2
1 + 1 + 2 + 1
1 + 1 + 1+ 1+ 1
1 + 2 + 2
1 + 2 + 1 + 1
1 + 3 + 1
2 + 3
2 + 1 + 2
2 + 2 + 1
2 + 1+ 1+ 1
3 + 2
3 + 1 + 1


이렇게 A(5)가 A(4) + A(3) + A(2) = 7+4+2 = 13 이 됩니다.


이전에 만든 수에서 1,2,3을 각각을 더했을 때 현재의 수가 나오므로
1을 더했을 때, 2를 더했을 때, 3을 더했을 때 현재의 수가 나오는. 각각의 이전 조합의 경우의 수를 더하면 됩니다.

만약 이 문제가 1,2,3,4 더하기 였다면
A(n)을 구하기 위해선
1 + A(n-1)
2+ A(n-2)
3 + A(n-3)
4 + A(n-4)
일 때가 A(n)이 나오는 경우의 수일 것입니다.

같은 맥락으로 이 문제가 1,2 더하기 였다면
A(n)을 구하기 위해선
1 + A(n-1)
2+ A(n-2)
일 때가 A(n)이 나오는 경우의 수겠죠?

https://github.com/mjung1798/algorithm_Java

 

mjung1798/algorithm_JAVA

JAVA algorithm study. Contribute to mjung1798/algorithm_JAVA development by creating an account on GitHub.

github.com

 

package com.jyami.baekjoon;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main_9095 {
    public static void main(String args[]) {

        Scanner s = new Scanner(System.in);

        int num = s.nextInt();
        int max = 0;
        List<Integer> list = new ArrayList<>();


        for (int i = 0; i < num; i++) {
            int input = s.nextInt();
            list.add(input);
            if (input > max)
                max = input;
        }

        int dp[] = new int[max + 1];

        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;

        for (int i = 4; i <= max; i++) {
            dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
        }

        for (Integer integer : list) {
            System.out.println(dp[integer]);
        }

    }
}

​​As soon as I saw the problem, it looked similar to the 0-1 knapsack problem, so I figured it must be a DP problem
and started scribbling like that to find the pattern lol

 

At first, when I was just looking at the sums of those numbers, it felt like something was repeating with additions going on, but I couldn't quite figure it out.
Then when I looked at the total sum of everything, the recurrence relation became clear.

As expected, with DP problems, once you find the pattern, the code ends up being pretty short.

https://www.acmicpc.net/problem/9095

 

Problem 9095: Adding 1, 2, 3

Problem: There are a total of 7 ways to represent the integer 4 as a sum of 1, 2, and 3. At least one number must be used in the sum. 1+1+1+1 1+1+2 1+2+1 2+1+1 2+2 1+3 3+1 Given an integer n, write a program to find the number of ways to represent n as a sum of 1, 2, and 3. Input: The first line contains the number of test cases T. Each test case consists of a single line with an integer n. n is a positive integer less than 11. Output:

www.acmicpc.net


But honestly, no matter how much I thought about it, I couldn't figure out why this pattern occurs.
I guess DP problems still feel tough since I haven't solved that many yet.

So I asked the people in my study group! And it was solved right away!!

 



First,
the cases where N is 1, 2, or 3 are given as base values.
If we let A(n) be the number of ways to represent the variable n:

[Base Values]

A(1) = 1
A(2) = 2
A(3) = 4

[When N=4]

Number of ways to represent 1 + 3 [=A(3)]
Number of ways to represent 2 + 2 [=A(2)]
Number of ways to represent 3 + 1 [=A(1)]

So A(4) = A(3) + A(2) + A(1) = 4 + 2 + 1 = 7.

Looking at each case in detail:

1 + A(3) 2 + A(2) 3 + A(1)
1 + 3
1 + 1 + 2
1 + 2 + 1
1 + 1+ 1+ 1
2 + 2
2 + 1 + 1
3 + 1


Similarly,

[When N=5]

Number of ways to represent 1 + 4 [=A(4)]
Number of ways to represent 2 + 3 [=A(3)]
Number of ways to represent 3 + 2 [=A(2)]

Looking at each case in detail:

1 + A(4) 2 + A(3) 3 + A(2)
1 + 1 + 3
1 + 1 + 1 + 2
1 + 1 + 2 + 1
1 + 1 + 1+ 1+ 1
1 + 2 + 2
1 + 2 + 1 + 1
1 + 3 + 1
2 + 3
2 + 1 + 2
2 + 2 + 1
2 + 1+ 1+ 1
3 + 2
3 + 1 + 1


So A(5) becomes A(4) + A(3) + A(2) = 7+4+2 = 13.


Since adding 1, 2, or 3 to a previously formed number gives us the current number,
we just need to add up the number of previous combinations that result in the current number when adding 1, when adding 2, and when adding 3.

If this problem were "Adding 1, 2, 3, 4" instead,
to find A(n), we would need:
1 + A(n-1)
2+ A(n-2)
3 + A(n-3)
4 + A(n-4)
— these would be the cases that produce A(n).

By the same logic, if this problem were "Adding 1, 2",
to find A(n), we would need:
1 + A(n-1)
2+ A(n-2)
— these would be the cases that produce A(n), right?

https://github.com/mjung1798/algorithm_Java

 

mjung1798/algorithm_JAVA

JAVA algorithm study. Contribute to mjung1798/algorithm_JAVA development by creating an account on GitHub.

github.com

 

package com.jyami.baekjoon;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main_9095 {
    public static void main(String args[]) {

        Scanner s = new Scanner(System.in);

        int num = s.nextInt();
        int max = 0;
        List<Integer> list = new ArrayList<>();


        for (int i = 0; i < num; i++) {
            int input = s.nextInt();
            list.add(input);
            if (input > max)
                max = input;
        }

        int dp[] = new int[max + 1];

        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;

        for (int i = 4; i <= max; i++) {
            dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
        }

        for (Integer integer : list) {
            System.out.println(dp[integer]);
        }

    }
}

댓글

Comments

Dev Book Review

[객체지향의 사실과 오해] 1장 : 협력하는 객체들의 공동체

제가 하고있는 스터디에서 개발 서적을 읽기로 결정을해서 매주 정해진 양을 읽고, 서로 이해안되는 점, 신기한 점을 공유하기로 했습니다 :) 저는 이렇게 책을 읽고 저의 생각, 헷갈렸던 점을 다른 박스로 구분하려합니다! 0. 개요 0-1. 객체지향 프로그래밍 객체지향 프로그래밍 = 현실 속에 존재하는 사물을 SW 내부로 옮겨온다. SW : 실세계의 투영 객체 : 사물에 대한 추상화 [모방한 것] : 직접 대응되는 사물로 생각하지만 실제로는 X (철학적 의미로 생각) But, 실제로 연관성은 희미하다 (사상 이해에 효과적으로 사용하는 통념) 객체지향 프로그래밍의 목적 = 새로운 세계의 창조 1. SW 객체의 자율성 (authonomous) : 상태와 행위를 캡슐화(encapsulation) 2. 객체는 협력..

[객체지향의 사실과 오해] 1장 : 협력하는 객체들의 공동체

728x90

 

제가 하고있는 스터디에서 개발 서적을 읽기로 결정을해서

매주 정해진 양을 읽고, 서로 이해안되는 점, 신기한 점을 공유하기로 했습니다 :) 

저는 이렇게 책을 읽고 저의 생각, 헷갈렸던 점을 다른 박스로 구분하려합니다!

0. 개요

0-1. 객체지향 프로그래밍

객체지향 프로그래밍 = 현실 속에 존재하는 사물을 SW 내부로 옮겨온다.

 

SW : 실세계의 투영
객체 : 사물에 대한 추상화

 

[모방한 것] : 직접 대응되는 사물로 생각하지만 실제로는 X (철학적 의미로 생각)

But, 실제로 연관성은 희미하다 (사상 이해에 효과적으로 사용하는 통념)

 

객체지향 프로그래밍의 목적 = 새로운 세계의 창조

 

1. SW 객체의 자율성 (authonomous) : 상태와 행위를 캡슐화(encapsulation)

2. 객체는 협력한다 (collaboration) : 객체끼리 협력이 가능한 수단은 메세지(message)

2. 연결 완전성 (seamlessness) : 실세계 사물을 기반으로 SW 객체를 식별한 후 구현까지 이어나간다.

 

 

상태와 행위

상태는 JAVA의 member 변수
행위는 method를 의미 할 것 같다.

 

1. 협력하는 사람들

협력에 참여하는 모든 사람이 역할에 따른 책임을 완수해야한다.

협력에는 여러 객체들이 참여한다.

 

책임 - 객체가 가지는 모든 메서드

역할 - 협력 내에서 쓰이는 책임이 역할

 

협력 (collaboration) = 요청 (request) +응답 (response)

  • 요청이 요청을 연쇄한다 요청 -> 요청 -> 요청
  • 요청과 응답은 서로 반대 방향으로 이루어진다.
  • 역할이 있으면 책임도 주어진다.

 

1-1. 협력의 특징

  1. 여러사람이 동일 역할을 수행 가능하다
  2. 역할 = 대체가능성 (substitude)
  3. 책임을 수행하는 방법은 자율적으로 선택할 수 있다.
    = 동일 요청에 대해 서로 다른 방식으로 응답이 가능하다 (다형성 : polymorphism)
  4. 한 사람이 동시에 여러 역할을 수행할 수 있다.

2. 역할, 책임, 협력

사람 -> 객체
에이전트의 요청 -> 메시지

에이전트의 요청 처리법 -> 메서드

 

협력 = 특정 책임을 수행하는 역할들 간의 연쇄적인 요청과 응답으로 목표를 달성하는 것

목표 = 어플리케이션의 기능을 구현하는 것

  1. 목표를 책임으로 작게 분할하여 -> 역할을 지닌 객체가 적절하게 처리가능하게 한다.
  2. 적절한 역할을 가진 객체가 처리하게 한다.

기능 = 객체들간의 요청 응답을 주고받는 것

2-1. 객체의 역할

  1. 여러 객체가 동일 역할을 수행
  2. 역할 = 대체 가능성
  3. 각 객체는 책임 수행할 수 있는 방법을 자율적으로 수행 가능하다
  4. 하나의 객체가 동시에 여러 역할을 수행 가능하다

3. 협력속에 사는 객체

패러다임의 중심 = 객체

3-1. 객체란?

  • 협력에 참여하는 주체
  • 애플리케이션 기능 구현을 위해 존재 : 혼자서 어려우니 다른 객체와 협력

객체지향의 아름다움 = 협력

협력의 조화 = 객체의 품질이 좋다

[3단 논법에 의해!!]

객체지향의 아름다움 = 객체의 품질이 좋다는 것

3-2. 객체의 두가지 덕목

  1. 객체는 '협력적'이어야 한다.
    • 다른 객체에서 오는 요청 응답을 받는다
      god object: 도움을 무시하고 스스로 모든 것을 처리하는 객체 (안좋다)
    • 응답 방식 : 객체가 결정
    • 응답 여부 : 객체가 결정한다.
  2. 객체는 '자율적'이어야 한다.
    • 자신의 행동을 스스로 결정하고 책임진다.
    • 요청에 대해 스스로 판단하고 행동한다
    • ex ) 손님이 캐시어에게, 바리스타한테 어떤 방식으로 전달하라 명령하지 않음

3-3. 객체란

= 상태(state)와 행동(behavior)을 함께 지닌 실체

 

= 내부와 외부의 명확한 구분이 있다.

   내부 : 사적인 부분 스스로 관리
   외부 : 접근 허가된 수단으로만 의사소통 ( what O / How X )

 

= 프로세스의 틀 안에서 자율성을 보장한다

자율성 보장 > 유지보수 용이 > 재사용 용이한 시스템 구축

 

상태와 행동은 자율성의 핵심 이유인 듯

- 행동을 하려면 이 행동을 하는데 필요한 상태가 있어야 하므로

 

내부 : 자율성을 의미하는 것 같다. (private 보장)
외부 : 협력적을 의미하는 것 같다. (public하게 협력)

3-4. 메세지(message)

= 객체들의 유일한 의사소통 수단

 

3-5. 메서드(method)

= 객체가 수신된 메세지를 처리하는 방법

   메세지를 전송하면 메시지에 대응되는 메서드를 실행한다

 

처음에는 메세지가 내가 아는 메서드의 parameter이고 method를 이용해 행동을 처리해서 그 결과가 나오는 것이라고 메세지와 메서드를 이해했다. 아래와 같이!

message를 이용해서 요청이 들어오면 그 요청값을 알맞는 method가 받아서 실행한다고 생각했는데

public void method ( T message ){
   ..method의 수행내용
}

 

스터디원들하고 토론하다보니 메서드의 이름이 메세지라는 결론을 내렸다.

public void message (  ){
   message에 알맞는 행동을 한다 : method
}

우리가 코드를 짤 때, 자바 method의 네이밍을 우리가 알기쉽게 직관적으로 (예를들면 getName())과 같이 명령을 하니 method의 이름 자체가 message라고 판단을 하게 되었다.
또한 해당 method는 들어온 param에 따라 message가 전달한 수행하라는 행동을 자율적으로 결정한다.

 

4. 객체 지향의 본질

  1. 객체 지향 : 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해 시스템을 분별하는 방법
  2. 자율적인 객체 : 상태행위를 함께 지니며 스스로 자기 자신을 책임지는 객체
  3. 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다.
    각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다
  4. 객체는 다른 객체와 협력하기 위해 메시지를 전송하고 메시지를 수신한 객체는 메시지를 처리하는데 적합한 메서드를 자율적으로 선택한다

 

4-1. 객체와 클래스

통념 : 클래스의 중요성 > 객체의 중요성

BUT : 클래스의 중요성 < 객체의 중요성

 

객체지향의 중심인 객체가 우선이 되어야한다!
class는 객체지향의 구성요소일 뿐이다!

 

prototype 기반 객체 지향언어 = javascript

 

  • class 개념 X 인데도 객체 O
  • 상속도 class가 아닌 객체간 위임(delegation) 기반이다.

댓글

Comments