CODING/스파르타 내일배움캠프 TIL

일정 관리 앱 Develop 개인과제 관련 트러블슈팅 TIL 모음

codingTrip 2025. 2. 6. 13:27

2025.02.06(목)

필수

Lv 0. API 명세 및 ERD 작성

이 부분은 과제 구현 후 작성하고자 한다.

 

Lv 1. 일정 CRUD

application.properties 설정 주의하기

2025-02-06T16:28:25.400+09:00 INFO 5078 --- [Schedule-Develop] [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] Database driver: undefined/unknown Database version: 9.2 Autocommit mode: undefined/unknown Isolation level: undefined/unknown Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value: com.example.scheduledevelop.domain.entity.Schedule.username] with root cause

 

위와 같은 예외가 발생했다.

package 안에 있는 코드는 강의 실습 코드를 바탕으로 작성했으므로 큰 실수는 없을 것이라고 생각했다.

따라서 아마도 JPA 설정은 처음이라 설정 부분에서 빠진 부분이 있을 것이라고 판단했다.

 

application.properties에 아래와 같은 코드를 추가했더니 잘 작동했다.

spring.application.name=Schedule-Develop

spring.datasource.url=jdbc:mysql://localhost:3306/schedule-develop
spring.datasource.username=계정
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

 

 

일정 생성 후, DB에서 생성일, 수정일 null 발생

일정 생성 후, DB 확인 결과

위와 같이 생성일과 수정일에 null이 되었다.

@EnableJpaAuditing
@SpringBootApplication
public class ScheduleDevelopApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleDevelopApplication.class, args);
    }

}

원인은 ScheduleDevelopApplication 파일에

@EnableJpaAuditing을 하지 않은 것이 문제였다.

 

위와 같이 설정하고 다시 실행하면 아래와 같이 DB에 잘 들어온 것을 확인할 수 있다.

일정 생성 후, DB 확인 결과

 

위와 같은 일을 겪고 나니 JPA 설정에 대해서는 적어도 잊어버리지 않은 것 같다는 생각이 들었다.

 

 

일정 단건 조회 구현 시, 컴파일 에러 발생

@GetMapping("/{scheduleId}")
    public RequestEntity<ScheduleResponseDto> findById(@PathVariable("scheduleId") Long id){

        ScheduleResponseDto scheduleResponseDto = scheduleService.findById(id);

        return new RequestEntity<>(scheduleResponseDto,HttpStatus.OK);
    }

위와 같이 responseEntity가 아닌 requestEntity를 사용해서 발생한 오류였다.

Response를 반환해야 한다고 머리로는 생각했으면서도

손은 그렇게 작성하지 못했고

눈은 그것을 판별하지 못했지만

다행히? 컴파일 에러로 보여서 잘 수정할 수 있었다.

 

Lv 2. 유저 CRUD

오늘 유저 자체의 CRUD는 구현을 완료했으나,

일정은 이제 작성 유저명 필드 대신 유저 고유 식별자 필드를 가집니다.

위의 부분에 대해서

일정 테이블과 유저 테이블의 연관 관계 매핑에 대해서

좀 더 고민해보고 구현할 예정이다.

 


2025.02.07(금)

필수

Lv 2. 유저 CRUD

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "com.example.scheduledevelop.domain.entity.Member.getName()" because the return value of "com.example.scheduledevelop.domain.entity.Schedule.getMember()" is null] with root cause

생성자 주입이나 Setter 주입을 통해 Member 값을 넣어줘야 하는데 그렇게 하지 않아서 발생했다.

public Schedule(String title, String contents, Member member) {
    this.title = title;
    this.contents = contents;
    this.member = member;
}

따라서 위와 같이 Schedule 클래스의 생성자에 member도 포함시키고

Member findMember = memberRepository.findMemberByEmailOrElseThrow(memberEmail);
Schedule schedule = new Schedule(title, contents, member);

Service에서 email로 해당 Member를 찾아온 후,

생성자에 member 값을 세팅해주었다.

 

필수

Lv 4. 로그인(인증)

일정 생성, 수정, 삭제시 로그인 Session 값을 가져오는 방법

로그인 기능 자체는 구현을 완료했다.

하지만 로그인 후, 일정을 생성, 수정, 삭제할 때 해당 세션 값을 가져오는 방법에 대해 고민이 생겼다.

그래서 숙련 Spring 2주차 강의에서 Session 부분을 다시 들으며 복습했다.

그리고 강의에서 해답을 찾을 수 있었다.

 

Spring에서는 Session을 쉽게 다루도록 @SessionAttribute라는 어노테이션이 제공된다고 한다.

사실 다른 방법도 있지만 더 깔끔해 보여서 나는 이 어노테이션을 사용하기로 햇다.

@SessionAttribute
- request.getSession(true); 와는 다르게 Session을 새로 생성하는 기능은 없다.
- 이미 로그인이 완료된 사용자를 찾는 경우 즉, Session이 있는 경우에 사용한다.

이미 AuthController에서 Session을 생성하기 때문에 생성 기능은 필요하지 않았다.

일정 Controller에서는 로그인 후, 즉 Session이 있는 경우에 사용하면 되는 것이었다.

 @PostMapping
    public ResponseEntity<ScheduleResponseDto> save(
        @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
        @RequestBody ScheduleSaveRequestDto requestDto) {

        // 생성 로직
        ScheduleResponseDto scheduleResponseDto
                = scheduleService.save(requestDto.getTitle(), requestDto.getContents(),loginMember);

        return new ResponseEntity<>(scheduleResponseDto, HttpStatus.CREATED);
    }

따라서 Controller에서 해당 Session을 매개변수로 받아서 Member타입의 변수에 저장했다.

그리고 Service에 같이 매개변수로 넣었다.

 

@Transactional
    public ScheduleResponseDto save(String title, String contents, Member member) {

        Schedule schedule = new Schedule(title, contents, member);

        Schedule savedSchedule = scheduleRepository.save(schedule);
        return new ScheduleResponseDto(
                savedSchedule.getId(),
                savedSchedule.getTitle(),
                savedSchedule.getContents(),
                savedSchedule.getMember().getName(),
                savedSchedule.getMember().getEmail()
        );
    }

이를 Service에서 Schedule 생성자를 통해 loginMember 값을 주입할 수 있었다.

 

 

작성자만 일정을 수정, 삭제하게 하는 방법

위에서 매개변수로 받아온 loginMember 변수를 활용하기로 했다.

각각의 id가 동일한지 비교하는 로직을 만들고

같지 않다면 예외처리를 하고,

같다면 수정, 삭제 로직을 수행하도록 할 계획이었다.

 

그러나 위와 같은 생각으로 구현을 하고 Postman을 통해 테스트를 하는데

이상한 점을 발견하게 되었다.

분명 작성자인데 수정, 삭제가 작동하지 않는 것이었다.

System.out.println("findMemberId = " + findMemberId);

위와 같이 print를 통해 각각의 id를 조회하고 나니 원인을 찾을 수 있었다...

memberId와 scheduleId를 비교하고 있으니 둘이 다를 수 밖에...

 

따라서 아래와 같이 findMember라는 새로운 Service 메서드를 만들어서

scheduleId에 해당하는 Member를 찾고 그 Member의 아이디를 찾아서 이를 비교했다.

@PatchMapping("/{scheduleId}")
    public ResponseEntity<Void> updateTitleAndContents(
            @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
            @PathVariable("scheduleId") Long id,
            @RequestBody UpdateTitleAndContentsRequestDto requestDto
    ) {

        Member findMember = scheduleService.findMember(id);
        Long findMemberId = findMember.getId();

        // 작성자만 수정 가능
        if (!loginMember.getId().equals(findMemberId)){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        // 수정 로직
        scheduleService.updateTitleContents(id, requestDto.getTitle(), requestDto.getContents());

        return new ResponseEntity<>(HttpStatus.OK);
    }

    @DeleteMapping("/{scheduleId}")
    public ResponseEntity<Void> delete(
            @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
            @PathVariable("scheduleId") Long id
    ){

        Member findMember = scheduleService.findMember(id);
        Long findMemberId = findMember.getId();

        // 작성자만 삭제 가능
        if (!loginMember.getId().equals(findMemberId)){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        // 삭제 로직
        scheduleService.delete(id);

        return new ResponseEntity<>(HttpStatus.OK);
    }

 

오늘은 계획대로 필수 과제 구현을 모두 끝내게 되어서 뿌듯하다.

다음 주에는 도전과제를 도전할 계획이다.

다음 주도 화이팅이다!