#5 북마크 개발

안떽왕·2023년 10월 24일
0

seamless 프로젝트

목록 보기
5/5

본격적인 작성에 들어가기 전에 데이터베이스에 bookmarks라는 테이블을 만들어 주었습니다.
해당 테이블의 필드는 3개로 id, user_id, center_id입니다.

spring에서는 관계형 데이터베이스처럼 다른 entity와 연결되어 있지만 실제 데이터베이스는 외래키로 연결되어 있지 않게 설정했습니다.

데이터베이스를 외래키로 연결하면 각 테이블이 연결되어 안정성은 증가하지만 그만큼 제약이 생기기 때문에 실제 데이터베이스는 연결되어 있지 않고

spring내에 entity들은 연결된 데이터베이스처럼 작동하길 바랬기에 각각의 필드에 해당 데이터의 entity를 설정해 주었습니다.

1. Repository 작성

@Repository
public interface BookmarkRepository extends JpaRepository<BookmarkEntity, Long> {
    List<BookmarkEntity> findByUser(UserEntity userEntity);
    Optional<BookmarkEntity> findByUserAndCenter(UserEntity userEntity, CenterEntity centerEntity);
}

북마크 레포지토리를 작성했습니다.

유저정보를 가져올 findByUser메서드와 센터정보도 함께 가져올 findByUserAndCenter메서드를 작성했습니다.

2. Entity 작성

@Entity
@Table(name = "bookmarks")
public class BookmarkEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "userId")
    private UserEntity user;

    @ManyToOne
    @JoinColumn(name = "centerId")
    private CenterEntity center;
    
    // getter setter 메서드 생략 ..
}

spring이 entity라는 것을 알 수 있게 @Entity를 달아주고 @Table어노테이션을 이용해 bookmarks테이블을 찾아가게 만들었습니다.

@ManyToOne어노테이션을 달아 유저나 센터가 여러 북마크를 가질 수 있고 북마크 하나는 하나의 유저와 센터를 갖게 설정해줬습니다.

@JoinColumn어노테이션을 이용해 spring에서 필드명은 user와 center 이지만 데이터베이스에서 userIdcenterId를 찾아가게 설정해주었습니다.

3. DTO 작성

dto는 request, response 2개를 만들었습니다.

public class BookmarkRequestDto {
    @NotNull
    private Long id;
    @NotNull
    private Long userId;
    @NotNull
    private Long centerId;
    
    // getter setter 메서드 생략..
}

BookmarkRequestDto는 북마크 추가 기능에 사용될 dto로 실제 데이터베이스와 동일한 구성을 가지고 있습니다.

public class BookmarkResponseDto {
    private Long id;
    private String name;
    private String address;
    private String email;
    private String tel;
    private String homepage;
    private Double mapx;
    private Double mapy;
    
    //getter setter 메서드 생략..
}

BookmarkResponseDto는 사용자에게 북마크한 센터의 정보를 제공할때 사용됩니다.

4. Service 작성

의존성 주입

@Service
public class BookmarkService {

    private final BookmarkRepository bookmarkRepository;
    private final UserRepository userRepository;
    private final CenterRepository centerRepository;

    @Autowired
    public BookmarkService(BookmarkRepository bookmarkRepository, UserRepository userRepository, CenterRepository centerRepository) {
        this.bookmarkRepository = bookmarkRepository;
        this.userRepository = userRepository;
        this.centerRepository = centerRepository;
    }
}

Service에 사용될 각 레포지토리의 의존성을 주입했습니다.

유효성 검사 메서드

	// 북마크 추가 전 유저 존재 확인
    private UserEntity validateExistingUser(Long userId) {
        return userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다."));
    }

    // 북마크 추가 전 센터 존재 확인
    private CenterEntity validateExistingCenter(Long centerId) {
        return centerRepository.findById(centerId)
                .orElseThrow(() -> new IllegalArgumentException("센터를 찾을 수 없습니다."));
    }

    // 이미 등록된 북마크인지 확인
    private void validateExistingBookmark(UserEntity user, CenterEntity center) {
        if (bookmarkRepository.findByUserAndCenter(user, center).isPresent()) {
            throw new IllegalArgumentException("이미 등록된 북마크입니다.");
        }
    }

북마크 추가, 조회, 삭제 메서드에 사용할 유효성 검사 메서드를 작성했습니다.

들어온 유저정보를 유저테이블과 센터테이블에서 조회해 각각 유효성을 검증하고 해당 데이터를 반환하게 작성했습니다.

북마크는 중복으로 생성할 수 없으므로 요청받은 유저와 센터가 이미 등록된 북마크라면 이미 등록된 북마크라는 메세지와 함께 에러를 내보냈습니다.

IllegalArgumentException에러를 내보내 예외처리를 했고 if문 대신 orElseThrow를 사용해 가독성을 높혔습니다.

데이터 변환 메서드

    private BookmarkResponseDto convertToDto(BookmarkEntity bookmarkEntity) {
        BookmarkResponseDto dto = new BookmarkResponseDto();

        CenterEntity center = bookmarkEntity.getCenter();

        dto.setId(center.getId());
        dto.setName(center.getName());
        dto.setAddress(center.getAddress());
        dto.setEmail(center.getEmail());
        dto.setTel(center.getTel());
        dto.setHomepage(center.getHomepage());
        dto.setMapx(center.getMapx());
        dto.setMapy(center.getMapy());

        return dto;
    }

유저의 북마크로 등록된 센터 정보를 가져오는 메서드에서 사용될 메서드로 BookmarkEntity객체의 내용을 가지고 센터의 데이터를 가지고와 BookmarkResponseDto객체인 dto에 넣어 반환해줍니다.

북마크 추가 메서드

    public BookmarkEntity addBookmark(BookmarkRequestDto bookmarkRequestDto) {
        UserEntity userEntity = validateExistingUser(bookmarkRequestDto.getUserId());
        CenterEntity centerEntity = validateExistingCenter(bookmarkRequestDto.getCenterId());
        validateExistingBookmark(userEntity, centerEntity);

        BookmarkEntity bookmarkEntity = new BookmarkEntity();
        bookmarkEntity.setUser(userEntity);
        bookmarkEntity.setCenter(centerEntity);

        return bookmarkRepository.save(bookmarkEntity);
    }

북마크의 entity는 유저와 센터의 entity로 필드 구성이 되어있기에 dto에서 받은 정보를 유저와 센터 entity 객체로 변환해줄 필요성이 있습니다.

위에서 작성한 유저와 센터 검증 메서드에 각각의 객체로 변환하여 반환하는 내용을 작성했기에 객체 타입에 맞는 검증 메서드를 사용하여 해당 객체를 얻을 수 있습니다.

이후 이미 등록된 북마크인지 확인하는 검증 메서드를 사용해 통과하면 북마크를 생성합니다.

북마크 조회 메서드

북마크의 필드는 유저id와 센터id밖에 없기에 프론트에서 요구하는 내용과는 거리가 있습니다.
프론트에서 받고자하는 내용은 북마크에 담긴 센터들의 정보이기에 센터정보를 반환하는 코드를 작성했습니다.

    public List<BookmarkResponseDto> getBookmarksByUserId(Long userId) {
        UserEntity user = validateExistingUser(userId);
        List<BookmarkEntity> bookmarkEntities = bookmarkRepository.findByUser(user);

        return bookmarkEntities.stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

이 메서드는 BookmarkResponseDto의 리스트를 반환하는 메서드입니다.

북마크를 조회하기 전 해당 유저가 존재하는지 유효성 검증을 해주고 통과한다면 BookmarkEntity리스트를 가지는 bookmarkEntities변수에 해당 유저의 북마크를 찾아 받아옵니다.

이후 bookmarkEntities변수를 가지고 stream을 생성하는데 stream은 컬렉션에 저장된 요소를 하나씩 참조해 람다식으로 처리할 수 있게 해주는 메서드입니다.

map을 이용해 convertToDto메서드를 하나씩 적용해 BookmarkEntity객체를 BookmarkResponseDto로 변환했습니다.

::는 메서드 참조를 나타내는 기호로 일반 람다식을 사용한다면
.map(entity -> this.convertToDto(entity)) 이렇게 표현했을 것 같습니다.

이후 생성된 stream을 회수해 BookmarkResponseDto의 리스트 형태로 반환해줍니다.

북마크 삭제 메서드

    public ResponseEntity<String> deleteBookmark(Long userId, Long centerId) {
        UserEntity user = validateExistingUser(userId);
        CenterEntity center = validateExistingCenter(centerId);

        Optional<BookmarkEntity> optionalBookmark = bookmarkRepository.findByUserAndCenter(user, center);

        if (optionalBookmark.isEmpty()) {
            throw new IllegalArgumentException("등록되지 않은 북마크입니다.");
        }

        bookmarkRepository.delete(optionalBookmark.get());
        return ResponseEntity.ok("북마크를 삭제했습니다.");
    }

유효성 검증 메서드를 사용해 유저와 센터의 존재 유무를 확인하고 레포지토리에 있는 findByUserAndCenter메서드를 사용해 요청받은 유저와 센터 정보롤 북마크를 찾습니다.

북마크를 발견하지 못했을 때 해당 북마크는 존재하지 않는다고 메세지를 보내고 존재할경우 북마크를 삭제하고 메세지를 보냈습니다.

5. Controller 작성

의존성 주입

@RestController
@RequestMapping("/bookmarks")
public class BookmarkController {

    private final BookmarkService bookmarkService;

    @Autowired
    public BookmarkController(BookmarkService bookmarkService) {
        this.bookmarkService = bookmarkService;
    }
}

어노테이션을 사용해 해당 클래스가 컨트롤러라는 것을 인지시키고, 주소 맵핑을 해주었습니다.

요청받은 정보를 Service로 넘기기위해 BookmarkService클래스 의존성을 주입해주었습니다.

메서드

    @PostMapping
    public ResponseEntity<String> addBookmark(@RequestBody BookmarkRequestDto bookmarkRequestDto) {
        bookmarkService.addBookmark(bookmarkRequestDto);
        return new ResponseEntity<>("북마크를 추가했습니다.", HttpStatus.CREATED);
    }

    @GetMapping("/{userId}")
    public ResponseEntity<List<BookmarkResponseDto>> getBookmarksByUserId(@PathVariable Long userId) {
        List<BookmarkResponseDto> bookmarks = bookmarkService.getBookmarksByUserId(userId);
        return ResponseEntity.ok(bookmarks);
    }

    @DeleteMapping("/{userId}/{centerId}")
    public ResponseEntity<String> deleteBookmark(@PathVariable Long userId, @PathVariable Long centerId) {
        String message = String.valueOf(bookmarkService.deleteBookmark(userId, centerId));
        return ResponseEntity.ok(message);
    }

북마크를 추가하는 post 요청의 경우 body에 객체를 담아 전달하고 get, delete 요청의 경우 url에 정보를 담아 처리했습니다.

profile
이제 막 개발 배우는 코린이

0개의 댓글