DevLog ๐Ÿ˜ถ

[QueryDSL] QueryDSL ์‚ฌ์šฉ ์‹œ NPE๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๊ฒฐํ•˜๊ธฐ ๋ณธ๋ฌธ

๊ฐœ๋ฐœ์ผ์ง€

[QueryDSL] QueryDSL ์‚ฌ์šฉ ์‹œ NPE๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๊ฒฐํ•˜๊ธฐ

dolmeng2 2023. 8. 14. 14:01

๐ŸŒฑ ๋ฌธ์ œ ์ƒํ™ฉ

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ผ์ด ๋งŽ์•„ QueryDSL์„ ๋„์ž…ํ•˜๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์— ์ƒ‰๋‹ค๋ฅธ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

java.lang.NullPointerException: Cannot read field "id" because "co.kirikiri.domain.goalroom.QGoalRoomMember.goalRoomMember.goalRoom.roadmapContent.roadmap" is null

NPE๋ฅผ ๋ณธ ๊ฑด ์˜ค๋žœ๋งŒ์ด์–ด์„œ ์กฐ๊ธˆ ์‚ฝ์งˆ์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

 


 

๐ŸŒฑ ๋„๋ฉ”์ธ ๊ตฌ์กฐ

์ง€๋‚œ ํฌ์ŠคํŒ…๊ณผ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋„๋ฉ”์ธ ๊ตฌ์กฐ๋Š” ๊ฑฐ์˜ ๋™์ผํ•œ๋ฐ, ์œ„ ๋ฌธ์ œ ์ƒํ™ฉ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ๋„๋ฉ”์ธ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

ํ•˜๋‚˜์˜ ๋กœ๋“œ๋งต ๋ณธ๋ฌธ(RoadmapContent) ์ •๋ณด์— ๋Œ€ํ•ด์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ณจ๋ฃธ(GoalRoom)์ด ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๊ณ , ํ•˜๋‚˜์˜ ๊ณจ๋ฃธ์— ๋Œ€ํ•ด์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ณจ๋ฃธ ๋Œ€๊ธฐ ๋ฉค๋ฒ„(GoalRoomPendingMembers)๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

์œ„ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

@Test
void ์นดํ…Œ๊ณ ๋ฆฌ_์กฐ๊ฑด_์—†์ด_์ฃผ์–ด์ง„_๋กœ๋“œ๋งต_์ด์ „์˜_๋ฐ์ดํ„ฐ๋ฅผ_์ฐธ๊ฐ€์ค‘์ธ_์ธ์›์ด_๋งŽ์€์ˆœ์œผ๋กœ_์กฐํšŒํ•œ๋‹ค() {
    ...

    final Roadmap gameRoadmap1 = ๋…ธ๋“œ_์ •๋ณด๋ฅผ_ํฌํ•จํ•œ_๋กœ๋“œ๋งต์„_์ƒ์„ฑํ•œ๋‹ค("๊ฒŒ์ž„ ๋กœ๋“œ๋งต", creator, gameCategory);
    final Roadmap gameRoadmap2 = ๋…ธ๋“œ_์ •๋ณด๋ฅผ_ํฌํ•จํ•œ_๋กœ๋“œ๋งต์„_์ƒ์„ฑํ•œ๋‹ค("๊ฒŒ์ž„ ๋กœ๋“œ๋งต2", creator, gameCategory);
    final Roadmap travelRoadmap = ๋…ธ๋“œ_์ •๋ณด๋ฅผ_ํฌํ•จํ•œ_๋กœ๋“œ๋งต์„_์ƒ์„ฑํ•œ๋‹ค("์—ฌํ–‰ ๋กœ๋“œ๋งต", creator, travelCategory);
  
    final GoalRoom gameRoadmap1GoalRoom = ๊ณจ๋ฃธ์„_์ƒ์„ฑํ•œ๋‹ค(gameRoadmap1.getContents().getValues().get(0), creator);
    final GoalRoom gameRoadmap2GoalRoom = ๊ณจ๋ฃธ์„_์ƒ์„ฑํ•œ๋‹ค(gameRoadmap2.getContents().getValues().get(0), creator);
    final GoalRoom travelRoadmapGoalRoom = ๊ณจ๋ฃธ์„_์ƒ์„ฑํ•œ๋‹ค(travelRoadmap.getContents().getValues().get(0), creator);

    // gameRoadmap1 : ์ฐธ๊ฐ€์ธ์› 0๋ช…
    // gameRoadmap2 : ์ฐธ๊ฐ€์ธ์› 1๋ช…
    final List<GoalRoomMember> gameRoadmap2GoalRoomMembers = List.of(
            new GoalRoomMember(GoalRoomRole.LEADER, LocalDateTime.now(), gameRoadmap2GoalRoom, creator));
    goalRoomMemberRepository.saveAll(gameRoadmap2GoalRoomMembers);

    // travelRoadmap : ์ฐธ๊ฐ€์ธ์› 2๋ช…
    final List<GoalRoomMember> travelRoadmapGoalRoomMembers = List.of(
            new GoalRoomMember(GoalRoomRole.LEADER, LocalDateTime.now(), travelRoadmapGoalRoom, creator),
            new GoalRoomMember(GoalRoomRole.FOLLOWER, LocalDateTime.now(), travelRoadmapGoalRoom, follower));
    goalRoomMemberRepository.saveAll(travelRoadmapGoalRoomMembers);

    final RoadmapCategory category = null;
    final RoadmapFilterType orderType = RoadmapFilterType.PARTICIPANT_COUNT;

    // when
    final List<Roadmap> firstRoadmapRequest = roadmapRepository.findRoadmapsByCategory(category, orderType,
            null, 2);
    final List<Roadmap> secondRoadmapRequest = roadmapRepository.findRoadmapsByCategory(category, orderType,
            gameRoadmap2.getId(), 10);

    ...
}

์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ๋ณต์žกํ•˜์ง€๋งŒ ๋ณ„๊ฑฐ์—†๋‹ค. ๊ทธ๋ƒฅ ๋กœ๋“œ๋งต์„ ์ƒ์„ฑํ•˜๊ณ , ๋กœ๋“œ๋งต์— ๋Œ€ํ•œ ์ฐธ๊ฐ€์ธ์›์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

 

ํ•ด๋‹น ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„์ด ๋˜์–ด ์žˆ๋‹ค.

@Override
public List<Roadmap> findRoadmapsByCategory(final RoadmapCategory category, final RoadmapFilterType orderType,
                                            final Long lastId, final int pageSize) {

    return selectFrom(roadmap)
            ...
            .where(
                    lessThanLastId(lastId, orderType)
           	)
            .fetch();
}
private BooleanExpression lessThanLastId(final Long lastId, final RoadmapFilterType orderType) {
    ...
    final NumberPath<Long> goalRoomMemberRoadmapId = goalRoomMember.goalRoom.roadmapContent.roadmap.id;
    return participantCountCond(goalRoomMemberRoadmapId.eq(roadmap.id))
            .lt(participantCountCond(goalRoomMemberRoadmapId.eq(lastId)));
}

์ธ์ž๋กœ ๋ฐ›์€ lastId (ํ˜„์žฌ๋Š” ๋กœ๋“œ๋งต์˜ PK ๊ฐ’์ด ๋“ค์–ด์˜ค๊ณ  ์žˆ๋‹ค.)์— ํ•ด๋‹นํ•˜๋Š” ๋กœ๋“œ๋งต์˜ ์ฐธ์—ฌ์ž ์ˆ˜๋ณด๋‹ค ๋” ์ ์€ ์ฐธ์—ฌ์ž ์ˆ˜๋ฅผ ๊ตฌํ•˜๋Š” ๋กœ๋“œ๋งต์„ ๊ตฌํ•˜๋Š” ์ฟผ๋ฆฌ์ด๋‹ค. 


 

๐ŸŒฑ NPE๊ฐ€ ๋ฐœ์ƒํ•  ์ง€์ ์„ ์ฐพ์•„๋ณด๊ธฐ

์ฒ˜์Œ์— ์ด ์˜ค๋ฅ˜๋ฅผ ์ ‘ํ–ˆ์„ ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์ค‘์—์„œ ๋‹จ์ˆœํ•˜๊ฒŒ ์ด ๋ถ€๋ถ„์—๋งŒ ์ง‘์ค‘์„ ํ–ˆ์—ˆ๋‹ค.

co.kirikiri.domain.goalroom.QGoalRoomMember.goalRoomMember.goalRoom.roadmapContent.roadmap is null.

์Œ... roadmap์ด null์ด๊ตฌ๋‚˜

 

๊ทธ๋ž˜์„œ goalRoomMember๋ฅผ ์ €์žฅํ•œ ํ›„์— ๋กœ๋“œ๋งต์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์ œ๋Œ€๋กœ ์ €์žฅ์ด ์•ˆ ๋˜์–ด ์žˆ๋‚˜ ์‹ถ์–ด์„œ findAll๋กœ ์ฐพ์•„๋ณด์•˜๋‹ค.

final List<GoalRoomMember> goalRoomMembers = goalRoomMemberRepository.findAll();
assertThat(goalRoomMembers.get(0).getGoalRoom().getRoadmapContent().getRoadmap().getId()).isNotNull();
assertThat(goalRoomMembers.get(1).getGoalRoom().getRoadmapContent().getRoadmap().getId()).isNotNull();
assertThat(goalRoomMembers.get(2).getGoalRoom().getRoadmapContent().getRoadmap().getId()).isNotNull();

NPE๋‹ˆ๊นŒ ๋‹น์—ฐํžˆ roadmap์˜ id ๊ฐ’์ด Null์ด ๋˜์–ด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๊ฒ ์ง€?๋ผ๊ณ  ์ƒ๊ฐํ•˜์˜€๋‹ค.

ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ๊ฐ€ ๋†€๋ž๊ฒŒ๋„ ์„ฑ๊ณตํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ’ ์ž์ฒด๋Š” ์ œ๋Œ€๋กœ ๋“ค์–ด๊ฐ”๊ฒ ๊ตฌ๋‚˜ ์‹ถ์–ด์„œ ๋‹ค๋ฅธ ๋ฐฉํ–ฅ์„ ํƒ์ƒ‰ํ•ด๋ณด์•˜๋‹ค.

 


 

๐ŸŒฑ QueryDSL์˜ ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„

final NumberPath<Long> goalRoomMemberRoadmapId = goalRoomMember.goalRoom.roadmapContent.roadmap.id;

๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ๋ผ์ธ์€ ์ •ํ™•ํ•˜๊ฒŒ ์œ„ ๋ผ์ธ์ด์—ˆ๋‹ค.

์–ด๋– ํ•œ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๊ณ  ๋ง๊ณ ์˜ ๋ฌธ์ œ๋ฅผ ๋– ๋‚˜์„œ, ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„๋ฅผ ํƒ์ƒ‰ํ•  ๋•Œ ๋ญ”๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์—ด์‹ฌํžˆ ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณธ ๊ฒฐ๊ณผ, ์•„๋ž˜์™€ ๊ฐ™์€ ๊ธ€์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

NullPointerException on QueryDSL where clause

Using Query DSL with hibernate (Spring Data JPA) to build a query like so if( bankId != null ){ query.where( coopMember.personId.bankAccountId.isNotNull().and(

stackoverflow.com

 

๊ทธ๋ฆฌ๊ณ  ๋‹ต๋ณ€ ๋‚ด์šฉ์—์„œ ๋งํ•ด ์ฃผ์‹  ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ•œ ๋ฒˆ ์ฝ์–ด๋ณด์•˜๋‹ค.

By default Querydsl initializes only reference properties of the first two levels.
In cases where longer initialization paths are required, these have to be annotated in the domain types via 
com.mysema.query.annotations.QueryInit annotations.
QueryInit is used on properties where deep initializations are needed.

๊ธฐ๋ณธ์ ์œผ๋กœ QueryDSL์€ ์ดˆ๊ธฐ 2๋‹จ๊ณ„์˜ ๋ ˆ๋ฒจ์— ์žˆ๋Š” ํ”„๋กœํผํ‹ฐ๋งŒ ์ดˆ๊ธฐํ™”ํ•˜๊ฒŒ ๋œ๋‹ค.

๋งŒ์•ฝ ๊ทธ ์ด์ƒ์˜ path๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด @QueryInit ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด์„œ ์ง์ ‘ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

ํ•œ ๋ฒˆ ์šฐ๋ฆฌ์˜ ๋ฌธ์ œ ์ƒํ™ฉ์— ์ ์šฉํ•ด๋ณด์ž.

 


 

๐ŸŒฑ @QueryInit

@QueryInit์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ์ ์œผ๋กœ @Entity ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด ์žˆ๋Š” ํด๋ž˜์Šค์˜ ํ•„๋“œ์— ๋Œ€ํ•ด์„œ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋‚ด๋ถ€ ์ธ์ž๋กœ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹ถ์€ path๋ฅผ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, *์„ ํ†ตํ•ด ์™€์ผ๋“œ์นด๋“œ๋ฅผ ํ†ตํ•ด์„œ๋„ ์ง€์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๋จผ์ €, @QueryInit์„ ์ง€์ •ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ์˜ ํํŒŒ์ผ์„ ๋””๋ฒ„๊น…ํ•ด๋ณด์•˜๋‹ค. (QGoalRoom)

ํ™•์ธํ•ด๋ณด๋ฉด GoalRoom์—์„œ QRoadmapContent๊นŒ์ง€์˜ ๊ฐ’์€ ์ž˜ ๊ฐ€์ ธ์˜ค์ง€๋งŒ, QRoadmap์— ๋Œ€ํ•ด์„œ๋Š” ์ •์˜๊ฐ€ ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ QRoadmap ๊ฐ’์ด null๋กœ ์ •์˜๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํƒ์ƒ‰์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋˜ ๊ฒƒ์ด๋‹ค.

 

public class GoalRoomMember {

   ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "goal_room_id", nullable = false)
    @QueryInit(value = {"roadmapContent.roadmap"})
    protected GoalRoom goalRoom;
}

๊ทธ๋ž˜์„œ, ์œ„์™€ ๊ฐ™์ด GoalRoomMember ์—”ํ‹ฐํ‹ฐ๊ฐ€ GoalRoom ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ •์˜ํ•œ ํ•„๋“œ์— @QueryInit์„ ์ •์˜ํ•ด์ฃผ์—ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด QGoalRoom์ด ์ดˆ๊ธฐํ™”๋  ๋•Œ RoadmapContent์™€ Roadmap ์ •๋ณด๊นŒ์ง€ ํ•จ๊ป˜ ์ดˆ๊ธฐํ™”๊ฐ€ ๊ฐ€๋Šฅํ•  ๊ฒƒ์ด๋‹ค.

์ฐธ๊ณ ๋กœ, ๋‚ด๋ถ€ value์˜ ๊ฐ’์€ goalRoom ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ์‹ค์ œ ํ•„๋“œ๋ช…์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. 

 

์ด๋Ÿฐ ์‹์œผ๋กœ ์ž˜๋ชป๋œ ํ•„๋“œ ๊ฐ’์„ ์ž…๋ ฅํ•˜๊ฒŒ ๋˜๋ฉด ์ปดํŒŒ์ผ ํƒ€์ž„ ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

goalRoom์ด ์‹ค์ œ๋กœ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ํ•„๋“œ๋ช…์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

 

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "goal_room_id", nullable = false)
@QueryInit(value = {"roadmapContent.*"})
protected GoalRoom goalRoom;

์ถ”๊ฐ€์ ์œผ๋กœ, ์ด๋ ‡๊ฒŒ ์™€์ผ๋“œ์นด๋“œ๋กœ ๋ช…์‹œํ•ด๋„ ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

 

์ˆ˜์ • ํ›„์— ๋‹ค์‹œ ๋””๋ฒ„๊น…์„ ์ง„ํ–‰ํ•ด๋ณด๋ฉด ์ด๋ฒˆ์—๋Š” roadmap์— ๋Œ€ํ•œ ์ •๋ณด๋„ ์ž˜ ์ฑ„์›Œ์ง„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ๋„ ์„ฑ๊ณต!

 


 

+) ์ถ”๊ฐ€์ ์œผ๋กœ, ์ด๋ ‡๊ฒŒ @QueryInit์„ ํŠน์ • ํ•„๋“œ์— ์ ์šฉํ•˜๊ณ  ๋‚˜๋ฉด ๊ธฐ์กด์— ์ž˜ ๋Œ์•„๊ฐ€๋˜ ์ฟผ๋ฆฌ๋“ค์ด ์•ˆ ๋Œ์•„๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค.

๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๋น„์Šทํ•œ ๋‹ค๋ฅธ ์ฟผ๋ฆฌ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ๋Š”๋ฐ, 2๋‹จ๊ณ„ ๋ ˆ๋ฒจ์— ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•ด์„œ NPE๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ๋‹ค.

๋น„์Šทํ•œ ์˜ค๋ฅ˜์ง€๋งŒ goalRoomMember -> member -> identifier (VO์—ฌ์„œ ๋‹จ๊ณ„์— ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ์ฒ˜์Œ์—๋Š” 3๋‹จ๊ณ„๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ ๊ณต์‹๋ฌธ์„œ์—์„œ 2๋‹จ๊ณ„๋ผ๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ๋ช…์‹œํ–ˆ์œผ๋‹ˆ๊นŒ...)์— ๋Œ€ํ•ด์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด์—ˆ๋‹ค.

@QueryInit์„ ๋‹ฌ์ง€ ์•Š์•˜์„ ๋•Œ๋Š” 3๋‹จ๊ณ„๊นŒ์ง€ ์ž˜ ๊ฐ”์—ˆ๋Š”๋ฐ, ๋ช…์‹œ์ ์œผ๋กœ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ง€์ •ํ•ด์ฃผ๋‹ˆ๊นŒ ์ด๋ ‡๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™๋‹ค.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "goal_room_id", nullable = false)
@QueryInit(value = {"roadmapContent.*"})
protected GoalRoom goalRoom;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
@QueryInit(value = {"identifier"})
protected Member member;

๊ทธ๋ž˜์„œ member ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋Š” ์ด๋ ‡๊ฒŒ identifier์— ๋Œ€ํ•ด์„œ๋„ ์ •์˜๋ฅผ ํ•ด์ฃผ์—ˆ๋‹ค.

 

 

์‚ฌ์‹ค ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์•„์ฃผ ๊ฐ„๋‹จํ–ˆ๋Š”๋ฐ ์ฒ˜์Œ ๋งˆ์ฃผํ•œ ์˜ค๋ฅ˜์—ฌ์„œ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค...!

Comments