이번 단계는 본격적으로 계층 별로 코드 작성하기!
아래 표대로 domain, repository, service, controller부터 구현한다.

1. Domain
그 전 글에서 데이터베이스 설계한 대로 Domain 패키지 안에서 엔티티 코드를 작성한다.
JPA를 사용하기 때문에 애노테이션만 달아주면 JPA가 자동으로 객체와 실제 데이터베이스 테이블을 연결시켜 준다.
덕분에 개발자는 쿼리를 따로 작성하지 않고 객체 중심 개발을 할 수 있다. (ORM 기술)
User.java
package toyProj.myDiary.domain;
import ...
@Entity //jpa 켜야 사용 가능
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) //기본 생성자 자동 생성, 다른 곳(서비스)에서 new 생성 불가
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //자동 증가++
private Long id; //PK
//로그인 ID: null 불가, 중복 불가, 길이 50
@Column(nullable = false, unique = true, length = 50)
private String loginId;
//비밀번호: null 불가 (이후 Spring Security BCrypt 사용)
@Column(nullable = false)
private String password;
//닉네임: null 불가, 길이 50
@Column(nullable = false, length = 50)
private String nickname;
//mappedBy로 Diary 엔티티의 'user' 필드가 주인임을 알림(알림용이라 Column 지정x)
//cascade: User 삭제 시 연관된 Diary도 함께 삭제
//orphanRemoval: 컬렉션에서 제거된 Diary도 DB에서 삭제
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Diary> diaries = new ArrayList<>();
//기본 말고 세팅용 생성자(회원 가입) -> 정적 팩토리 메서드 패턴 사용 권장
public static User create(String loginId, String password, String nickname) {
User user = new User();
user.loginId = loginId;
user.password = password;
user.nickname = nickname;
return user;
}
}
이렇게 코드를 작성한 상태에서 DB 연결 없이 실행시키면 바로 오류가 뜬다.
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
build.gradle에서 JPA 의존성을 추가해 놓고 막상 DB 연결할 URL 정보가 없으니 터지는 오류이다.
그래서 미리 MariaDB를 cmd에서 생성하고 application-local.yml에 등록해 놓으면 괜찮지만, 만약 나중에 등록할 예정이라면 테스트용으로 H2를 넣어놓아도 된다. (아니면 jpa 잠시 비활성화 하든가)
코드에서 @Table(name = "users")로 이름을 붙여줬는데, 처음엔 이거 안 했다가 나중에 H2 데이터베이스로 테스트 코드 실행할 때 오류가 났었다. MariaDB는 관대한 편이나 H2는 'user'가 예약어로 엄격히 정해져 있어서 겹쳐서 오류가 났던 것이다. 따라서 이름을 붙여주는 식으로 수정하였더니 정상적으로 H2에서도 실행될 수 있었다.
정적 팩토리 메서드 패턴은 static 메서드를 통해 외부에서 생성자를 간접적으로 호출하도록 하는 디자인 패턴이다.
우선 @NoArgsConstructor(access = AccessLevel.PROTECTED)를 이용해서 기본 생성자는 자동으로 생성하되 외부에선 접근하지 못하도록 한다. 단순히 new로 호출할 때보다 이렇게 호출하게 되면 create란 이름으로 그 목적을 확실히 알 수 있어 코드 가독성이 좋아진다.
또한 객체 생성을 캡슐화하여 데이터를 은닉할 수 있다. 프로젝트에서 DTO를 사용하다 보니 DTO와 엔티티 간에 형 변환이 자유롭게 이루어져야 하는데, 이 패턴을 이용하면 외부에서 생성자 내부 구현을 전부 모르더라도 쉽게 변환이 가능하다.
Diary.java
package toyProj.myDiary.domain;
import ...
@Entity
@Getter @Setter
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED) //기본 생성자 자동 생성
public class Diary {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//글 제목: null 불가
@Column(nullable = false)
private String title;
//글 내용: null 불가, TEXT 타입으로 긴 내용 저장 가능
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@CreatedDate //최초 저장 시 자동으로 현재 시간 세팅
@Column(updatable = false) //이후 변경 불가
private LocalDateTime createdAt;
@LastModifiedDate //수정 시 현재 시간 갱신
@Column //변경 가능
private LocalDateTime updatedAt;
@Column(nullable = false)
private LocalDate diaryDate; //날짜 (캘린더 구분용)
//연관 관계의 주인: Diary가 user_id를 FK로 관리
//LAZY: 일기 조회 시 User 정보를 즉시 불러오지 않음
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
//생성자
public static Diary create(String title, String content, LocalDate diaryDate, User user) {
Diary diary = new Diary();
diary.title = title;
diary.content = content;
diary.diaryDate = diaryDate;
diary.user = user;
return diary;
}
//수정 메서드 (제목, 내용만 변경 가능)
public void update(String title, String content) {
this.title = title;
this.content = content;
//수정 시간은 @LastModifiedDate가 자동으로 갱신함
}
}
여기에서도 마찬가지로 기본 생성자는 @NoArgsConstructor으로 자동 생성하되, create하는 건 정적 팩토리 메서드 패턴으로 관리하여 외부에서 안전하고 쉽게 사용할 수 있게 작성한다. 또한 글은 수정 기능을 제공해야 하므로 수정 메서드도 이곳에 같이 작성한다.
시간 관련해서는 애노테이션이 자동으로 세팅해주도록 한다.
이때 @CreatedDate, @LastModifiedDate 동작을 위해서 Spring Boot 메인에 Auditing을 활성화해야 한다.
package toyProj.myDiary;
import ...
@SpringBootApplication
@EnableJpaAuditing //@CreatedDate, @LastModifiedDate 동작을 위해 추가
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. Repository
JPA는 형식만 맞으면 이름을 지어서 해도 괜찮다. 따로 SQL 쿼리를 작성할 필요 없이 JPA가 알아서 처리해 준다.
1) 함수명부터 작성: find, exist 등
2) 그러면 관련 함수 타입 자동 완성 (Id (PK)쪽으로 자동 완성해주는 거 선택)
3) 필요한 변수명으로 바꾸고 override 삭제, 인자도 변경
(기존 형식에 없는 건 따로 SQL 쿼리 작성해야 함)
위 방식을 이용해서, 이후 service에서 사용하게 될 (기능 구현에 필요한데 DB를 이용해야 하는) 메서드를 작성해 준다.
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
//로그인 시 아이디로 사용자 조회
Optional<User> findByLoginId(String loginId);
//회원가입 시 아이디 중복 체크
boolean existsByLoginId(String loginId);
}
DiaryRepository.java
public interface DiaryRepository extends JpaRepository<Diary, Long> {
//메인 화면 캘린더에서 일기가 존재하는 날짜에만 녹색 표시할 때 사용
//특정 사용자의 일기가 존재하는 날짜 목록
@Query("SELECT DISTINCT d.diaryDate
FROM Diary d
WHERE d.user.id = :userId
AND YEAR(d.diaryDate) = :year
AND MONTH(d.diaryDate) = :month")
List<LocalDate> findDiaryDatesByUserAndYearMonth(
@Param("userId") Long userId,
@Param("year") int year,
@Param("month") int month
);
//메인 화면에서 캘린더 날짜 클릭 시, 해당 날짜에 전체 글 조회할 때 사용
//특정 사용자의 특정 날짜 일기 목록 (최신순 정렬)
List<Diary> findByUserIdAndDiaryDateOrderByCreatedAtDescIdDesc(Long userId, LocalDate diaryDate);
}
★ Spring Data JPA의 메서드 이름 규칙 (JPA가 해석 가능한 이름)
findBy + 조건 + And + 조건 + OrderBy + 정렬조건
위처럼 작성하면 JPA가 아래와 같은 SQL로 해석한다.
SELECT *
FROM diary
WHERE user_id = ? AND diary_date = ?
ORDER BY created_at DESC, id DESC
반면 첫번째 메서드는 JPA가 해석을 못하기 때문에 직접 쿼리로 작성해야 한다.
-JPA가 못하는 것들
1) DISTINCT: 중복 제거 (한 날짜에 여러 글이 저장되면 중복되니까, 하나만 추출하려면 중복 제거해야 함)
2) YEAR(), MONTH() 같은 함수 사용
3) 특정 필드만 조회 (diaryDate만)
+메서드 이름으로 가능하면 자동 생성 쓰는 게 좋지만, 이런 경우는 @Query 사용하는 게 좋음
1) 집계 (COUNT, SUM)
2) 함수 (YEAR, DATE_FORMAT 등)
3) JOIN 복잡
4) DTO 바로 조회
5) DISTINCT 필요
따라서 이건 직접 쿼리로 작성한다.
3. Service
처음엔 API 관련 기능 구현부터 먼저 하고, 이후에 Sprnig Security/JWT 코드를 작성한 다음에 service에도 적용시켜 수정하였다.
기본적으로는 @Transactional(readOnly = true)를 이용하여 읽기 전용 트랜잭션으로 관리한다. 읽기 전용 트랜잭션은 DB에 스냅샷을 만들지 않아서 성능 최적화가 될 뿐만 아니라 안전하게 작업할 수 있어 사용했다.
쓰기 작업이 필요한 메서드에만 @Transactional을 달아서 오버라이드하면 쓰기 모드로 바꿀 수 있다.
UserService에서는 설계했던 요구사항에 따라 아이디 중복, 비밀번호 4자리 이상 관련하여 검사하는 로직을 구현한다.
여기에서 User 생성자를 직접 호출하지 않고 정적 팩토리 메서드를 이용하여 간접적으로 호출한다.
코드에 주석처리 된 부분은 security 적용하기 전에 임시로 작성한 부분이다.
UserService.java
@Service
@RequiredArgsConstructor //final 필드 생성자 자동 생성 (DI)
@Transactional(readOnly = true) //기본은 읽기 전용 트랜잭션 (성능 최적화)
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; //SpringConfig에서 Bean 등록함
private final JwtTokenProvider jwtTokenProvider;
/*
[회원가입]
아이디 중복 체크 후 User 엔티티 저장
비밀번호 암호화는 추후 BCryptPasswordEncoder 적용
@Transactional <- 오버라이드하면 read에서 write 됨
*/
@Transactional
public void join(UserJoinRequest request) {
//1. 아이디 중복 검사
if (userRepository.existsByLoginId(request.getLoginId())) {
throw new IllegalArgumentException("이미 사용 중인 아이디입니다.");
}
//2. 비밀번호 4자리 이상인지 검사
if (request.getPassword().length() < 4) {
throw new IllegalArgumentException("비밀번호는 4자리 이상이어야 합니다.");
}
//3. 비밀번호 암호화 (Spring Security 적용 시 passwordEncoder.encode() 사용)
//BCrypt로 비밀번호 암호화 후 저장 -> DB에는 "$2a$10$..." 형태의 해시값이 저장됨
String encodedPassword = passwordEncoder.encode(request.getPassword());
User user = User.create(
request.getLoginId(),
//request.getPassword(), //암호화 전 임시
encodedPassword,
request.getNickname()
);
//4. DB에 엔티티 저장
userRepository.save(user);
}
/*
[로그인]
아이디/비밀번호 검증 후 응답 반환
*/
public UserLoginResponse login(UserLoginRequest request) {
//1. 아이디 검증
User user = userRepository.findByLoginId(request.getLoginId())
.orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호가 틀렸습니다."));
//2. 비밀번호 검증
// if (!user.getPassword().equals(request.getPassword())) {
// throw new IllegalArgumentException("아이디 또는 비밀번호가 틀렸습니다.");
// }
//matches(입력된 평문, DB의 해시값) -> 내부적으로 BCrypt 비교
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new IllegalArgumentException("아이디 또는 비밀번호가 틀렸습니다.");
}
//검증 통과 -> JWT 토큰 발급
String token = jwtTokenProvider.createToken(user.getId());
//3. 성공 시 응답 반환 (메인 화면에서 닉네임 사용해야 해서)
return new UserLoginResponse(user.getId(), user.getNickname(), token);
}
}
-BCrypt matches()
passwordEncoder.encode("12345678") 처럼 값을 넣었을 때, 매번 다른 해시 값을 만든다.
하지만 passwordEncoder.matches("12345678", 해시 값)으로 하는 순간, 내부적으로 salt를 분리해서 비교하기 때문에 항상 올바르게 검증된다. (직접 == 비교는 할 수 없다.)
-인증/인가 방식으로 구현하니 예전에 회원가입/로그인을 구현할 때와 차이점
회원가입 때: 비밀번호를 암호화해서 DB에 저장
로그인 때: 암호화된 값으로 검증, 통과화면 JWT 토큰을 발급받아서 응답으로 반환 -> 갖고 있으면서 이후에도 다른 페이지 들어갈 때마다 계속 검증함
DiaryService.java
API 명세서 대로 각 기능을 구현하면 다음과 같이 된다.
1) 캘린더 월별로 일기 등록한 날짜 조회: 해당 월에 일기가 있는 날짜 목록 반환 -> 프론트에서 녹색으로 표시
Repository에서 구현했던 메서드를 이용하여 날짜 목록을 List에 담고, DTO에 담아서 반환한다.
public DiaryCalendarResponse getCalendarDates(Long userId, int year, int month) {
List<LocalDate> dates = diaryRepository
.findDiaryDatesByUserAndYearMonth(userId, year, month);
return new DiaryCalendarResponse(dates);
}
//DiaryCalendarResponse.java
@Getter
@AllArgsConstructor
public class DiaryCalendarResponse {
private List<LocalDate> datesWithDiary;
}
2) 해당 날짜 별 일기 전체 목록 조회
public List<DiaryListResponse> getDiariesByDate(Long userId, LocalDate date) {
return diaryRepository
.findByUserIdAndDiaryDateOrderByCreatedAtDescIdDesc(userId, date)
.stream()
.map(DiaryListResponse::from) //엔티티 -> DTO 변환
.collect(Collectors.toList());
}
//DiaryListResponse.java
@Getter
public class DiaryListResponse {
private Long id;
private String title;
private LocalDateTime createdAt;
//엔티티를 직접 외부에 노출하지 않고 DTO로 변환
public static DiaryListResponse from(Diary diary) {
DiaryListResponse dto = new DiaryListResponse();
dto.id = diary.getId();
dto.title = diary.getTitle();
dto.createdAt = diary.getCreatedAt();
return dto;
}
}
첫번째 메서드는 처음부터 List로 담아 DTO로 변환했지만, 이건 각각 DTO로 변환한 다음에 DTO들을 List로 담아서 반환한다.
그 이유는, 첫 번째 메서드는 LocalDate만 조회하기 때문에(레포지토리에서 직접 쿼리 작성한 그거) 엔티티가 아니라서 변환할 게 없기 때문이다. 여기에서 DTO는 그냥 데이터를 감싸는 역할만 한다.
반면 두 번째 메서드는 Diary 엔티티를 레포지토리에서 갖고 오기 때문에, 엔티티를 컨트롤러에 그대로 반환하면 안 되니까 DTO로 변환이 필요한 경우이다.
따라서 DB에서 Diary 엔티티 리스트를 갖고 온 다음, DTO로 하나씩 변환하고 List로 묶어서 반환한다.
★ 엔티티를 그대로 반환하면 안 되는 이유
-엔티티 구조 노출됨
-양방향 연관관계 시 무한 JSON 루프 발생할 수 있음
-필요 없는 데이터까지 전송, 오버헤드, 보안 위험(비밀번호가 포함되면 보안 문제)
-추후 API 스펙 변경 어려움
=> DTO로 변환해서 사용!
+) 나머지 메서드들
//일기 상세 조회 (본인 것만 조회 가능)
public DiaryDetailResponse getDiary(Long userId, Long diaryId) {
Diary diary = findDiaryWithOwnerCheck(userId, diaryId);
return DiaryDetailResponse.from(diary);
}
//일기 작성
//userId는 JWT 인증 후 SecurityContext에서 꺼내옴
@Transactional
public Long create(Long userId, DiaryCreateRequest request) {
//1. 사용자 검증
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));
//2. 일기 생성
Diary diary = Diary.create(
request.getTitle(),
request.getContent(),
request.getDiaryDate(),
user
);
//3. DB에 엔티티 저장하고 일기 id 받아서 컨트롤러에 넘김
return diaryRepository.save(diary).getId();
}
//일기 수정
//Dirty Checking: 트랜잭션 안에서 엔티티 수정 시,
//별도 save() 호출 없이 자동으로 UPDATE 쿼리 실행
@Transactional
public void update(Long userId, Long diaryId, DiaryUpdateRequest request) {
Diary diary = findDiaryWithOwnerCheck(userId, diaryId);
diary.update(request.getTitle(), request.getContent());
//save() 호출 불필요 -> Dirty Checking이 처리
}
//일기 삭제
@Transactional
public void delete(Long userId, Long diaryId) {
Diary diary = findDiaryWithOwnerCheck(userId, diaryId);
diaryRepository.delete(diary);
}
//공통 내부 메서드: 일기 조회 + 사용자 검증 (다른 사용자의 일기 접근 방지)
private Diary findDiaryWithOwnerCheck(Long userId, Long diaryId) {
Diary diary = diaryRepository.findById(diaryId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다."));
if (!diary.getUser().getId().equals(userId)) {
throw new IllegalArgumentException("본인의 글만 접근할 수 있습니다.");
}
return diary;
}
-Dirty Checking
update() 메서드에서 save()를 호출하지 않아도 된다. @Transactional 안에서 JPA가 엔티티 변경을 감지해서, 트랜잭션 커밋 시 자동으로 UPDATE 쿼리를 날려주기 때문이다.
4. Controller
[전체 인증 흐름 정리]
1. 회원가입
POST /api/users/join
{ "loginId": "hong", "password": "1234", "nickname": "홍길동" }
→ 비밀번호 BCrypt 암호화 후 DB 저장
2. 로그인
POST /api/users/login
{ "loginId": "hong", "password": "1234" }
→ 응답: { "userId": 1, "nickname": "홍길동", "accessToken": "eyJhbGc..." }
3. 이후 모든 API 요청
Header: Authorization: Bearer eyJhbGc...
→ JwtAuthenticationFilter가 토큰 검증
→ SecurityContext에 userId=1 저장
→ Controller에서 @AuthenticationPrincipal → Long userId 로 꺼내 씀
4. 토큰 없이 /api/diaries 접근 시
→ 403 Forbidden (SecurityConfig의 anyRequest().authenticated() 에 막힘)
@AuthenticationPrincipal 동작 이유:
필터에서 new UsernamePasswordAuthenticationToken(userId, ...)로 저장할 때마다
첫번째 인자가 principal인데, 이 값을 @AuthenticationPrincipal가 그대로 꺼내줌
UserController.java
package toyProj.myDiary.controller;
import ...
/*
[사용자 관련 API]
POST /api/users/join : 회원가입
POST /api/users/login : 로그인
*/
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor //final 필드 생성자 자동 생성 (DI)
public class UserController {
//서비스 로직 사용
private final UserService userService;
//회원가입
@PostMapping("/join")
public ResponseEntity<Map<String, String>> join(@RequestBody UserJoinRequest request) {
userService.join(request); //dto로 담아서 전달하고 dto로 받음
return ResponseEntity.ok(Map.of("message", "회원가입이 완료되었습니다."));
//return ResponseEntity.ok("회원가입이 완료되었습니다.");
}
//로그인: JWT 적용 후, 응답 바디에 acessToken 포함
@PostMapping("/login")
public ResponseEntity<UserLoginResponse> login(@RequestBody UserLoginRequest request) {
UserLoginResponse response = userService.login(request); //dto로 담아서 전달하고 dto로 받음
return ResponseEntity.ok(response);
}
}
처음엔 주석 처리한 코드처럼 단순 문자열로 응답을 반환했으나, 이후 프론트엔드에서 json 파싱 오류나서 json처럼 map으로 담아 보내도록 수정했다.
DiaryController.java
API 설계대로 구현한다.
처음에는 userId를 쿼리 파라미터로 받는 식으로 구현했다가, 이후 JWT 적용 후 securityContext에 저장된 userId를 애노테이션이 자동으로 꺼내는 방식으로 수정했다.
API에서 ? 뒤는 쿼리 파라미터이므로 주소로 매핑하지 않고 @RequestParam으로 받는다. 그 외에 글의 고유 id가 필요한 {id}부분은 주소로 매핑하고 @PathVariable로 설정한다.
request dto를 받을 때는 @RequestBody로 받는다. (json)
package toyProj.myDiary.controller;
import ...
/*
[일기 관련 API (RESTful API 설계)]
캘린더 월별로 일기 등록한 날짜 조회
GET /api/diaries/calendar?userId=1&year=2026&month=3
해당 날짜 별 일기 전체 목록 조회
GET /api/diaries?userId=1&date=2026-03-17
일기 상세 조회
GET /api/diaries/{id}?userId=1
일기 작성
POST /api/diaries?userId=1
일기 수정
PUT /api/diaries/{id}?userId=1
일기 삭제
DELETE /api/diaries/{id}?userId=1
!) userId를 쿼리 파라미터로 받는 건 임시 구조
-> JWT 적용 후, SecurityContext에서 자동으로 꺼내 쓸 예정
-> @AuthenticationPrincipal: JwtAuthenticationFilter에서 SecurityContext에 저장한
UsernamePasswordAuthenticationToken의 principal (= userId Long값) 을 꺼냄
*/
@RestController
@RequestMapping("/api/diaries")
@RequiredArgsConstructor //final 필드 생성자 자동 생성 (DI)
public class DiaryController {
//서비스 로직 사용
private final DiaryService diaryService;
//캘린더 월별로 일기 등록한 날짜 조회
@GetMapping("/calendar")
public ResponseEntity<DiaryCalendarResponse> getCalendar(
//@RequestParam Long userId,
@AuthenticationPrincipal Long userId, //userId 자동 주입됨
@RequestParam int year,
@RequestParam int month) {
return ResponseEntity.ok(diaryService.getCalendarDates(userId, year, month));
}
//해당 날짜 별 일기 전체 목록 조회
@GetMapping
public ResponseEntity<List<DiaryListResponse>> getDiariesByDate(
//@RequestParam Long userId,
@AuthenticationPrincipal Long userId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
return ResponseEntity.ok(diaryService.getDiariesByDate(userId, date));
}
//일기 상세 조회
@GetMapping("/{id}")
public ResponseEntity<DiaryDetailResponse> getDiary(
@PathVariable Long id,
//@RequestParam Long userId
@AuthenticationPrincipal Long userId) {
return ResponseEntity.ok(diaryService.getDiary(userId, id));
}
//일기 작성
@PostMapping
public ResponseEntity<Long> create(
//@RequestParam Long userId,
@AuthenticationPrincipal Long userId,
@RequestBody DiaryCreateRequest request) {
Long diaryId = diaryService.create(userId, request);
return ResponseEntity.ok(diaryId);
}
//일기 수정
@PutMapping("/{id}")
public ResponseEntity<Map<String, String>> update(
@PathVariable Long id,
//@RequestParam Long userId,
@AuthenticationPrincipal Long userId,
@RequestBody DiaryUpdateRequest request) {
diaryService.update(userId, id, request);
return ResponseEntity.ok(Map.of("message", "수정되었습니다."));
//return ResponseEntity.ok("수정되었습니다.");
}
//일기 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, String>> delete(
@PathVariable Long id,
//@RequestParam Long userId
@AuthenticationPrincipal Long userId) {
diaryService.delete(userId, id);
return ResponseEntity.ok(Map.of("message", "삭제되었습니다."));
//return ResponseEntity.ok("삭제되었습니다.");
}
}
Spring Security + JWT 정리하려고 쓴 건데 또 길어져서 이건 다음 글로...
'🌟Project > myDiary - 일기 기록 웹' 카테고리의 다른 글
| [토이프로젝트 개발일지 01] 일기 기록 웹 설계 (0) | 2026.03.19 |
|---|