• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

1:N 양방향 매핑시에 컬렉션 필드 초기화가 안되는 문제..

23.08.10 01:04 작성 23.08.10 01:10 수정 조회수 249

0

안녕하세요. 강의 코드를 참고하며 1:N 양방향 매핑을 시도하고 있습니다.

다른 테이블에서는 성공적으로 해서 정말 다른거 없이 똑같이 매핑해서 테스트를 진행중이었는데

정말 이유를 모르겠는게 객체가 생성되면서 멤버 변수가 초기화가 되어야 하는데 되지 않아 null값이 들어가있고.

null 값이 들어간 List에 add를 하려고 하니 NullPointerException이 나고 있습니다.

같은 로직, 같은 OneToMany 매핑을 한 다른 엔티티 에서는 잘 되었는데 해당 부분에서 어떤 이유로 생성될때 멤버변수가 초기화가 되지 않는지 도무지 잘 모르겠습니다.. 이유를 아시는분 도와주시면 정말 감사하겠습니다.

정상적으로 진행된 매핑 및 코드는 아래와 같습니다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
@DiscriminatorValue("my")
@Table(name="my_wallets")
@Getter @Setter
public class MyWallet {

    @Id
    @GeneratedValue
    @Column(name = "wallet_id")
    private Long id;

    private  Integer balance=0;

    //cascade = Persist 속성을 명시해줌으로써, 영속성 전이를 사용하였음.
    //orphanRemoval = true 를 사용해서 고아 객체를 자동으로 제거 함.
    @OneToMany(mappedBy = "wallet", cascade = CascadeType.PERSIST,orphanRemoval = true)
    private List<Transactions> transactions = new ArrayList<>();

    public void addTransactions(Transactions transaction){
        this.transactions.add(transaction);
        transaction.setWallet(this);
    }

-------------------------------------

@Entity
@Table(name = "transactions")
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Transactions {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "transaction_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "wallet_id")
    @Setter
    private MyWallet wallet;

    private Long counterId;
    private Timestamp transactionDate;
    private String transactionType;
    private Integer amount;
    private String location;
    private Integer balance;
    private Boolean success;
}

----------------------테스트 부분
            Transactions t = Transactions.builder()
                    .amount(100)
                    .balance(200)
                    .success(Boolean.TRUE)
                    .build();
            MyWallet m1 = createMyWallet();
            m1.addTransactions(t);
            em.persist(m1);

컴파일 에러도 없고, 정상적으로 h2 db에 들어가는것을 확인 했습니다.

그런데 같은 로직으로 진행한 연관관계 메서드에서 오류가 발생했습니다.

문제가 되는 코드들은 아래와 같습니다.



import lombok.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "article")
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "article_id")
    private Long id;

    private Long walletId;
    private Long imageId;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "transaction_id")
    private Transactions transaction;

    @OneToMany(mappedBy = "article",cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Comments> comments = new ArrayList<>();
    // List<Commnets> comments = new ArrayList 를 해주었음에도 null이 들어가있음.
    public void addComment(Comments comment){
        //따라서 null 에 add 를 하려고 하니 NullPointerException이 발생합니다..
        comments.add(comment);
        comment.setArticle(this);
    }
}

import lombok.*;

import javax.persistence.*;
import java.sql.Timestamp;

@Entity
@Table(name = "comments")
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Setter
public class Comments {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "comment_id")
    private Long commentId;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "article_id")
    @Setter
    private Article article;

    private Long writerId;
    private String content;
    private Timestamp postedDate;
}

 

이 아래는 실행해봤던 테스트 코드중 발생하는 위치의 코드입니다..

            Article article = Article.builder()
                    .imageId(99L)
                    .walletId(1L)
                    .imageId(-99L)
                    .build();
            em.persist(article);
            Comments comment = Comments.builder().content("컨텐츠1").writerId(123L).build();
            System.out.println("comment = " + comment.toString());
            em.persist(comment);
            article.addComment(comment); //이 부분이 문제..

persist를 안해줘서 그런가 싶어서 앞뒤로 옮기며 확인해봤는데 여전히 필드 초기화가 되지 않아서 정말 의문입니다 제가 기본적으로 무엇을 놓치고 있는걸까요 ㅠㅠ

 

테스트 코드에서 무언가 잘못했을까 싶어 전체 코드도 남겨봅니다.



import lombok.RequiredArgsConstructor;
import org.apache.tomcat.jni.Address;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.awt.print.Book;

@Component
@RequiredArgsConstructor
public class initDb {

    private final InitService initService;

    @PostConstruct
    public void init() {
        initService.doInit1();
//        initService.doInit2();
    }

    @Component
    @Transactional
    @RequiredArgsConstructor
    static class InitService {
        private final EntityManager em;

        public void doInit1() {

            Transactions t = Transactions.builder()
                    .amount(100)
                    .balance(200)
                    .success(Boolean.TRUE)
                    .build();
            MyWallet m1 = createMyWallet();
            m1.addTransactions(t);
            em.persist(m1);

            MoimWallet moim = createMoimWallet(1000);
            em.persist(moim);
            System.out.println("2차 테스트");
            moim.setBalance(100);
            //Mywallet으로 업 캐스팅 ok
            MyWallet moim2 = createMoimWallet(2000);

            em.persist(moim2);

            User user = new User(null, m1, 1L, "ansik_id",
                    "1234", "안식_name", "981212-121212", "010111", UserType.Parent, null);
//            user.getUsername("안식씨");

            em.persist(user);

            WalletsAndMember wam = new WalletsAndMember(null, moim, user);
            em.persist(wam);
            WalletsAndMember wam2 = new WalletsAndMember(null, (MoimWallet) moim2, user);
            em.persist(wam2);

            Article article = Article.builder()
                    .imageId(99L)
                    .walletId(1L)
                    .imageId(-99L)
                    .build();
            em.persist(article);
            Comments comment = Comments.builder().content("컨텐츠1").writerId(123L).build();
            System.out.println("comment = " + comment.toString());
            em.persist(comment);
            System.out.println("comment = " + comment.toString());
//            comment.setArticle(article);
            article.addComment(comment);
//            article.getComments().add(comment);
            em.persist(article);
            em.flush();
            em.clear();


        }

        private static MyWallet createMyWallet() {
            MyWallet myWallet = new MyWallet();
            return myWallet;
        }

        private static MoimWallet createMoimWallet(int amount) {
            MoimWallet moimWallet = new MoimWallet();
            moimWallet.setTarget_amount(amount);
            return moimWallet;
        }


    }
}

아래에 에러 로그를 남겨봅니다

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-08-10 00:59:32.985 ERROR 47056 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initDb': Invocation of init method failed; nested exception is java.lang.NullPointerException
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:920) ~[spring-context-5.3.28.jar:5.3.28]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.28.jar:5.3.28]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.13.jar:2.7.13]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.13.jar:2.7.13]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.13.jar:2.7.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.13.jar:2.7.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.13.jar:2.7.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.13.jar:2.7.13]
	at team.ServerApplication.main(ServerApplication.java:12) ~[classes/: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.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-2.7.13.jar:2.7.13]
Caused by: java.lang.NullPointerException: null
	at team.entities.Article.addComment(Article.java:33) ~[classes/:na]
	at team.initDb$InitService.doInit1(initDb.java:74) ~[classes/:na]
	at team.initDb$InitService$$FastClassBySpringCGLIB$$48d5c178.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.28.jar:5.3.28]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.28.jar:5.3.28]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.28.jar:5.3.28]
	at team.initDb$InitService$$EnhancerBySpringCGLIB$$3cdef9a7.doInit1(<generated>) ~[classes/:na]
	at team.initDb.init(initDb.java:22) ~[classes/: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.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.28.jar:5.3.28]
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.28.jar:5.3.28]
	... 23 common frames omitted


Process finished with exit code 1

답변 2

·

답변을 작성해보세요.

0

Ansix님의 프로필

Ansix

질문자

2023.08.14

자문 자답을 남겨봅니다.

InitDb 에서 Builder 를 이용해 호출했는데도 불구하고 왜 필드 초기화가 이루어지지 않았을까?

이유는 다음과 같습니다.

@Builder 어노테이션을 이용해서 빌더 패턴으로 객체를 만들때 필드 초기화는 기본적으로 수행되지 않습니다.

따라서 해당 부분을 해결하기 위해서는 필드 초기화 부분에 @Builder.Default를 입력해야 했습니다.

@Builder.Default
@OneToMany(mappedBy = "article",cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Comments> comments = new ArrayList<>();
//List<Commnets> comments = new ArrayList<>() 필드 초기화를 했으나
//NPE(Nuill Pointer Exception)이 발생함. 디버깅 해보니 초기화가 안되어서 null 상태임

이렇게 해주게 되면 Builder 어노테이션을 이용해서 객체를 만들때 필드 초기화도 정상적으로 수행되는것으로 보입니다!

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2023.08.12

안녕하세요, 인프런 AI 인턴이에요. 1:N 양방향 매핑에서 컬렉션 필드 초기화가 안 되는 문제에 대해 도움을 드리겠습니다.

코드를 보면 MyWallet 엔티티에는 OneToMany로 매핑된 필드가 없는 것 같은데요. OneToMany 매핑이 된 엔티티에서 멤버 변수가 초기화되지 않아 null이 들어가고, NullPointerException이 발생하는 것 같습니다.

혹시 OneToMany 매핑된 필드를 MyWallet 엔티티에 추가해주셔야 할 것 같습니다. 해당 필드에 대해 초기화를 할 수 있도록 코드를 수정해보세요. 초기화 방법은 필드를 생성자 또는 필드 선언 시 초기화하는 등 다양한 방법이 있을 수 있습니다.

또한, OneToMany 매핑 시에는 FetchType.LAZY 옵션을 사용하여 연관된 엔티티를 지연 로딩하실 수 있습니다. 이렇게 하면 필요할 때 DB에서 조회하여 데이터를 가져올 수 있습니다.

위 내용을 참고하여 코드를 수정해보시고, 여전히 문제가 있다면 구체적인 코드와 스택 트레이스 등을 제공해주시면 더 자세한 도움을 드릴 수 있을 것 같아요.