JPA 데이터 저장소 ArrayList→ MySQL 변환 실습
- Model 사용 프로젝트 변환
- build.gradle- dependencies 추가
- Spring Data JPA, Lombok, MySQL, H2, Spring Web, Spring Web Services, Thymeleaf
- application.properties 추가
-
# thymeleaf spring.thymeleaf.cache=false # jpa spring.jpa.hibernate.ddl-auto=update spring.jpa.generate-ddl=false spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect # pretty sql format spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true # mysql spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8&serverTimezone=Asia/Seoul spring.datasource.username=root spring.datasource.password=root # logging logging.level.org.hibernate.type.description.sql.BasicBinder=trace logging.level.org.hibernate.SQL=debug # Hibernate 6.1.5 updated in springboot 3.x logging.level.org.hibernate.orm.jdbc.bind=trace
-
- 전체 출력 (SELECT)
- List<Product> productList = productRepository.findAll();
- 추가 (INSERT)
-
@PostMapping("/add-action") public String addProduct(@RequestParam String name, @RequestParam int price, @RequestParam LocalDate limitDate) { Product product = Product.builder() .name(name) .price(price) .limitDate(limitDate) .build(); productRepository.save(product); return "redirect:/"; }
-
- 수정할 데이터 출력 (SELECT-WHERE)
- Product product = productRepository.findById(id).orElse(null);
- findById(id) 뒤에 orElse(null)을 적어야한다. (id는 Long 타입)
- Product product = productRepository.findById(id).orElse(null);
- 수정 (UPDATE)
-
@PostMapping("/edit-action") public String editProduct(@RequestParam Long id, @RequestParam String name, @RequestParam int price, @RequestParam LocalDate limitDate) { Product product = productRepository.findById(id).orElse(null); product.setName(name); product.setPrice(price); product.setLimitDate(limitDate); productRepository.save(product); return "redirect:/"; }
-
- 삭제 (DELETE)
-
@GetMapping("/delete-action") public String deleteProduct(@RequestParam Long id) { Product product = productRepository.findById(id).orElse(null); productRepository.delete(product); return "redirect:/"; }
-
- build.gradle- dependencies 추가
HttpSession, BindingResult 사용한 로그인/로그아웃, 회원가입 구현
- build.gradle- dependencies 추가
- implementation 'org.springframework.boot:spring-boot-starter-validation’
- 로그인 구현
- Login 데이터 넣을 DTO 클래스 생성
- 클래스에 어노테이션 주입
- @Getter, @Setter, @NoArgsConstructor
- id, password 멤버 변수 생성
- 각 멤버변수에 어노테이션 주입
- @NotBlank(message = “ ”)
- 빈 칸일 때 출력할 메시지 내용 작성
- @Size(min = n, max = m)
- 받아올 아이디/비밀번호의 문자열 길이 범위 지정
- @NotBlank(message = “ ”)
- 각 멤버변수에 어노테이션 주입
- 예시
-
@Getter @Setter @NoArgsConstructor public class MemberLoginDTO { @NotBlank(message = "userId에 null, 빈문자열, 스페이스문자만 넣을 수 없습니다. ") @Size(min = 4, max = 20) private String userId; @NotBlank(message = "userPw에 null, 빈문자열, 스페이스문자만 넣을 수 없습니다. ") @Size(min = 4, max = 20) private String userPw; }
-
- 클래스에 어노테이션 주입
- 컨트롤러에서 로그인 처리 메소드 작성
- 메소드에 어노테이션 주입
- @PostMapping, @ReponseBody
- 매개변수 추가
- 위에서 만든 login DTO (@Valid, @ModelAtribute 어노테이션 주입)
- BindingResult
- HttpSession
- 예시 (loginAction)
-
@PostMapping("/loginAction") @ResponseBody public String loginAction(@Valid @ModelAttribute MemberLoginDTO dto, BindingResult bindingResult, HttpSession session){ if( bindingResult.hasErrors() ){ //바인딩 오류 //DTO에 설정한 message값을 가져온다. String detail = bindingResult.getFieldError().getDefaultMessage(); //DTO에 유효성체크를 걸어놓은 어노테이션명을 가져온다. String bindResultCode = bindingResult.getFieldError().getCode(); System.out.println( detail + ":" + bindResultCode); return "<script>alert('" + detail +"'); history.back();</script>"; } System.out.println(dto.getUserId()); System.out.println(dto.getUserPw()); //로그인 처리 로직 //1. 메시지 : "아이디가 없습니다" //2. : "암호가 맞지 않습니다." Optional<MemberEntity> optional = memberRepository.findByUserId(dto.getUserId()); if( !optional.isPresent() ){ return "<script>alert('아이디가 없습니다.'); history.back();</script>"; } Optional<MemberEntity> optional2 = memberRepository.findByUserIdAndUserPw(dto.getUserId(), dto.getUserPw()); if( !optional2.isPresent() ){ return "<script>alert('암호가 맞지 않습니다.'); history.back();</script>"; } //세션에 로그인 여부/로그인 아이디/로그인 권한 저장. session.setAttribute("isLogin", true); session.setAttribute("userId", optional2.get().getUserId()); session.setAttribute("userRole", optional2.get().getUserRole()); String userRole = optional2.get().getUserRole(); if(userRole.equals("ROLE_ADMIN") == true) { return "<script>alert('관리자 로그인 성공'); location.href='/admin';</script>"; } else { return "<script>alert('로그인 성공'); location.href='/';</script>"; } }
-
- 메소드에 어노테이션 주입
- 레포지토리에 해당 아이디/비밀번호의 엔티티 찾는 코드 작성
- JPA 혹은 nativeQuery로 작성
- 예시
-
@Query(value = "select * from member m where m.user_id = :param_user_id " + "and m.user_pw = :param_user_pw", nativeQuery = true) Optional<MemberEntity> findByUserIdAndUserPw( @Param("param_user_id") String userId, @Param("param_user_pw") String userPw); @Query(value = "select * from member m where m.user_id = :userId", nativeQuery = true) Optional<MemberEntity> findByUserId(String userId);
-
- html에 위에서 만든 메소드 Form에 적용하기
- 예시
- <form action="/loginAction" method="post">
- <button type="submit">로그인하기</button>
- 예시
- Login 데이터 넣을 DTO 클래스 생성
- 로그아웃 구현
- 컨트롤러에서 로그아웃 처리 메소드 작성
- 메소드에 어노테이션 주입
- @GetMapping, @ResponseBody
- 매개변수 추가
- HttpSession
- 로그인 관련 값을 null로 set한 후, session.invalidate(); 로 세션 종료
- 예시 (logoutAction)
-
@GetMapping("/logoutAction") @ResponseBody public String logoutAction(HttpSession session) { // 로그아웃 액션 session.setAttribute("isLogin", null); session.setAttribute("userId", null); session.setAttribute("userRole", null); session.invalidate(); // 세션 종료(JSESSIONID 종료), 모든 속성 제거 return "<script>alert('로그아웃되었습니다.'); location.href='/';</script>"; }
-
- 메소드에 어노테이션 주입
- html에 위에서 만든 메소드 Form에 적용하기
- 예시
- <button type="button" onclick="location.href='/logoutAction'">로그아웃</button>
- 예시
- 컨트롤러에서 로그아웃 처리 메소드 작성
- 회원가입 구현
- 회원가입 데이터 넣을 DTO 클래스 생성
- 클래스에 어노테이션 주입
- @Getter, @Setter, @AllArgsConstructor, @NoArgsConstructor, @Builder
- 회원가입 정보들 멤버 변수 생성
- 각 멤버변수에 어노테이션 주입
- @NotBlank(message = “ ”)
- 빈 칸일 때 출력할 메시지 내용 작성
- @Size(min = n, max = m)
- 받아올 아이디/비밀번호의 문자열 길이 범위 지정
- @DateTimeFormat(pattern = "yyyy-MM-dd")
- @NotBlank(message = “ ”)
- 각 멤버변수에 어노테이션 주입
- 예시
-
@Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder public class MemberJoinDTO { private Long id; @Size(min = 4, max = 20, message = "userId는 4자이상 20자 이하입니다.") @NotBlank(message = "null, 빈문자열, 스페이스문자열만 넣을 수 없습니다.") private String userId; @Size(min = 4, max = 20, message = "암호는 4자이상 20자 이하입니다.") @NotBlank(message = "null, 빈문자열, 스페이스문자열만 넣을 수 없습니다.") private String userPw; @NotBlank(message = "null, 빈문자열, 스페이스문자열만 넣을 수 없습니다.") private String userName; @NotBlank(message = "null, 빈문자열, 스페이스문자열만 넣을 수 없습니다.") private String userRole; private String userAddress; // 엔티티에 없는 변수도 추가 가능 @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate joinDate; // DTO를 save용 Entity로 변환해주는 메소드 public MemberEntity toSaveEntity() { return MemberEntity.builder() .userId(userId) .userPw(userPw) .userName(userName) .userRole(userRole) .joinDate(joinDate) .build(); }
-
- 클래스에 어노테이션 주입
- 컨트롤러에서 로그인 처리 메소드 작성
- 메소드에 어노테이션 주입
- @PostMapping, @ReponseBody
- 매개변수 추가
- 위에서 만든 login DTO (@Valid, @ModelAtribute 어노테이션 주입)
- BindingResult
- 예시 (joinAction)
-
@PostMapping("/joinAction") @ResponseBody public String joinAction(@Valid @ModelAttribute MemberJoinDTO dto, BindingResult bindingResult) { if( bindingResult.hasErrors() ){ //바인딩 오류 //DTO에 설정한 message값을 가져온다. String detail = bindingResult.getFieldError().getDefaultMessage(); //DTO에 유효성체크를 걸어놓은 어노테이션명을 가져온다. String bindResultCode = bindingResult.getFieldError().getCode(); System.out.println( detail + ":" + bindResultCode); return "<script>alert('" + detail +"'); history.back();</script>"; } System.out.println("name: " + dto.getUserName()); try { MemberEntity memberEntity = dto.toSaveEntity(); memberRepository.save(memberEntity); } catch (Exception e) { e.printStackTrace(); System.out.println("회원가입 실패"); return "<script>alert('회원가입 실패');history.back();</script>"; } return "<script>alert('회원가입 성공');location.href='/';</script>"; }
-
- 메소드에 어노테이션 주입
- html에 위에서 만든 메소드 Form에 적용하기
- 예시
- <form action="joinAction" method="post">
- <button type="submit">회원가입</button>
- 예시
- 회원가입 데이터 넣을 DTO 클래스 생성
- 데이터 값 확인 어노테이션 종류
-
//@NotNull Null 불가 //@Null Null만 입력 가능 //@NotEmpty Null, 빈 문자열 불가 //@NotBlank Null, 빈 문자열, 스페이스만 있는 문자열 불가 //@Size(min=,max=) 문자열, 배열등의 크기가 만족하는가? //@Pattern(regex=) 정규식을 만족하는가? //@Max(숫자) 지정 값 이하인가? //@Min(숫자) 지정 값 이상인가 //@Future 현재 보다 미래인가? //@Past 현재 보다 과거인가? //@Positive 양수만 가능 //@PositiveOrZero 양수와 0만 가능 //@Negative 음수만 가능 //@NegativeOrZero 음수와 0만 가능 //@Email 이메일 형식만 가능 //@Digits(integer=, fraction = ) 대상 수가 지정된 정수와 소수 자리 수 보다 작은가? //@DecimalMax(value=) 지정된 값(실수) 이하인가? //@DecimalMin(value=) 지정된 값(실수) 이상인가? //@AssertFalse false 인가? //@AssertTrue true 인가?
-
Logger
- Slf4j (Simple Logging Facade for Java)
- 파사드 패턴을 이용한 로깅 인터페이스
- Slf4j를 앞에 세워두고 어플리케이션과 로깅 프레임워크 간의 직접적인 결합도를 없애고 파사드만을 바라보게 하여 로깅 구현체가 어떤 식으로 변경되든 변화를 무시 혹은 최소화할 수 있다.
- Spring - logging 툴 사용 이유
- System.out.println()은 IO리소스를 많이 사용하여 시스템이 느려질 수 있다.
- 로그를 파일로 저장하여 분석할 필요가 있기 때문에 사용
- logging 툴 종류
- commons-logging
- Spring3에서 사용하는 logging 툴
- log4j
- 효율적인 메모리 관리로 많이 사용되었다.
- logback
- log4j보다 성능이 더 우수하여 최근에 많이 사용되고 있다.
- commons-logging
- SLF4J
- logback을 사용하기 위한 인터페이스
Spring Boot Logging
- SLF4J
- 로깅에 대한 추상 레이어를 제공하는 인터페이스 모음
- @Slf4j
- 스프링 부트에서 로그를 남기는 어노테이션
- 스프링부트 Logging 순서
- build.gradle- dependencies 추가
- testCompileOnly 'org.projectlombok:lombok' // 테스트 의존성 추가
- testAnnotationProcessor 'org.projectlombok:lombok' // 테스트 의존성 추가
- application.yml 파일 생성
-
logging: level: com: study: ex21logger: debug pattern: file: "[%d{HH:mm:ss.SSS}][%-5level][%logger.%method:line%line] - %msg%n" level: info file: name: logs/mylog.log logback: max-history: 30 file-name-pattern: "logs/mylog.%d{yyyy-MM-dd}.%i"
-
- test 폴더 안에 LogTest 클래스 생성
- 클래스에 어노테이션 주입
- @Slf4j
- ApplicationTests 클래스 상속받기 (extends)
- 메소드 생성
- 메소드에 어노테이션 주입
- @Test
- log 코드 작성
- 메소드에 어노테이션 주입
- 예시
-
@Slf4j public class LogTest extends Ex21LoggerApplicationTests { static int count = 0; @Test public void TestLogger(){ Class myClass = LogTest.class; // Class 객체 - 클래스에 대한 정보(멤버함수,멤버변수, 생성자)을 담고 있음. Logger logger = LoggerFactory.getLogger(LogTest.class); logger.trace("trace 로깅 {}", count++); logger.debug("debug 로깅 {}", count++); logger.info("info 로깅 {}", count++); logger.warn("warn 로깅 {}", count++); logger.error("error 로깅 {}", count++); } @Test public void testSlf4j(){ log.trace("Slf4j trace 로깅 {}", count++); log.debug("Slf4j debug 로깅 {}", count++); log.info("Slf4j info 로깅 {}", count++); log.warn("Slf4j warn 로깅 {}", count++); log.error("Slf4j error 로깅 {}", count++); } }
-
- 클래스에 어노테이션 주입
- logback.xml 파일 생성
-
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT" /> </root> </configuration>
-
- logback-spring.xml 파일 생성
-
<?xml version="1.0" encoding="UTF-8" ?> <!-- 60초마다 설정 파일의 변경을 확인하여 변경시 갱신 --> <configuration scan="true" scanPeriod="60 seconds"> <!-- 로그 패턴에 색상 적용 %clr(pattern){color} https://logback.qos.ch/manual/layouts.html#coloring --> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <!-- springProfile 태그를 사용하여 profile 별 property 값 설정 --> <springProfile name="local"> <!-- local log file path --> <property name="LOG_PATH" value="/Users/username/Desktop/log"/> </springProfile> <springProfile name="dev"> <!-- dev log file path --> <property name="LOG_PATH" value="/home/instance/log"/> </springProfile> <!-- Environment 내의 프로퍼티들을 개별적으로 설정 --> <springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/> <!-- log file name --> <property name="LOG_FILE_NAME" value="log"/> <!-- err log file name --> <property name="ERR_LOG_FILE_NAME" value="err_log"/> <!-- console log pattern --> <property name="CONSOLE_LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %clr(%-5level) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15thread]){faint} %clr(%-40.40logger{36}){cyan} %clr(:){faint} %msg%n"/> <!-- file log pattern --> <property name="FILE_LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %-5level ${PID:-} --- [%15.15thread] %-40.40logger{36} : %msg%n"/> <!-- Console Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- File Appender --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 파일경로 설정 --> <file>${LOG_PATH}/${LOG_FILE_NAME}.log</file> <!-- 출력패턴 설정 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <!-- Rolling 정책 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- .gz .zip 등을 넣으면 자동 일자별 로그 파일 압축 --> <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.gz</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- 파일당 최고 용량 kb, mb, gb --> <maxFileSize>10KB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!-- 일자별 로그 파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거 --> <maxHistory>30</maxHistory> <!-- 전체 파일 크기를 제어하며, 전체 크기 제한을 조과하면 가장 오래된 파일을 삭제 --> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> </appender> <!-- 에러의 경우 파일에 로그 처리 --> <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>error</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <file>${LOG_PATH}/${ERR_LOG_FILE_NAME}.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <!-- Rolling 정책 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- .gz .zip 등을 넣으면 자동 일자별 로그파일 압축 --> <fileNamePattern>${LOG_PATH}/${ERR_LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- 파일당 최고 용량 kb, mb, gb --> <maxFileSize>10KB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!-- 일자별 로그 파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거 --> <maxHistory>60</maxHistory> </rollingPolicy> </appender> <!-- root 레벨 설정 --> <root level="${LOG_LEVEL}"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> <appender-ref ref="ERROR"/> </root> </configuration>
-
- build.gradle- dependencies 추가
728x90
'교육 (Today I Learned) > Hanaro' 카테고리의 다른 글
[Hanaro] 62일차 / REST API(CRUD), JSP (0) | 2024.04.18 |
---|---|
[Hanaro] 61일차 / Spring Boot (Test, Scheduler) (0) | 2024.04.13 |
[Hanaro] 59일차 / MyBatis (0) | 2024.04.10 |
[Hanaro] 58일차 / Spring Boot (Thymeleaf Layout), JSP (0) | 2024.04.10 |
[Hanaro] 57일차 / Spring Boot (URI 어노테이션) (0) | 2024.04.06 |