Validation 활용

  • JSON 객체는 파라미터가 되는 필드들을 JSON 객체로 감싼다.
    • 즉, 어떤 필드가 null인지 알 수 없다.

validation 적용

  1. 필드에 NotNull 조건 추가
    • @NotNull 어노테이션 추가
public class UserVO implements Serializable{
    ...
    @NotNull(message = "userName필드가 null입니다.")
    private String userName;
    ...
}
  1. controller 클레스에 validation 적용
    • @Validated 어노테이션 추가
@RequestMapping(value = "/regist")
public ResponseEntity<?> registUser(@Validated @RequestBody UserVO userVO){
    System.out.println("controller vo check::" + userVO.toString() );
    userService.createUser(userVO);
    return new ResponseEntity(null, HttpStatus.OK);
}

예외 처리를 위한 ControllerAdvice

Controller에 대한 예외 처리

  • @ControllerAdvice
    • Controller 클래스들의 모든 예외에 대해서 공통으로 초리할 수 있다.
    • 이를 사용하려면 @EnableWebMvc 선언 또는 <mvc:annotation-driven/> 추가

예외 클래스 작성

  • 에러 정보 전달 클래스
public class UserNotFoundException extends RuntimeException{
    private static final long serialVersionUID = 1L;

    public UserNotFoundException() {
    }

    public UserNotFoundException(String message) {
        super(message);
    }
}
  • 서비스 클래스
@Service
public class UserService {
    @Autowired
    UserRepository userRepository;

    public Iterable<UserVO> findAllUserInfo(){
        Iterable<UserVO> allUsers = userRepository.getUserInfoAll();
        return allUsers;
    }

    public void dummyInfo(){
        ServletUriComponentsBuilder.fromCurrentRequest()
                .toUriString();
    }

    public void createUser(UserVO userVO){
        System.out.println("userVO::" + userVO.toString());
        userRepository.adduserInfo(userVO);
    }

    public Iterable<? extends UserVO> findByLikeUserName(String userName){
        Iterable<UserVO> resultList = userRepository.findByUserNameLike(userName);
        return resultList;
    }

    public UserVO findByOneUserName(String userName){
        UserVO userVO = userRepository.findByUserName(userName);
        return userVO;
    }
}

ExceptionHadnler

  • @ControllerAdvice 선언 후 @ExceptionHandler로 예외를 명시할 수 있음
    • @ResponseStatus로 예외 응답 코드를 정의할 수도 있음
@ControllerAdvice
public class ApiExceptionHandler {
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ApiErrorDetail> handleUserNotFoundException(UserNotFoundException unfe){
        ApiErrorDetail errorDetail = new ApiErrorDetail();
        errorDetail.setTimeStamp(new Date());
        errorDetail.setCode(1002);// 별도의 예외코드 추가
        errorDetail.setMessage(unfe.getMessage());

        return new ResponseEntity(errorDetail, HttpStatus.NOT_FOUND);
    }
}

DB 예외 처리

Transaction

Database 참고

PlatformTransactionManager

  • PlatformTransactionManager를 추상화해서 트랜잭션에 관련된 특정 기술에 대한 종속성을 낮춤
    img

Transaction 설정

  • TransactionManagerConfigure를 추가
@Configuration
@EnableTransactionManagement
@PropertySource("application.properties")
public class MariaDBConnectionConfig implements TransactionManagementConfigurer{
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String dbUsername;
    @Value("${spring.datasource.password}")
    private String dbPassword;
    @Value("${spring.datasource.classname}")
    private String dbClassName;

    @Lazy
    @Bean(destroyMethod = "close")
    public DataSource dataSource(){
        final HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setUsername(dbUsername);
        hikariConfig.setPassword(dbPassword);

        hikariConfig.addDataSourceProperty("url", dbUrl);
        hikariConfig.setDataSourceClassName(dbClassName);
        hikariConfig.setLeakDetectionThreshold(2000);
        hikariConfig.setPoolName("jpubDBpool");

        final HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        return dataSource;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dataSource());
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return transactionManager();
    }
}

spring transaction과 Service 레이어

  • Repository Layer에서는 하나의 DB table을 대상으로 데이터 입력 조회 갱신 등의 기능을 가진 메서드들이 필요
  • Service Layer에서는 위의 메서드들을 모아서 한 번의 요청 안에서 여러 테이블을 조회하거나 갱신할 필요가 있음
  • 대부분 트랜잭션에 대한 롤백 설정은 Service Layer에서 처리

@Transacional 어노테이션

  • @Transacional
    • 트랜잭션 제어가 필요한 메서드에 사용
    • 인터페이스나 추상 클래스에는 사용 X
      • 실제 구현 클래스 내에서 사용
    • 롤벡에 관련된 속성값을 정의할 수 있음
      • isolation
      • propagation
      • readOnly
      • rollbackFor
      • no-rollback-for
      • timeout

Spring Boot Test

  • 단위 테스트 작성 시
    • junit, mockito, spring-test를 추가해 사용함
  • spring-boot-test-starter는 테스트에 필요한 의존성들을 spring-starter로 만들어 모아 놓은 모듈
    • 이를 사용하면 테스트 관련 라이브러리들을 별도로 추가하지 않아도 됨

DB 연동 테스트

  • 의존성 추가
    • testCompile("org.springframework.boot:spring-boot-starter-test")
    • testCompile은 테스트 시에만 의존성 추가됨DB 연동 테스트 코드
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MyBatisConfig.class, loader = AnnotationConfigContextLoader.class)
@Rollback
public class MybatisTestConfig {
}
  • UserRepository 클래스 테스트 클래스
public class UserDaoTest extends MybatisTestConfig {
    @Autowired
    private UserRepository userRepository;


    @Test
    public void testList(){
        System.out.println(userRepository.getUserInfoAll());
    }

    @Test
    public void createUser(){
        UserVO userVO = new UserVO();
        userVO.setId("jpub115");
        userVO.setUserName("홍길동");
        userVO.setEmail("test4@jpub.com");
        userRepository.adduserInfo(userVO);
        System.out.println(userRepository.getUserInfoAll());
    }
}

Service 클래스의 단위 테스트

  • 위와 동일하게 test에 Service 클래스를 스캐닝하는 설정 클래스를 로드
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ServiceConfig.class, loader = AnnotationConfigContextLoader.class)
@Rollback
public class ServiceTestConfig {
}
  • 테스트 클래스
public class UserServiceTest extends ServiceTestConfig {
    @Autowired
    UserService userService;

    @Test
    public void findUserList(){
        userService.findAllUserInfo();
    }

    @Test
    public void findUserNameTest(){
        String uname = "kim1";
        System.out.println(userService.findByOneUserName(uname));
    }
}

통합 테스트

  • 최초의 사용자 액션에 접점이 되는 컨트롤러부터 지금까지 테스트해 온 모든 테스트를 합친 test
  • 매우 복잡함
    • 톰캣 또는 임베디드 톰캣을 실행
    • 다른 모든 계층 클래스들의 메서드들의 정상 실행
    • 통합 테스트와 단위 테스트는 분리해서 관리통합 테스트를 위한 gradle 설정
  • 다음은 integrationTest 폴더 하위에 작성한 코드들이 클래스 패스에
integrationTest {
        java.srcDir "src/integrationTest/java"
        resources.srcDir "src/integrationTest/resources"

        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
  • 통합 테스트용 task 추가 및 단위 테스트와의 관계 설정
task integrationTest(type: Test) {
    testClassesDir = sourceSets.integrationTest.output.classesDir
    classpath = sourceSets.integrationTest.runtimeClasspath
    outputs.upToDateWhen { false }
}
check.dependsOn integrationTest
integrationTest.mustRunAfter test

 

'WebStudy > Spring' 카테고리의 다른 글

Spring Security  (0) 2021.08.03
Spring Cache  (0) 2021.07.28
Boot Starter  (0) 2021.07.22
Annotation  (0) 2021.07.21
REST API 2  (0) 2021.07.20
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기