내 인생의 가장 중요한 결정을 할 때에 다가와준 사람 그 결정과 과정을 함께 해준 사람 어쩌면 내 인생에서 가장 도움이된 사람이 아닐까 싶다. 나는 그를 귀인이라고 부르기로 했다. 3년동안 연애 하면서 정말 상처도 많이 받고 맞지 않는 부분이 너무 많아 정말 힘들었지만 중간 중간 포기하고 또 포기를 했던 순간들도 있었지만...
그래도 다시 일어서서 끝까지 그 사람을 믿고 따라간 것은 내 인생에서 가장 잘한 일 같다. 그리고 가장 오래 아니 평생 기억될 것이다. 정말 많이 밉고 정말 많이 화나지만 정말 말로 표현할 수 없는 정도로 고마운 사람이고 정말 아직도 많이 사랑하는 사람이다..
덕분에 내가 볼 수 없었던 나의 단점을 볼 수 있었고 내가 정말 그런 사람을 되고 싶지 않기에 정말 꼭 고쳐보려고 한다. 내 생각이라고 합리화 하며 상대에게 이해하라고 강요하지 않기. 내 생각을 정확하게 전달 한다며 상황 장황하게 늘어놓지 않기. 단어 선택에 신중하게 하기. 느려도 괜찮으니까 빨리 이해시키지 않아도 되니까 아니 이해 시킨다는 생각은 버려야지..신중하게 생각하고 조심히 이야기 하기. 특히 갈등 상황에서는 쉽게 감정 상할 가능성이 높으니까..
내가 너무 자기합리화를 해서 내 생각을 너무 강요했다.. 정말 어리석은 짓이지..느려도 괜찮아, 조급해 할 필요 없어, 느려서 상처 주고 받는 것 보다 섣부른 판단에 섣부른 언행으로 상대에게 상처주는 행동은 절대 하면 안된다. 이건 정말을 나쁜 행동이다.
특히 마음이 많이 여렸던 그 사람. 조심스러웠던 그 사람 나에게 이해를 공감을 존중을 원했던 사람..나에게 정말 고마웠던 상대였고 정말 많이 사랑했던 놓치고 싶지 않았던 내 인생에 가장 기억될 귀인이기에 상처 주기 싫다는 조급함에 빨리 이해시켜야 한다는 합리화로 그를 너무나도 여린 그를 내가 상처를 주고 나를 포기하게 만들었다..
이번 3주차에서는 JPA 연관관계 매핑하기 강의에서 만들었던 도서관리 프로그램 배포하기를 하였다.
JPA를 조금 더 객체지향적으로 코드를 고쳐볼 수 있었다.
[3주차 과제]
3주차에서 진행한 미니프로젝트는 출퇴근 사내 관리프로그램이었다.
JPA에 대한 개념이 많이 부족해 생각한 대로 프로그램은 돌아가는데 제대로 코드를 작성한게 맞는지, 코드는 깔끔하게 짰는지 등등 많은 생각이 들었다. 그래도 이번 강의를 통해서 JPA가 무엇인지 어떻게 사용하는지 그리고 스프링에 대한 개념에 대해 조금 더 와닿게 되었다.
오래된 취준 기간으로 약간의 강제성이 필요할 때 우연히 인프런 워밍업 클럽 스터디를 접하게 되었고, 현재 나에게 제일 필요했던 공부인 JPA를 공부할 수 있어 정말 뜻 깊은 스터디였다. 단순 강의만 듣는 것이 아닌 미션과 미니프로젝트를 병행하여서 강의 내용을 보다 더 쉽게 이해할 수 있었다.
이번 스터디로 습득한 지식을 바탕으로 조금 더 공부하여 나머지 2,3,4단계까지 마무리 해보고 개인 서버에 배포까지 해볼 예정이다.
@Entity
@Getter
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false, length = 20)
private String name;
@Column(nullable = false, length = 20)
private String manager;
@Column(nullable = false)
private long employeeCnt;
protected Team(){}
public Team(String name, String manager, long employeeCnt) {
this.name = name;
this.manager = manager;
this.employeeCnt = employeeCnt;
}
public void updateEmployeeCnt() {
this.employeeCnt++;
}
public void updateManager(String name) {
this.manager = name;
}
}
1
Controller
@RestController
@RequestMapping("/attendance/team")
public class TeamController {
private final TeamService service;
public TeamController(TeamService service) {
this.service = service;
}
@PostMapping("/resister_team")
public void resisterTeam(@RequestBody RegisterTeamRequest request) {
service.insertTeam(request);
}
}
Service
@Service
public class TeamService {
private final TeamRepository repository;
public TeamService(TeamRepository repository) {
this.repository = repository;
}
public void insertTeam(RegisterTeamRequest request) {
repository.findByName(request.getName()).ifPresent(team -> {
throw new IllegalArgumentException("이미 존재하는 팀 이름입니다.");
});
repository.save(new Team(request.getName(),
request.getManager(), request.getMemberCnt()));
}
}
RegisterTeamRequest 객체에서 팀 이름을 꺼내와 등록된 팀이 있는지 확인해준다.
@RestController
@RequestMapping("/attendance/employee")
public class EmployeeController {
private final EmployeeService service;
public EmployeeController(EmployeeService service) {
this.service = service;
}
@PostMapping("/register_employee")
public void resisterEmployee(@RequestBody ResisterEmployeeRequest request) {
service.insertEmployee(request);
}
}
Service
@Service
public class EmployeeService {
private final EmployeeRepository repository;
private final TeamService teamService;
public EmployeeService(EmployeeRepository repository, TeamService teamService) {
this.repository = repository;
this.teamService = teamService;
}
@Transactional
public void insertEmployee(ResisterEmployeeRequest request) {
if (request.getRole() == null) {
throw new IllegalArgumentException("직급을 입력해주세요.");
}
repository.findById(request.getId()).ifPresent(employee -> {
throw new IllegalArgumentException("이미 등록된 직원입니다.");
});
repository.save(new Employee(request.getName(), request.getTeamName(),
request.getRole(), request.getBirthday(), request.getWorkStartDate()));
if (Role.MANAGER.equals(request.getRole())) {
teamService.updateManager(request.getTeamName(), request.getName());
}
teamService.updateEmployeeCnt(request.getTeamName());
}
}
Role이 null인지 확인하고 없다면 예외를 발생시킨다.
Id를 사용하여 이미 등록되어 있는 직원인지 확인한다.
있다면 예외를 발생시키고 없다면 새로운 직원을 만들어준다.
Employee 객체를 만들어 새로운 직원을 저장한다.
이때 Role이 매니저인 경우 teamService의 updateManager메서드를 실행시킨다.
teamService의 updateEmployeeCnt 메서드를 실행하여 해당 팀의 직원 수를 증가시킨다.
TeamService
@Service
public class TeamService {
private final TeamRepository repository;
public TeamService(TeamRepository repository) {
this.repository = repository;
}
@Transactional
public void updateEmployeeCnt(String teamName) {
Team team = findTeamByName(teamName);
team.updateEmployeeCnt();
}
@Transactional
public void updateManager(String teamName, String name) {
Team team = findTeamByName(teamName);
team.updateManager(name);
}
private Team findTeamByName(String name) {
return repository.findByName(name)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 팀 입니다."));
}
}
@RestController
public class FruitController {
private final FruitServiceV2 fruitService;
public FruitController(FruitServiceV2 fruitService) {
this.fruitService = fruitService;
}
@GetMapping("/api/v1/fruit/stat")
public FruitResponse selectFruits(@RequestParam String name) {
return fruitService.selectFruits(name);
}
@PostMapping("/api/v1/fruit")
public void insertFruits(@RequestBody FruitRequest fruitRequest) {
fruitService.insertFruits(fruitRequest);
}
@PutMapping("/api/v1/fruit")
public void updateFruits(@RequestBody FruitUpdateRequest request) {
fruitService.updateFruits(request);
}
}
Service
@Service
public class FruitServiceV2 {
private final FruitRepository fruitRepository;
public FruitServiceV2(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public FruitResponse selectFruits(String name) {
long salesAmount = fruitRepository.selectSalesAmount(name);
long notSalesAmount = fruitRepository.selectNotSalesAmount(name);
return new FruitResponse(salesAmount, notSalesAmount);
}
public void insertFruits(FruitRequest request){
fruitRepository.save(new Fruits(request.getName(),
request.getWarehousingDate(), request.getPrice()));
}
public void updateFruits(FruitUpdateRequest request) {
Fruits fruit = fruitRepository.findById(request.getId())
.orElseThrow(() -> new IllegalArgumentException("해당하는 과일이 없습니다."));
fruit.updateFruit();
fruitRepository.save(fruit);
}
}
Repository
public interface FruitRepository extends JpaRepository<Fruits, Long> {
@Query(value = "SELECT SUM(price) as salesAmount FROM fruits
WHERE is_sold = true AND name = :name", nativeQuery = true)
long selectSalesAmount(@Param("name") String name);
@Query(value = "SELECT SUM(price) as notSalesAmount FROM fruits
WHERE is_sold = false AND name = :name", nativeQuery = true)
long selectNotSalesAmount(@Param("name") String name);
}
Sql에서 SUM() 함수를 사용하여야 하는데 어떻게 해야할지 고민하다가 검색해보니 @Query어노테이션으로 사용자 정의 쿼리를 사용할 수 있는 것을 알게 되었다. @Query는 실행할 메서드 위에 정적 쿼리를 작성하는 방식으로 사용된다.
메서드에 @Param 어노테이션으로 조건에 사용할 변수를 지정해주어 사용할 수 있었다.
Fruit
@Entity
public class Fruits {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false, length = 20)
private String name;
@Column(nullable = false)
private LocalDate warehousingDate;
@Column(nullable = false)
private long price;
@Column(nullable = false, columnDefinition = "boolean default false")
private boolean isSold = false;
protected Fruits() {}
public Fruits(String name, LocalDate warehousingDate, long price) {
this.name = name;
this.warehousingDate = warehousingDate;
this.price = price;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public LocalDate getWarehousingDate() {
return warehousingDate;
}
public long getPrice() {
return price;
}
public boolean isSold() {
return isSold;
}
public void updateFruit() {
this.isSold = true;
}
}
기존 코드를 JPA로 고치느라 엄청 애먹었다..😭
강사님 코드를 보며 하나하나씩 바꿔 보았지만 SQL의 SUM() 함수를 어떻게 사용해야할지 감이 오지 않았고, 어찌어찌 다 고치고 나니 Property가 없다는 에러가 났다.
No property 'updateFruits' found for type 'Fruit'
검색 해보니 클래스 명이 맞지 않거나 카멜케이스로 인한 오류 존재하지 않은 필드명을 사용한 쿼리 메서드를 만들었을 경우 오류가 난다는 것이었다.
그런데 아무리 봐도 내 코드엔 이상이 없었다. 그렇게 몇시간을 찾아보았는데..인터페이스에 내가 Service에 있는 메서드를 다 적어 놓은 것이었다..이걸 지우고 나니 아래와 같은 오류가 낫다.
Parameter 0 of constructor in com.group.libraryapp.controller.fruits.FruitService required a bean of type 'com.group.libraryapp.controller.fruits.FruitRepository' that could not be found.
FruitRepository를 찾을 수 없다는 것이었다. 찾아보니 이전에 만들었던 FruitService코드에 FruitRepository가 주입 된 상태였고 나는 FruitRepositoryV2를 사용하려고 했기 때문에FruitRepository를 주석 처리하여 파일을 사용하지 않게 하니 당연히 못찾는 것이었다. 그래서 FruitServiceV2까지 주석 처리를 한 뒤 오류는 해결 되었고 프로그램이 정상적으로 실행 될 수 있었다.
Controller
@RestController
public class FruitController {
private final FruitServiceV2 fruitService;
public FruitController(FruitServiceV2 fruitService) {
this.fruitService = fruitService;
}
@GetMapping("/api/v1/fruit/count")
public FruitCountResponse countFruits(@RequestParam String name) {
return fruitService.countFruits(name);
}
}
Service
@Service
public class FruitServiceV2 {
private final FruitRepository fruitRepository;
public FruitServiceV2(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public FruitCountResponse countFruits(String name) {
long count = fruitRepository.countByName(name);
return new FruitCountResponse(count);
}
}
Repository
public interface FruitRepository extends JpaRepository<Fruits, Long> {
long countByName(String name);
}
FruitCountResponse
public class FruitCountResponse {
private long count;
public FruitCountResponse(long count) {
this.count = count;
}
public long getCount() {
return count;
}
}
[실행 결과]
사과를 조회 했을 때 4개가 나오는 것을 볼 수 있다.
Controller
@RestController
public class FruitController {
private final FruitServiceV2 fruitService;
public FruitController(FruitServiceV2 fruitService) {
this.fruitService = fruitService;
}
@GetMapping("/api/v1/fruit/list")
public List<FruitListResponse> listFruits(@RequestParam String option, @RequestParam long price) {
return fruitService.listFruits(option, price);
}
}
Service
@Service
public class FruitServiceV2 {
private final FruitRepository fruitRepository;
public FruitServiceV2(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public List<FruitListResponse> listFruits(String option, long price) {
if (option.equals("GTE")) {
return fruitRepository.findAllByPriceGreaterThanEqualAndIsSoldIsFalse(price)
.stream()
.map(fruit -> new FruitListResponse(fruit.getName(),
fruit.getPrice(), fruit.getWarehousingDate()))
.collect(Collectors.toList());
} else if (option.equals("LTE")) {
return fruitRepository.findAllByPriceLessThanEqualAndIsSoldIsFalse(price)
.stream()
.map(fruit -> new FruitListResponse(fruit.getName(),
fruit.getPrice(), fruit.getWarehousingDate()))
.collect(Collectors.toList());
}
return new ArrayList<>();
}
}
여러 결과를 받아야 하기 때문에 List타입으로 나올 수 있도록 하였다.
여기서 findAllByPriceGreaterThanEqualAndIsSoldIsFalse를 사용하였는데 하나하나 살펴보자면
findAll: 모든 결과를 가져오고
price: price를 조건으로 가져오는데
GreaterThanEqual: 해당 조건 이상인 것
LessThanEqual: 해당 조건 이하인 것
AndIsSoldIsFalse: 그리고 IsSold 칼럼이 False인 것
이라고 해석할 수 있다.
즉, 입력한 가격이 이상이면서 IsSold가 false인 것을 조회하는 것이다.
SELECT * FROM FRUITS WHERE PRICE > 10000 AND IS_SOLD = FALSE;