Validation 활용
- JSON 객체는 파라미터가 되는 필드들을 JSON 객체로 감싼다.
- 즉, 어떤 필드가 null인지 알 수 없다.
validation 적용
- 필드에 NotNull 조건 추가
-
@NotNull 어노테이션 추가
public class UserVO implements Serializable{ ... @NotNull(message = "userName필드가 null입니다.") private String userName; ... }
-
@NotNull 어노테이션 추가
- 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); }
-
@Validated 어노테이션 추가
예외 처리를 위한 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를 추상화해서 트랜잭션에 관련된 특정 기술에 대한 종속성을 낮춤
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