Querydsl 사용하는 이유

data jpa의 @Query로는 다양한 조회 기능 사용에 한계가 존재

->해결 하기 위해 정적 타입을 지원하는 가장 유명한 조회 프레임워크 Querydsl 사용

동적 쿼리 작성이 편리

1. build.gradle 수정

 

plugin 블럭에 추가

id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"

dependencies 블럭에 추가

 implementation 'com.querydsl:querydsl-jpa'

querydsl 설정 부분 블럭으로 추가

//querydsl설정

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

2. configuration 추가

이 설정을 통해 jpaQueryFactory를 프로젝트 어느 곳에서나 주입받아 사용 가능

@Configuration
public class QuerydslConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory(){
        return new JPAQueryFactory(entityManager);
    }

}

 

참고 사이트

https://hello-gg.tistory.com/62

https://jojoldu.tistory.com/372

 

 

 

원래 validate_password_policy 변수의 값 등을 변경하는 방식으로 비밀번호 정책을 바꿀 수 있었다.

my.cnf 파일에 들어가서 해당 변수를 바꾸려고 하거나

mySQL을 실행한 후 mysql> SET GLOBAL validate_password_policy=LOW;을 실행해 바꾸려고해도 

Unknown system variable 'validate_password_policy'라는 오류가 떴다.

 

1. SHOW VARIABLES LIKE 'validate_password%'; 로 시스템 변수명 확인

mySQL 버전이 업그레이드 되면서 새로운 방법으로 비밀번호 정책을 변경해야하는 게 아닌가 생각이 들던 중 

SHOW VARIABLES LIKE 'validate_password%';

위 코드를 통해 비밀번호 유효성 검사 시스템 변수들을 확인할 수 있다는 사실을 알았다. 

 

해당 명령어를 통해 비밀번호 정책 관련 시스템 변수들을 확인해보니 validate_password_policy에서 validate_password.policy로 변수명이 바껴있었고 validate_password_length 변수도 validate_password.length로 변수명이 바껴있었다. 이밖에도 관련 다른 변수명들을 해당 명령어를 통해 확인할 수 있다.

 

2. 바뀐 변수명에 맞춰 비밀번호 정책 바꿔주기

SET GLOBAL validate_password.policy=LOW;

 

SET GLOBAL validate_password.length=4;

 

3. 확인해보기

SHOW VARIABLES LIKE 'validate_password%'; 

정상적으로 변경됐음을 확인할 수 있다.

 

참고 사이트

https://scbyun.com/1137

AOP (Aspect-Orientes Programming)란?

관점 지향 프로그래밍

AOP는 OOP(객체지향프로그래밍)와 다른 게 아니라 OOP를 보완해주는 것

OOP에서 핵심 비즈니스 로직과 독립적으로 분리하기 힘든 부가 기능들을 AOP의 Aspect을 활용해 모듈화해준다.

핵심 비즈니스 로직을 담고 있지는 않지만 어플리케이션에 부가됨으로써 의미를 갖는 특별한 모듈!

 

OOP: 비지니스 로직의 모듈화

AOP: 인프라 혹은 부가 기능의 모듈화

  ex) 트랜잭션, 로깅, 등

 

Aspect을 이용해 모듈화 왜 필요? 그냥 쓰면 안되나?

흩어진 관심사를 횡단 기능에 관심을 두어 하나로 합칠 수 있다.

이 과정에서 중복을 줄여 코드의 효율성도 증가하고 유지보수도 더 쉬움

ex)

매 클래스마다 이용자가 사용하는 부가 기능(ex, 실행시간 측정 등)이 있다고 생각해보자.

관련된 코드들을 매 클래스마다 다 넣으면 핵심 로직과 관련된 코드도 아닌데 작성도 힘들고 수정이 필요할 시 유지보수도 힘들다.

그렇다고 없앨 수는 없다.

 

AOP 용어

Aspect

부가 기능 모듈

advice와 point cut을 내재

Target

aspect에 적용되는 대상

Advice

어떤 기능을 부가적으로 넣을지

Join Point

advice 적용될 위치, 끼어들 수 있는 지점, 메서드 진입 지점, 생성자 호출 지점 등 다양한 시점에 적용 가능

Point Cut

어디에 적용해야 하는지에 대한 정보 ex 메서드 정보 등

Proxy

타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑 오브젝트

Weaving

지정된 객체에 aspect를 적용해서 새로운 proxy 객체를 생성하는 과정

 

AOP 구현체 종류

1. AspectJ

2. Spring AOP

 

AOP 적용 방법

1. 컴파일시 -> AspectJ

자바 파일을 클래스 파일로 만들 때 바이트 코드를 조작하여 AOP 적용

2. 클래스 로드시 -> AspectJ

컴파일은 원래 클래스 그대로, 클래스를 로딩하는 시점에 AOP 적용

3. 런타임시(프록시 패턴 사용) -> Spring AOP

Spring AOP 대상인 클래스의 빈이 만들어질 때 Spring AOP가 프록시 기능이 추가된 클래스를 자동으로 만든다.

이후 원본이 아닌 프록시 기능이 추가된 클래스를 빈으로 등록

Spring AOP란?

스프링은 프록시 패턴을 이용해 AOP를 구현

스프링 빈에만 AOP 적용 가능

모든 AOP 기능을 지원하진 않고 스프링 IoC와 연동하여 중복코드, 프록시 클래스 작성 번거로움과 같은 흔한 문제들에 대한 해결책을 지원하는 게 목적

 

Spring AOP 의존성 추가

 

maven

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

참고 사이트

https://www.youtube.com/watch?v=Hm0w_9ngDpM 

https://jojoldu.tistory.com/71

https://devlog-wjdrbs96.tistory.com/398

https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95

https://engkimbs.tistory.com/746

https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/

 

 

 

 

 

 

 

로그, 로깅이란?

로깅이란 시스템 동작시 시스템의 상태와 작동 정보를 시간에 따라 기록하는 것

그 기록이 로그

 

로깅의 장점

소프트웨어의 디버깅이나 모니터링을 위하여 소프트웨어 동작 상태 정보를 기록해서 볼 수 있다.

-상황에 따라 Level별 메시지 가능

-프로그램의 실행에 대한 흐름과 에러 확인 가능

-자유로운 출력 형식과 위치 가능

-프레임워크를 이용하여 쉽게 설정 가능

 

로깅 프레임워크 3가지 종류들

1. logback(사용 추천)

log4j와 유사하지만 향상된 성능과 필터링 옵션, slf4j, 자동 리로드 기능 지원

 

spring boot 환경의 경우 spring-boot-starter-web > spring-boot-starter-logging에 기본적으로 logback 구현체가 포함!

-> 따로 프레임워크를 지정하지 않는다면 자동으로 logback이 적용된다.

 

공식 메뉴얼 링크

http://logback.qos.ch/manual/index.html

 

Logback Manual

The logback manual The complete logback manual documents the latest version of logback framework. In over 150 pages and dozens of concrete examples, it covers both basic and advanced logback features, including: the overall logback architecture discussion

logback.qos.ch

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-logging

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

2. log4j2

logback 이후에 나온 프레임워크

logback과 유사하나 Multi Thread 환경에서의 비동기 로거(Async Logger) 케이스에 처리량이 더 높고 시간 효율이 좋다.

 

spring boot 환경의 경우 log4j2를 사용하려면 dependency에서 logback 제거 작업 필요

 

3. slf4j

로깅에 대한 추상 레이어

코드를 일정하게 유지하면서 구현체의 전환 (다른 프레임워크로의 전환)을 쉽게 지원

logback, log4j2는 slf4j의 구현체

 

로그 레벨 

TRACE  <  DEBUG  <  INFO  <  WARN  <  ERROR

 

-ERROR : 오류가 발생

-WARN  : 처리 가능한 문제, 향후 에러의 원인이 될 수 있는 경고성 메시지

-INFO  : 상태 변경과 같은 정보성 로그를 표시한다.
-DEBUG : 디버깅하기 위한 정보. 
-TRACE : Debug보다 상세한 정보

 

차후에 스프링 부트에서 logback 설정을 하는 방법도 다뤄볼까 예정 중이다.

 

참고 사이트

https://wildeveloperetrain.tistory.com/36

https://goddaehee.tistory.com/206

최신 스프링 부트 프로젝트에선 (스프링부트 2.2x 이상 버전) Junit으로 JUnit5버전이 들어간다.

Junit4를 사용하고 싶다면 build.gradle dependencies에 아래 코드를 추가하면 된다.

 

testImplementation("org.junit.vintage:junit-vintage-engine") {
 exclude group: "org.hamcrest", module: "hamcrest-core"
}

 

추가적으로 JUnit4에서 @Test는 org.junit.Test를 사용해야한다.

스프링 부트 2.2x 버전 이후부터는 자동적으로 JUnit5가 들어간다.

 

내가 현재 듣고 있는 스프링 부트 강의는 예전 강의라 JUnit4를 사용하고 있는데 Junit5를 써보고 싶었고 현재 진행 중인 스프링 부트 프로젝트도 JUnit5를 쓸 생각이기 때문에 Junit5로 구글링해서 코드를 작성해봤다.

 

작성하면서 발견한 주요 차이점들을 적어보자면 @RunWith(SpringRunner.class)이 Junit5에선 @SpringBootTest에 포함되면서 생략 가능해졌다.

Junit4에서 사용하던 @Test(expected = 클래스명.class)이 사라지면서 

IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> userService.join(user2));
assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());

와 같은 코드를 사용해야했다.

 

UserService 검증 Juni4 코드

package simplebook.simpleshop.service;

import simplebook.simpleshop.Domain.User;
import simplebook.simpleshop.Repository.UserRepository;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class UserServiceTest {

 @Autowired
 UserService userService;
 @Autowired
 UserRepository userRepository;
 
 @Test
 public void 회원가입() throws Exception {
  //Given
  User user = new User();
  user.setUserName("bryn");
  //When
  Long saveId = userService.join(user);
  //Then
  assertEquals(user, userRepository.findOne(saveId));
 }
 
 @Test(expected = IllegalStateException.class)
 public void 중복_회원_예외() throws Exception {
 //Given
 User user1 = new User();
 user1.setUserName("bryn");
 User user2 = new User();
 user2.setUserName("bryn");
 //When
 userService.join(user1);
 userService.join(user2); //예외가 발생해야 한다.
 //Then
 fail("예외가 발생해야 한다.");
 }
}

 

UserService 검증 Junit5 코드

package simplebook.simpleshop.service;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import simplebook.simpleshop.Domain.User;
import simplebook.simpleshop.Repository.UserRepository;
import javax.transaction.Transactional;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
class UserServiceTest {

    @Autowired
    UserService userService;
    @Autowired
    UserRepository userRepository;

    @Test
    public void 회원가입() throws Exception {
        //Given
        User user = new User();
        user.setUserName("bryn");
        //When
        Long saveId = userService.join(user);
        //Then
        assertEquals(user, userRepository.findOne(saveId));
    }

    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        User user1 = new User();
        user1.setUserName("bryn");
        User user2 = new User();
        user2.setUserName("bryn");
        //When
        userService.join(user1);
        userService.join(user2); //예외가 발생해야 한다.
        //Then
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> userService.join(user2));
        assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());
    }

}

 

+추가)

위에서 쓰인 User domain과 다른 domain임을 참고바랍니다.

 

User 검증 Juni4 코드

import jpabook.simpleshop.domain.User;
import jpabook.simpleshop.repository.UserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {
 @Autowired UserRepository userRepository;
 @Test
 @Transactional
 @Rollback(false)
 public void testUser() {
 
 User user = new User();
 user.setUsername("user1");
 
 Long savedId = userRepository.save(user);
 User findUser = userRepository.find(savedId);
 
 Assertions.assertThat(findUser.getId()).isEqualTo(user.getId());
 Assertions.assertThat(findUser.getUsername()).isEqualTo(user.getUsername());
 }
}

 

User 검증 Junit5 코드

package simplebook.simpleshop.User;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import javax.transaction.Transactional;

@ExtendWith(SpringExtension.class)
@SpringBootTest
class UserRepositoryTest {
    @Autowired UserRepository userRepository;

    @Transactional
    @Test
    public void testUser() throws Exception{
        User user=new User();
        user.setUserName("user1");
        
        Long saveId = userRepository.save(user);
        User findUser = userRepository.find(saveId);
        
        Assertions.assertThat(findUser.getId()).isEqualTo(user.getId());
        Assertions.assertThat(findUser.getUserName()).isEqualTo(user.getUserName());
    }
}

 

Junit5 테스트 코드를 통해 UserRepository가 정상적으로 작동함을 확인했다.

 

+ Recent posts