백엔드 14일차 - 스프링부트 : 연관 관계 출력하기

parang·2025년 4월 25일

LG CNS AM Inspire Camp 2기

목록 보기
25/50
post-thumbnail

연관관계

N : 1

@ManyToOne -> 앞쪽에 있는 many가 쓰는 테이블 본인!

player - team


@Entity
public class Player { 
    @Id int playerId; 
    String playerName; 
    
    @ManyToOne -> 외래키가 되는 것.
    @Column(name = "team_id")
    Team team;
}
-> team_id 이름의 속성으로 외래키가 만들어짐.

Junit으로 테스트

이전 포스트에도 간략하게 설명했지만 junit은 단위테스트를 말한다.


@SpringBootTest
class DatabaseTests {
	@Autowired PlayerRepository playerRepository;

	@Test
	void contextLoad() {
		List<Player> list = playerRepository.findAll();
		//반복문
		list.stream().forEach((player) -> {
			System.out.println(player);
		});
	}

반복문을 람다식으로 표현하기 위해 stream()를 사용해봤는데, stream으로 map을 사용하기에 편리하다.
이런식으로, @Test 어노테이션을 붙이면 메소드 별로 테스트가 가능하다.

디버그 콘솔에 보면 실행이 잘 된 것을 볼 수 있다!

보기 좋게 string으로 바꿔주기 위해 tostring object 상속을 받아야 하지만, @Data만 붙이면 알아서 스프링부트가 처리해준다.

단방향 -> 양방향 관계 맺기

@OneToMany(mapperBy = "team")
양방향 관계 맺기위한 코드이다.
상대(player)에서 사용하는 변수 명을 넣어 주어야 한다.
many인 player를 표현하기 위해서 밑에는 list로 player를 받았다.

이제, 위의 테스트와 똑같이 만들어 테스트해보면,

org.hibernate.LazyInitializationException:
일명 lazy 오류가 난다.

그 이유는, 팀은 db에 플레이어 정보가 없기 때문에 조회 전에 플레이어 정보를 가져와야 한다. (db에서 확인 가능, N : 1 관계에서 1 쪽이 문제!)

해결 방법 1. @OneToMany(mappedBy = "team", fetch = FetchType.EAGER)
-> 정보 먼저 조회

해결방법 2. @Transactional (추천)
-> findAll 후, 계속 연결을 유지

해결방법 3. fetch join(jpql)

따라서.. OneToMany는 최대한 사용하지 않는게 마음이 편하다.

주의 ! java.lang.StackOverflowError

-> 양방향 참조 때문에 발생하는 오류
-> 출력할 때 팀에서 플레이어가 들어있고, 팀 전체를 출력하려고 할때 서로가 서로를 부르는 오버플로우 상황 발생

출력

목표 : 내가 출력하고 싶은 데이터만 json을 출력하고 싶음.

Entity를 그냥 출력하려고 할때는 데이터의 모든 것이 출력되어 매우 비효율적이다. 따라서, DTO를 사용해서 데이터를 가공하는 것이다. DTO는 entity의 어노테이션을 다 뺀 내용과 같다. (단, @Data는 있어야함)

Team Entity -> DTO로 변환하여 map으로 받고 출력.

@GetMapping("/team")
public List<TeamDTO> team() {
    
    List<Team> list = teamRepository.findAll();
    List<TeamDTO> dtoList = list.stream().map((team) -> {
      
        TeamDTO dto = new TeamDTO();
        dto.setTeamId(team.getTeamId());
        dto.setTeamName(team.getTeamName());
    
        return dto;
    }).toList();
    return dtoList; 
}


이런 식으로, 내가 원하는 내용한 뽑아서 출력할 수 있다. 실무에서 Entity -> DTO 변환은 거의 필수라고 한다.

이렇게 변환 함으로써, 오버플로우 문제도 같이 해결된다.

player Entity -> DTO 바꿔서 출력.

목표 : 플레이어 이름과, 팀 이름( N : 1 관계인 TEAM의 이름) 가져와서 JSON형식으로 출력


@GetMapping("/player")
public List<PlayerDTO> plyer() {
	List<Player> list = playerRepository.findAll();
    List<PlayerDTO> dtoList = list.stream().map((player) -> {
    	PlayerDTO dto = new PlayerDTO();
        dto.setPlayerName(player.getPlyerName());
        Team t = player.getTeam();
        TeamDTO dto2 = new TeamDTO();
        dto2.setTeamName(t.getTeamName());
        dto.setTeam(dto2);
        
        return dto;
     }).toList();
     return dtoList;
 }

이런식으로 원하는 정보만 선별해서 잘 출력된 것을 알 수 있다.

DTO 코드 리팩토링

요약 : DTO 클래스를 Entity에 포함시켜 따로 빼기

  1. PlayerDTO/TeamDTO 클래스에서 @NoArgsConstructor @AllArgsConstructor 어노테이션 추가
  2. Entity에 코드 추가
public PlayerDTO toDto() {
    TeamDTO teamDto = (team != null) ?
     new TeamDTO(team.getTeamId(), team.getTeamName(), null) : null;
     return new PlayerDTO( playerId, playerName, teamDto);
  }
 
  public PlayerDTO toDtoWithoutTeam() {
    return new PlayerDTO(playerId, playerName, null);
 }
 
 코드 형태가 닟선건 그냥 암기해서 익숙해지기로.. ^^!
  • @JsonInclude(JsonInclude.Include.NON_NULL) : null이 아닌 것만 출력하는 코드

조건 검색(Query Method)

Query Method란? 메소드 이름을 통해 SQL을 생성하는 방식이다.
Repository에 작성한다.

 List<Titanic> findByNameContaining(String name);
 어떤 이름을 포함하는 찾기 sql문

이런식으로, 쭉 나열해서 메소드를 작성하면 된다.



일주일 후기

험난했던 일주일이 끝났다. 완벽하게 모든 개념들을 습득했다고 자신은 못하지만 열심히 흐름을 따라가려고 노력했던 것 같다. 주말에는 푹 쉬면서 배운거를 천천히 복습해야지...

profile
파랑입니다.

0개의 댓글