sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void update(String email) {
User user = userRepository.findByEmail(email);
user.setPassword("modify the password");
userRepository.save(user);
}
...
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void update(String email) {
User user = userRepository.findByEmail(email);
user.setPassword("modify the password");
}
...
}
😎 두 번의 query를 날리던 코드가 한번의 query로 해결할 수 있습니다.
권한은 관리자와 일반 사용자가 있습니다. (O)
😎 Admin column으로 구분합니다.
회원가입, 로그인, 회원정보 수정, 삭제가 가능합니다. (O)
😎 RestAPI를 공부했던 것을 바탕으로 구현했습니다.
JWT 토큰으로 특정 사용자를 구분합니다. (O)
😎 여기 에서 확인할 수 있습니다.
OAuth로 로그인이 가능합니다. (x)
😎 JWT와 OAuth 대한 내용은 양이 많을 것 같아 따로 블로그를 게시하겠습니다.
😎 코드를 깔끔하게 만들기 위해 롬복을 사용합니다.
어떤 필드에도 @Getter, @Setter를 어노테이트 할 수 있습니다.
AccessLevel를 명시적으로 지정하지 않을 경우 public으로 getter/setter method를 만듭니다.
클래스에도 @Getter, @Setter를 어노테이트 할 수 있습니다.
non-static field들에 모두 어노테이션됩니다.
수동으로 AccessLevel을 조정하여 비활성 할 수 있습니다.
😎 Getter와 Setter를 사용하여 안전하게 캡슐화 하기 위해 사용합니다.
😎 빌더 패턴을 사용하여 복잡한 필드를 관리하기 위해 사용합니다.
😎 모든 필드를 받는 생성자를 만들기 위해 사용합니다.
😎 파라미터가 없는 생성자를 생성하기 위해 사용합니다.
😎 각각의 필드를 하나씩 받는 생성자를 만들기 위해 사용합니다.
😎 해당 Controller를 Swagger에 포함하기 위해 사용합니다.
😎 /api/v1을 사용하여 버전을 확인하기 쉽고 v2의 개발을 편리하게 하기 위해 사용합니다.
😎 View를 반환하는 것이 아닌 JSON 형태로 응답하기 위해 사용합니다.
😎 ResponseEntity를 이용하여 HTTP 응답으로 반환하는 것으로 수정해볼 예정입니다.
😎 jwt 토큰을 HTTP 헤더로 받기 위해 사용합니다.
😎 User의 HTTP body를 받기 위해 사용합니다.
@Api(tags = {"사용자"})
@RequestMapping(value = "/api/v1")
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// 회원가입
@PostMapping("/users/new-user")
public UserDto signUp(@RequestBody UserDto userDto) {
return userService.signUp(userDto);
}
// 로그인
@PostMapping("/users/user")
public UserDto signIn(@RequestBody UserDto userDto) {
return userService.signIn(userDto);
}
// 회원 수정
@PutMapping("/users/user")
public UserDto updateUser(@RequestHeader("Authorization") String userToken, @RequestBody UserDto userDto) {
return userService.updateUser(userToken, userDto);
}
// 회원 삭제
@DeleteMapping("/users/user")
public UserDto deleteUser(@RequestHeader("Authorization") String userToken, @RequestBody UserDto userDto) {
return userService.deleteUser(userToken, userDto);
}
😎 API 문서 : Swagger
😎 회원 수정과 로그인시에는 Jwt 토큰을 받습니다.
😎 User domain의 Service 계층을 나타내기 위해 사용합니다.
😎 Service를 Bean에 등록하기 위해 사용합니다.
😎 ResponseStatusException은 RuntimeException이므로 Transaction이 제대로 동작합니다.
😎 변경 (Transactional 삭제)
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
...
}
😎 위와 같이 구현체인 SimpleJpaRepository에서 이미 Transactional을 어노테이션 한다는 것이 확인되어 불필요한 Transactional 어노테이션을 삭제 합니다.
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
private final UserRepository userRepository;
public UserDto signUp(UserDto userDto) {
if(userRepository.existsByEmail(userDto.getEmail())) throw new ResponseStatusException(CONFLICT, "이미 가입되어 있는 유저입니다");
userDto.setCreatedUser();
User user = UserDto.toEntity(userDto);
return UserDto.of(userRepository.save(user));
}
public UserDto signIn(UserDto userDto) {
User foundUser = userRepository.findByEmail(userDto.getEmail()).orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "존재하지 않는 사용자 입니다."));
if(!foundUser.getPassword().equals(userDto.getPassword())) throw new ResponseStatusException(BAD_REQUEST, "비밀번호가 틀렸습니다.");
return UserDto.of(foundUser);
}
public UserDto updateUser(String userToken, UserDto userDto) {
User user = userRepository.findByEmail(userDto.getEmail()).orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "존재 하지 않는 유저입니다."));
userRepository.updateUser(user, userDto);
return UserDto.of(user);
}
public UserDto deleteUser(String userToken, UserDto userDto) {
User user = userRepository.findByEmail(userDto.getEmail()).orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "존재 하지 않는 유저입니다."));
userRepository.deleteUser(user);
return UserDto.of(user);
}
}
😎 RestAPI를 통해 요청받은 UserController에서 각각의 기능의 UserService를 호출합니다.
😎 호출된 UserService는 기능에 따른 로직을 수행합니다.
😎 UserRepository를 통해 DB의 내용을 수정합니다.
😎 DTO를 UserController에 반환합니다.
😎 User Table과 매핑하기 위해 사용합니다.
😎 Dirty checking을 사용하여 업데이트 하기 위해 사용합니다.
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@DynamicUpdate
public class User {
@Id
private String email;
@Column
@NotNull
private String password;
@Column
@NotNull
private Boolean admin;
@Column
@NotNull
private Boolean isDeleted;
@Column
@NotNull
private Timestamp createdDate;
@Column
@NotNull
private Timestamp updatedDate;
@Column
private Timestamp deletedDate;
@Column
@NotNull
private String creator;
@Column
private String updater;
}
😎 UserTable과 매핑되는 Entity입니다.
😎 User domain의 Repository를 Bean에 등록하기 위해 사용합니다.
😎 SpringDataJpa를 사용하기 위해 사용합니다.
😎 SimpleJpaRepository를 상속하여 사용하는 방법을 고안중입니다.
@Repository
public interface UserRepository extends JpaRepository<User, String> {
boolean existsByEmail(Object email);
Optional<User> findByEmail(Object email);
default void updateUser(User user, UserDto userDto) {
if(userDto.getPassword() != null) user.setPassword(userDto.getPassword());
if(userDto.getAdmin() != null) user.setAdmin(userDto.getAdmin());
if(userDto.getIsDeleted() != null) user.setIsDeleted(userDto.getIsDeleted());
if(userDto.getCreatedDate() != null) user.setCreatedDate(userDto.getCreatedDate());
if(userDto.getUpdatedDate() != null) user.setUpdatedDate(Timestamp.valueOf(LocalDateTime.now()));
if(userDto.getDeletedDate() != null) user.setDeletedDate(userDto.getDeletedDate());
if(userDto.getCreator() != null) user.setCreator(userDto.getCreator());
if(userDto.getUpdater() != null) user.setUpdater("API");
}
default void deleteUser(User user) {
if(user.getIsDeleted().equals(false)) {
user.setIsDeleted(true);
user.setDeletedDate(Timestamp.valueOf(LocalDateTime.now()));
user.setUpdater("API");
}
else {
throw new ResponseStatusException(BAD_REQUEST, "이미 삭제된 유저 입니다.");
}
}
}
😎 DB에 접근하는 DAO 입니다.
😎 updateUser와 deleteUser는 Dirty Checking을 통해 DB의 User를 수정합니다.
😎 [CowAPI] 2-1. TDD Code review를 기반으로 테스트 코드를 작성했습니다.