Formatter를 이용한 LocalDateTime 처리
웹에서 전달되는 데이터는 JSON 형식의 데이터일 수도 있고 첨부파일 등이 포함되는 경우에는 form-data 혹은 일반적인 웹에서 사용하는 x-www-form-urlencoded 일 수도 있습니다.
이러한 처리 과정에서 날짜/시간은 항상 주의해야 합니다. 날짜/시간은 브라우저에서 문자열로 전송되지만, 서버에서는 LocalDate 혹은 LocalDateTime 으로 처리됩니다.
그렇기 때문에 이를 변환해 주는 Formatter를 추가해서 이 과정을 자동으로 처리 할 수 있도록 설정합니다.
addFormatters 메서드는 스프링의 WebMvcConfigurer 인터페이스에 정의된 메서드 중 하나로, 애플리케이션에서 사용하는 데이터 형식을 일관되게 유지하기 위해 커스텀 포매터를 등록하는 데 사용됩니다.
포매터는 문자열과 특정 데이터 타입 간의 변환을 담당하며, 이를 통해 요청 파라미터나 폼 데이터 등을 적절한 데이터 타입으로 변환하여 사용할 수 있습니다.
addFormatters의 구체적인 기능
- 포매터 등록: addFormatters 메서드를 사용하여 커스텀 포매터를 등록할 수 있습니다. 포매터는 데이터 형식을 변환하는 역할을 합니다.
- 데이터 바인딩: 요청 매개변수, 폼 데이터, 경로 변수 등을 객체로 변환할 때 사용됩니다.
- 데이터 형식 유지: 포매터를 사용하면 애플리케이션 전반에 걸쳐 특정 데이터 형식을 일관되게 유지할 수 있습니다.
포매터 등
CustomServletConfig 클래스에서 addFormatters 메서드를 사용하여 커스텀 포매터를 등록하는 방법입니다.
WebMvcConfigurer를 구현한 설정 클래스에 포매터를 등록합니다.
package org.zerock.mallapi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.zerock.mallapi.controller.formatter.LocalDateFormatter;
import org.zerock.mallapi.controller.formatter.LocalDateTimeFormatter;
@Configuration
public class CustomServletConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateFormatter());
registry.addFormatter(new LocalDateTimeFormatter()); // accessTime 기록을 위한 포매터
}
작성 위치
스프링 MVC 동작과정에서 사용될 수 있도록 CustomServletConfig 클래스를 config 패키지 밑에 설정을 추가 합니다.
LocalDateFormatter의 사용
등록된 포매터는 스프링 MVC에서 자동으로 사용되므로, 컨트롤러에서 LocalDateTime 타입을 파라미터로 받을 때 자동으로 변환됩니다.
controller 패키지 하위로 formatter 패키지를 선언하고 LocalDateTimeFormatter 클래스를 추가합니다.
package org.zerock.mallapi.controller.formatter;
import org.springframework.format.Formatter;
import java.time.format.DateTimeFormatter;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.Locale;
public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {
@Override
public LocalDateTime parse(String text, Locale locale) throws ParseException {
return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Override
public String print(LocalDateTime object, Locale locale) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(object);
}
}
DTO와 엔티티 클래스에 LocalDateTime 필드를 추가
시간을 저장하기 위해 DTO와 엔티티 클래스에 LocalDateTime 필드를 추가합니다.
package org.zerock.mallapi.entity;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Entity
@ToString
@Table(name = "tbl_ip")
public class UserIP {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String ipAddress;
private LocalDateTime accessTime; // LocalDateTime 필드 추가
}
package org.zerock.mallapi.dto;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserIPDTO {
private Long id;
private String ipAddress;
private LocalDateTime accessTime; // LocalDateTime 필드 추가
}
이 후 스프링부트를 재시작합니다.
로그를 확인하면 tbl_ip 테이블에 accessTime 컬럼을 추가하는 쿼리가 실행된 것을 확인할 수 있습니다.
데이터베이스 설정
물론 application.properties 또는 application.yml 파일에 데이터베이스 설정이 이렇게 되어 있어야 합니다.
spring.jpa.hibernate.ddl-auto 설정은 Spring Boot에서 JPA (Java Persistence API)와 Hibernate를 사용할 때, 데이터베이스 스키마의 자동 생성 및 관리를 어떻게 할 것인지를 정의합니다. 이 설정의 값에 따라 애플리케이션이 시작될 때 데이터베이스 스키마를 어떻게 처리할지를 결정합니다.
다양한 옵션과 그 의미는 다음과 같습니다:
- none:
- Hibernate가 데이터베이스 스키마와 관련된 어떤 작업도 수행하지 않도록 합니다.
- validate:
- 애플리케이션이 시작될 때 엔티티 클래스와 데이터베이스 테이블 구조가 일치하는지 검증합니다. 일치하지 않으면 예외가 발생합니다.
- update:
- 애플리케이션이 시작될 때 엔티티 클래스의 변경 사항에 맞춰 데이터베이스 스키마를 업데이트합니다. 기존 데이터는 유지하면서 스키마를 변경합니다.
- create:
- 애플리케이션이 시작될 때 데이터베이스 스키마를 새로 생성합니다. 기존 데이터는 삭제됩니다.
- create-drop:
- 애플리케이션이 시작될 때 데이터베이스 스키마를 새로 생성하고, 애플리케이션이 종료될 때 스키마를 삭제합니다. 주로 테스트 환경에서 사용됩니다.
spring.jpa.hibernate.ddl-auto=update의 의미
spring.jpa.hibernate.ddl-auto=update 설정을 사용하면:
- 애플리케이션이 시작될 때 JPA 엔티티 클래스의 변경 사항에 맞춰 데이터베이스 스키마를 업데이트합니다.
- 기존 데이터는 삭제되지 않고, 새로운 필드나 테이블이 추가되거나, 기존 필드의 타입이 변경되는 등의 스키마 변경 작업이 수행됩니다.
- 이는 개발 초기 단계나 프로토타입 단계에서 유용합니다. 하지만 운영 환경에서는 예상치 못한 스키마 변경이 발생할 수 있기 때문에 주의가 필요합니다.
- 운영 환경에서는 주로 validate 설정을 사용하여 스키마와 엔티티 간의 불일치를 검증하거나, 스키마 변경을 직접 관리하는 것이 일반적입니다. update 설정은 개발 환경에서 데이터 손실 없이 빠르게 스키마를 반영하기 위해 유용합니다.
예시
다음은 application.properties 파일에 spring.jpa.hibernate.ddl-auto 설정을 사용하는 예시입니다
# application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.jpa.show-sql=true
UserIPService에 시간 저장 로직 추가
기존 코드에서 변경할 필요는 없습니다.
package org.zerock.mallapi.service;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.zerock.mallapi.dto.UserIPDTO;
import org.zerock.mallapi.entity.UserIP;
import org.zerock.mallapi.repository.UserIpRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Service
@Transactional
@Log4j2
@RequiredArgsConstructor
public class UserIPServiceImpl implements UserIPService {
// 자동주입 대상은 final로
private final ModelMapper modelMapper; // ModelMapper를 주입받음
private final UserIpRepository userIpRepository;
@Override
public Long ipRegister(UserIPDTO userIPDTO) {
log.info("ipRegister");
UserIP userIP = modelMapper.map(userIPDTO, UserIP.class);
UserIP savedUserIP = userIpRepository.save(userIP);
return savedUserIP.getId();
}
}
MemberController 클래스
이제 컨트롤러에서 IP 주소와 접근 시간을 저장합니다.
컨트롤러에서 UserIPDTO 객체를 생성하고 LocalDate.now()와 LocalDateTime.now()로 현재 날짜와 시간을 설정하여 서비스로 전달하면, 서비스는 이 데이터를 데이터베이스에 저장합니다.
package org.zerock.mallapi.controller;
import java.time.LocalDateTime;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zerock.mallapi.dto.MemberDTO;
import org.zerock.mallapi.dto.UserIPDTO;
import org.zerock.mallapi.service.MemberService;
import org.zerock.mallapi.service.UserIPService;
import org.zerock.mallapi.util.GetIpFromHeader;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@RequiredArgsConstructor
@Controller
@RequestMapping("/member")
public class MemberController {
private final UserIPService userIPService;
private final GetIpFromHeader getIpUtil;
private final MemberService memberService;
@GetMapping("/login") // 로그인 페이지 URL
public String getLoginForm() {
log.info("로그인페이지");
// 로그인 페이지에 접근한 사용자의 IP주소를 가져오기
String clientIp = getIpUtil.getIpFromHeader();
log.info("아이피: " + clientIp);
// 로그인 페이지에 접근한 사용자의 IP주소를 DB에 저장
UserIPDTO userIPDTO = new UserIPDTO();
userIPDTO.setIpAddress(clientIp);
// 사용자의 접속시간을 DB에 저장
LocalDateTime now = LocalDateTime.now().withNano(0); // LocalDateTime 객체를 초 단위까지만 설정
userIPDTO.setAccessTime(now);
userIPService.ipRegister(userIPDTO);
return "loginPage"; // 로그인 페이지 뷰 이름 반환
}
}
데이터베이스에서 access_time 확인
'Web > 스프링부트(SpringBoot Framework)' 카테고리의 다른 글
(2) @Autowired와 @AllArgsConstructor 차이점 (0) | 2024.07.15 |
---|---|
(1) @AllArgsConstructor과 @NoArgsConstructor 차이점 (0) | 2024.07.15 |
(1) 스프링부트, JPA로 구현한 로그인을 시도한 사용자의 IP 저장 (0) | 2024.07.11 |
React와 Spring Boot를 함께 사용하는 환경에서 서버 사이드 렌더링(SSR) 구현하기...(작성중) (0) | 2024.07.10 |
AuthenticationFailureHandler, AuthenticationEntryPoint 차이점 (0) | 2024.07.02 |