면접에서도 자주나오는 질문중 하나라고 하셨고 숙지가 필요하다라고 하셨다!
안중요한건 없겠지만 내꺼로 만들어 보겠다..
백엔드 개발자는 DB는 어쩔수 없는 숙명? 같은 거니까 잘알고 넘어가야 한다고 하셨다.. 화이팅해보자아
Atomic(원자성):
Consistancy(일관성):
모든 DB테이블의 자료들은 항상 정해진 규칙에 맞춰서 자료가 저장되어있어야한다.
Isolation(고립성/독립성):
트랜잭션특징중 까다로운 특징
Durablity(지속성):
commit이 되는 시점에는 DB에 commit된 이력은 무조건 남아 있어야 한다.
컨트롤러에서 일반적으로 새로운 데이터를 만든다 하면 POSTMapping을 사용하므로 변경
그리고 DTO패키지에 CreateDeveloper class 를 생성
하위에 Request class를 생성해서 하는거로 예제 실행
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Request{
private DeveloperLevel developerLevel;
private DeveloperSkillType developerSkillType;
private Integer experienceYears;
private String memberId;
private String name;
private Integer age;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Response{
private DeveloperLevel developerLevel;
private DeveloperSkillType developerSkillType;
private Integer experienceYears;
private String memberId;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Request{
@NotNull
private DeveloperLevel developerLevel;
@NotNull
private DeveloperSkillType developerSkillType;
@NotNull
@Min(0)
@Max(20)
private Integer experienceYears;
@NotNull
@Size(min= 3,max= 50, message = "memberId size must 3 ~ 50")
private String memberId;
@NotNull
@Size(min= 3,max= 20, message = "name size must 3 ~ 20")
private String name;
@Min(18)
private Integer age; //-나이는 민감한 개인정보 일수도 있으므로 @NotNull 뺌
}
@PostMapping("/create-developer")
public List<String> createDevelopers(
@Valid @RequestBody CreateDeveloper.Request request
) {
log.info("request :{}", request);
dMarkerService.createDeveloper();
return Collections.singletonList("Olaf");
}
@Valid : RequestBody에 들어온 값들을 이 request에 데이터를 담아주면서 Java Bean Validation을 하고
문제 발생시 MessageArgumentValidException이 발생함
private void validateCreateDeveloperRequest(CreateDeveloper.Request request) {
// Business validation
DeveloperLevel developerLevel = request.getDeveloperLevel();
Integer experienceYears = request.getExperienceYears();
if (developerLevel == DeveloperLevel.SENIOR
&& experienceYears < 10) {
throw new DMakerException(LEVEL_EXPERIENCE_YEARS_NOT_MATCHED);
}
if (developerLevel == DeveloperLevel.JUNGNIOR
&& (experienceYears < 4 || experienceYears > 10)) {
throw new DMakerException(LEVEL_EXPERIENCE_YEARS_NOT_MATCHED);
}
if (developerLevel == DeveloperLevel.JUNIOR && experienceYears > 4) {
throw new DMakerException(LEVEL_EXPERIENCE_YEARS_NOT_MATCHED);
}
// Java8 에서는 이렇게 사용했었음
// Optional<Developer> developer = developerRepository.findByMemberId(request.getMemberId());
// if(developer.isPresent()) throw new DMakerException(DUPLICATED_MEMBER_ID);
//-Java11에서는 이렇게 한줄로 처리할 수 있다.
developerRepository.findByMemberId(request.getMemberId())
.ifPresent((developer -> {
throw new DMakerException(DUPLICATED_MEMBER_ID);
}));
}
이렇게 만들어 줄 때 표시한 부분을 만들어 주지 않았다.. ㅎ
수정 후 테스트 결과
현재 CRUD 중 Create 부분 만 생성을 하였다.
다른 생성한 코드들은 GitHub에 올리겠다. 몰랐던 부분들을 주로 적을 거 같다!
우리가 API에서 응답을 내려줄때 Entity를 그대로 쓰면 안되는 Anti Pattern 이다
@GetMapping("/developers")
public List<DeveloperDto> getALlDevelopers() {
//GET /developers HTTP/1.1
log.info("GET /developers HTTP/1.1");
return dMarkerService.getAllDevelopers();
}
DeveloperDto를 받아오도록 수정
전체 DeveloperDto를 받아오는 서비스
public List<DeveloperDto> getAllDevelopers() {
return developerRepository.findAll()
.stream().map(DeveloperDto :: fromEntity)
.collect(Collectors.toList());
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeveloperDto {
private DeveloperLevel developerLevel;
private DeveloperSkillType developerSkillType;
private String memberId;
public static DeveloperDto fromEntity(Developer developer){
return DeveloperDto.builder()
.developerLevel(developer.getDeveloperLevel())
.developerSkillType(developer.getDeveloperSkillType())
.memberId(developer.getMemberId())
.build();
}
}
저장한 값들이 제대로 호출 됨을 알 수 있다.
@GetMapping("/developer/{memberId}")
public DeveloperDetailDto getDeveloperDetail(
@PathVariable String memberId
) {
//GET /developers HTTP/1.1
log.info("GET /developers HTTP/1.1");
return dMarkerService.getDeveloperDetail(memberId);
}
public DeveloperDetailDto getDeveloperDetail(String memberId) {
return developerRepository.findByMemberId(memberId)
.map(DeveloperDetailDto :: fromEntity)
.orElseThrow(()->new DMakerException((NO_DEVELOPER)));
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeveloperDetailDto {
private DeveloperLevel developerLevel;
private DeveloperSkillType developerSkillType;
private Integer experienceYears;
private String memberId;
private String name;
private Integer age;
public static DeveloperDetailDto fromEntity(Developer developer){
return DeveloperDetailDto.builder()
.developerLevel(developer.getDeveloperLevel())
.developerSkillType(developer.getDeveloperSkillType())
.experienceYears(developer.getExperienceYears())
.memberId(developer.getMemberId())
.name(developer.getName())
.age(developer.getAge())
.build();
}
}
현재 실습하는것은 @PutMapping으로 데이터 수정하는 방법에 대해서 공부
수정을 받는다는것은 Developer의 모든 정보를 RequestBody(JSON타입)으로 모든정보를 받아야한다.
@PutMapping("/developer/{memberId}")
public DeveloperDetailDto editDeveloper(
@PathVariable String memberId,
@Valid @RequestBody EditDeveloper.Request request
) {
//GET /developers HTTP/1.1
log.info("GET /developers HTTP/1.1");
return dMarkerService.editDeveloper(memberId,request);
}
@Transactional
public DeveloperDetailDto editDeveloper(String memberId, EditDeveloper.Request request) {
validateEditDeveloperRequest(request,memberId);
Developer developer = developerRepository.findByMemberId(memberId).orElseThrow(
() -> new DMakerException(NO_DEVELOPER)
);
developer.setDeveloperLevel(request.getDeveloperLevel());
developer.setDeveloperSkillType(request.getDeveloperSkillType());
developer.setExperienceYears(request.getExperienceYears());
return DeveloperDetailDto.fromEntity(developer);
}
private void validateEditDeveloperRequest(EditDeveloper.Request request,String memberId) {
validateDeveloperLevel(
request.getDeveloperLevel(),
request.getExperienceYears()
);
}
이 키워드를 통해서 editDeveloper메소드가 실행되기 전에 트랜잭션 시작후 Developer엔티티에 값을 바꾼후 더티 체킹후 변경된 값을 commit해줌
트랜잭션이 어떻게 동작하는지 어떤 명령들이 있을수 있는지에 대해서 공부할 예정
트랜잭션은 하나가 성공하고 하나가 실패하지않은 Atomic한 성질(핵심적인성질)을 가져야한다.
Delete기능 코드는 GitHub에 올릴예정
이렇게 만약에 처리 중간에 Exception이 발생해서 처리가 안된다면 위(1.)은 rollback이 되고 아래(.2)는 바뀌지 않을것이다.
Delete 시 Exception이 발생하여
1.과정이 rollback되어 Retired로 안바뀌고 EMPLOYED 인것을 확인 할 수 있다.
트랜잭션을 제대로 설정 안하고 한다면 어떻게 될까