인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

룰루랄라님의 프로필 이미지
룰루랄라

작성한 질문수

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

연관관계 편의 메서드 관련 질문입니다.

작성

·

406

1

안녕하세요. 컬렉션을 초기화 해주어도 NPE가 발생하고 제가 생각한 방향으로 되지 않아 질문드립니다.
저는 여러 권한을 가질 수 있는 관리자를 만들고 있습니다.
관리자 정보가 들어가는 Account테이블
관리자 권한이 들어가는 AccountAdminRole테이블(insert ... values (1, 'ARTICLE_READ),(2,'ARTICLE_CREATE)) ... 이런식으로 들어가 있습니다.
이 두 사이에 테이블을 두어 관리하는데 이를 AccountAdminGrant라 하고 있습니다.
Account와 AccountAdminGrant는 AccountAdminGrant를 주인으로 하는 manytoone 양방향 맵핑
AccountAdminGrant와 AccountAdminRole은 AccountAdminGrant를 주인으로 하는 manytoOne 단방향 맵핑을 하려고 합니다.
Account class {
 @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<AccountAdminGrant> accountAdminGrants = new HashSet<>();
}
AccountAdminGrant {
@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "account_id")
    private Account account;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "account_admin_role_id")
    private AccountAdminRole accountAdminRole;

    public void changeAccount(Account account) {
        this.account = account;
        account.getAccountAdminGrants().add(this);
    }
}

AccountAdminGrant에 연관관계 편의 메서드를 만든 뒤 서비스 레이어에서 관리자를 저장할 때

 @Transactional
    public void createAdmin(AdminDto.Save dto) {
        // account 생성 accountRole 가져와서 accountGrant에 둘다 넣고 생성해서 저장
        Account account = Account.builder()
                ...
                .build();

        accountRepository.save(account);

        Set<AccountAdminRole> adminRoles = aarRepository.findByRoleIn(dto.getRoles());

        for (AccountAdminRole role : adminRoles) {
            AccountAdminGrant accountAdminGrant = AccountAdminGrant.builder()
                    .accountAdminRole(role)
                    .build();
            accountAdminGrant.changeAccount(account);
            aagRepository.save(accountAdminGrant);
//            System.out.println("zzz="+accountAdminGrant.getAccountAdminRole().getRole()); USER_READ
        }

Account 객체를 생성해 저장한 뒤

AccountAdminRole에서 사용자가 입력한 권한 객체들을 가져와 for를 돌리며

AccountAdminGrant객체를 만들며 그 안에서 편의 메서드를 호출해 주고 있습니다.

문제는 이때 자꾸 연관관계 편의 메서드의 account.getAccountAdminGrant().add(this) 에서 NPE가 발생합니다. 30번째 줄이 그곳입니다.

2022-03-14 10:35:41.228 ERROR 4584 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
	at kr.solmap.accounts.domain.AccountAdminGrant.changeAccount(AccountAdminGrant.java:30) ~[main/:na]
	at kr.solmap.accounts.service.JoinService.createAdmin(JoinService.java:153) ~[main/:na]
	at kr.solmap.accounts.service.JoinService$$FastClassBySpringCGLIB$$40fcca25.invoke(<generated>) ~[main/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.16.jar:5.3.16]
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.16.jar:5.3.16]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.16.jar:5.3.16]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.16.jar:5.3.16]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.16.jar:5.3.16]
	at kr.solmap.accounts.service.JoinService$$EnhancerBySpringCGLIB$$4420dd86.createAdmin(<generated>) ~[main/:na]
	at kr.solmap.accounts.web.AdAdminController.settingsAdminNew(AdAdminController.java:67) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.58.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.16.jar:5.3.16]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.58.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:219) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:132) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.6.2.jar:5.6.2]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.16.jar:5.3.16]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.16.jar:5.3.16]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

 

ㄹ여기서 더 추가적으로 해줄 수 있는 설정이 있다면 무엇일까요? tostring exclude 와 @EquealAndHashCode를 해주어도 같은 에러가 납니다.

 

그리고 두번째로 궁금한것은 그래서 저 에러가 발생한 부분을 주석한 후 돌리면 각 테이블에 잘 저장도 되고

코드 중 계정 정보를 보여주는 화면에서 account.getAccountAdminGrants() 로 해당 계정이 가지고 잇는 권한을 찍어보면 테이블에 있는 것을 잘 가져옵니다... 저는 account에 설정을 해주지 않았는데 왜 권한을 가져오는 건지 이해가 잘 되지 않습니다.

스프링이...다 해주는 것인가요?

 

 

또하다보니 질문이 생겨 남깁니다. 세번째 질문은

제가 연관관계의 주인을 AccountAdminGrant로 잡고 양방향 맵핑을 했습니다.

Account의 업데이트를 작성하는데 서비스 레이어에서 아래 같은 코드를 작성했습니다.

if (CollectionUtils.isEmpty(dto.getRoles())) {
           account.getAccountAdminGrants().clear();
        }

dto로 roles를 set으로 클라이언트에서 받아왔을 때 비어있다면 clear 해주겠다 했는데 주인이 아닌쪽은 읽기만 가능하다고 하셨는데

이렇게 작성하니 db에서 삭제가 됩니다. 읽기와 삭제는 가능한것인가요? 데이터의 생성과 변경(삭제아닌 변경)만 주인이 가능한 것인가요?

Account의 casecade = CasecadeType.All 을 한번 삭제하고 해보니 삭제가 안되긴 합니다. 그럼
JPA는 기본적으로 양방향 주인으로 정한쪽은(데이터 생성, 변경, 삭제)는 가능하고 주인이 아닌쪽은 읽기만 가능한데 CASECADE설정을 해주면 이것이 무시되고 이루어지는 걸까요?

분명 이해를 했다고 생각하고 코드를 짜본건데 제 생각대로 되지않네요ㅋㅋ큐큐ㅠ큐ㅠ

저와 같은 고민을 하시고 해결하신 분 계시다면.. 알려주세용

답변 2

2

두번째 질문에 대해서 답을 드리자면 

https://joanne.tistory.com/220

 

링크로 올려드린 위 글을 참고해주시면 될 듯 합니다. 

 

그리고 첫번째 질문의 경우에는 소스를 직접 봐야 알 수 있을 듯 싶은데,

어디에선가 초기화가 제대로 이루어지지 않아서 null 값으로 설정되었을 것으로 추측됩니다. 

(Builder 패턴을 사용해서 생성하신 것으로 보여지는데, 지금까지 주어진 코드만으로는 자세한 확인이 어렵네요.) 

구글 드라이브를 통해서 소스를 올려주시거나 

혹은 

changeAccount() 함수 에서 

if (account.getAccountAdminGrants() == null ) { // 이것이 null 인지 테스트
    System.out.println("error!!") ; 
}else{
    account.getAccountAdminGrants().add(this); 
}

이런 식으로 확인해보시면 좋지 않을까 싶습니다. 

 

제 답변이 잘못되었다면 영한님이나 서포터즈분들께서 추가적으로 답변을 달아주셨으면 합니다. 

감사합니다. 

김영한님의 프로필 이미지
김영한
지식공유자

나르비님 답변 감사합니다^^

2

첫번째 두번째 질문에는 답을 찾지 못했지만 세번째 질문에 대해서는 다행스럽게도 

답을 드릴 수 있을 것 같습니다. 

 

룰루랄라님과 비슷한 고민을 하신 분이 올리신 질문글 링크입니다. 

영한님의 답변 내용을 그대로 붙여넣자면 이러합니다! 

안녕하세요 bk님

명확하게 답변을 드리자면, 마지막에 말씀해주신 부분이 맞습니다.

cascade는 mappedBy, 양방향 등등과 전혀 관계가 없습니다.

복잡하게 다른 것과 엮어서 고민하지 않으셔도 됩니다^^

단순하게 A -> B 관계가 cascade로 되어 있으면 A엔티티를 PERSIST할 때 B 엔티티도 연쇄해서 함께 PERSIST 해버린다고 이해하시면 됩니다.

감사합니다.

https://www.inflearn.com/questions/15855

 

1,2번도 제가 답을 찾게 된다면 올려드릴게요! ㅠㅠ 

룰루랄라님의 프로필 이미지
룰루랄라
질문자

나르비님 감사합니다! 

룰루랄라님의 프로필 이미지
룰루랄라

작성한 질문수

질문하기