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

javabase님의 프로필 이미지

작성한 질문수

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]

안녕하세요. 강의 후 개인적으로 학습 시 나타나는 NPE관련 질문드립니다.

해결된 질문

작성

·

310

1

안녕하세요! 강의를 완강 후 혼자 프로젝트를 진행하다 도서 대출 코드를 보고 비슷하게 구현한 사용자가 채용공고를 지원하는 메소드를 호출시 테스트 코드에서 NPE가 발생하는데 혹시 이유를 알 수 있을까요? 여러가지 서칭해봐도 해결이 안되서 질문드립니다... ㅠㅠ

회원entity

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "users")
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ApplyHistory> applyHistory = new ArrayList<>();

    public void applyCompany(JobPosting jobPosting) {
        this.applyHistory.add(new ApplyHistory(this, jobPosting));
    }

    @Builder
    private User(Long id, String name, List<ApplyHistory> applyHistory) {
        this.id = id;
        this.name = name;
        this.applyHistory = applyHistory;
    }

}

ApplyHistory entity(JobPosting과 user객체가 N:M 매핑해주는 entity)

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "apply_history")
@Entity
public class ApplyHistory {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "apply_history_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "job_posting_id")
    private JobPosting jobPosting;

    public ApplyHistory(User user, JobPosting jobPosting) {
        this.user = user;
        this.jobPosting = jobPosting;
    }
}

 

JobPosting Entity

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "job_posting")
@Entity
public class JobPosting {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "job_posting_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "company_id")
    private Company company;

    @Column(name = "posting_position")
    private String position;

    private int compensation; //채용보상금

    @Column(name = "posting_details")
    private String postingDetails;

    @Column(name = "technology_used")
    private String technologyUsed;

    @Builder
    private JobPosting(Company company, String position, int compensation, String postingDetails,
        String technologyUsed) {
        this.company = company;
        this.position = position;
        this.compensation = compensation;
        this.postingDetails = postingDetails;
        this.technologyUsed = technologyUsed;
    }

    public void updateJobPosting(String position, int compensation, String postingDetails,
        String technologyUsed) {
        this.position = position;
        this.compensation = compensation;
        this.postingDetails = postingDetails;
        this.technologyUsed = technologyUsed;
    }
}

 

 

applyService

@RequiredArgsConstructor
@Service
public class ApplyService {

    private final JobPostingRepository jobPostingRepository;
    private final UserRepository userRepository;
    private final ApplyHistoryRepository userJobPostingRepository;

    @Transactional
    public void applyCompany(ApplyCompanyRequest request) {

        // 1. 채용공고 정보 찾기
        JobPosting jobPosting = jobPostingRepository.findById(request.getJobPostingId())
            .orElseThrow(() -> new ResourceNotFoundException("jobPosting", request.getJobPostingId()));

        // 2. 유저 정보 가져오기
        User user = userRepository.findById(request.getUserId())
            .orElseThrow(() -> new ResourceNotFoundException("user", request.getUserId()));

        // 3. 지원 유무 확인
        // 3-1. 지원 중이면 예외 발생
        if (userJobPostingRepository.existsByJobPostingAndUser(jobPosting, user)) {
            throw new IllegalArgumentException("이미 지원하신 회사입니다.");
        }

        user.applyCompany(jobPosting);
    }
}

 

리퀘스트

@Getter
@Setter
public class ApplyCompanyRequest {

    private Long jobPostingId;
    private Long userId;
}

 

서비스 테스트 코드

@SpringBootTest
class ApplyServiceTest {

    @Autowired
    JobPostingService jobPostingService;

    @Autowired
    ApplyService applyService;

    @Autowired
    JobPostingRepository jobPostingRepository;

    @Autowired
    UserRepository userRepository;

    @Autowired
    ApplyHistoryRepository applyHistoryRepository;

    @Autowired
    CompanyRepository companyRepository;

    @AfterEach
    void tearDown() {
        applyHistoryRepository.deleteAllInBatch();
        jobPostingRepository.deleteAllInBatch();
        userRepository.deleteAllInBatch();
        companyRepository.deleteAllInBatch();
    }

    @DisplayName("사용자는 채용 공고를 지원 할 수 있다.")
    @Test
    @Transactional
    void applyCompany() {
        //given
        User user = User.builder()
            .id(1L)
            .name("jw")
            .build();

        User savedUser = userRepository.save(user);

        Company company = Company.builder()
            .name("company1")
            .country(Country.KOREA)
            .city(City.SEOUL)
            .build();
        Company savedCompany = companyRepository.save(company);

        JobPosting jobPosting = JobPosting.builder()
            .company(savedCompany)
            .position("백엔드")
            .postingDetails("백엔드 개발자 채용합니다.")
            .compensation(500000)
            .technologyUsed("Java")
            .build();

        JobPosting savedJobPosting = jobPostingRepository.save(jobPosting);

        ApplyCompanyRequest request = new ApplyCompanyRequest();
        request.setUserId(savedUser.getId());
        request.setJobPostingId(savedJobPosting.getId());

        //when
        applyService.applyCompany(request);

        //then


    }

}

 

-> 이부분에서 applyCompany(request) 호출 시 NPE가 발생합니다.

java.lang.NullPointerException
	at com.wanted.findjob.domain.user.User.applyCompany(User.java:36)
	at com.wanted.findjob.api.service.ApplyService.applyCompany(ApplyService.java:39)
	at com.wanted.findjob.api.service.ApplyService$$FastClassBySpringCGLIB$$2f4064b0.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:792)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:707)
	at com.wanted.findjob.api.service.ApplyService$$EnhancerBySpringCGLIB$$81701d47.applyCompany(<generated>)
	at com.wanted.findjob.api.service.ApplyServiceTest.applyCompany(ApplyServiceTest.java:83)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

 

테스트코드가 아닌 직접 서버를 작동해서 api를 호출 시 정상적으로 db에 들어가는 걸 볼 수 있는데 어디가 문제 인지를 모르겠습니다.. ㅠ

답변 2

1

javabase님의 프로필 이미지
javabase
질문자

User Entity에서 Bulider패턴 사용시 List<ApplyHistory> applyHistory 초기화 부분문제 때문에 일어난 거였습니다.. 혹시라도 비슷한 경험을 하시는 분들을 위해 질문은 남겨 놓을게요 감사합니다!

0

최태현님의 프로필 이미지
최태현
지식공유자

안녕하세요 정우님!! 와우~ 해결하셨군요!! 다행입니다~ 😊

 

추가적으로 테스트를 하다가 뭔가 동작이 이상하면

DB의 데이터를 확인해보실 수 있습니다.

 

예를 들어, 테스트 중간에 ApplyHistoryRepository에 접근해보는 것이죠!

 

혹시나 또 어려운 점 있으시면 편하게 질문 남겨주세요.

감사합니다! 🙇

javabase님의 프로필 이미지
javabase
질문자

친절한 답변에 링크 영상까지 정말 감사합니다.!!

javabase님의 프로필 이미지

작성한 질문수

질문하기