블로그

김 동현

3주차 정리 [인프런 워밍업 클럽 0기 BE]

미니프로젝트 - 출퇴근 사내관리3주차 부터는 본격적으로 미니프로젝트 개발을 시작했다.이전에 과제에서는 테이블구조까지 다 나왔었지만 미니프로젝트는 api명세만 나와있고나머지 세부적인 구성은 모두 직접 해주어야했다.이 과정에서 어떤 테이블이 api를 만들기에 더 적합한지 고민도 해보고 db성능을 위해서 어떻게 분리해야할지를계속 고민했던것같다. 각 api의 비즈니스상의 에러처리도 고민해보고 코드에서도 더 짧고 이해하기 쉬운 메서드를 작성하려고노력했다. 미니프로젝트https://github.com/cokeholic-kim/comapnySys erdhttps://www.erdcloud.com/d/G28nMCNKTPSy5iBTz 포스팅과제 1-1 프로젝트 구성,Entity생성과제 1-2 팀 등록,조회과제 1-3 직원등록,조회 지금은 기능은 4단계까지 전부 구현해놓고 다른 분들의 코드를 참고하면서 리팩토링을 진행중이다.강의만보고 작성했을때는 나오지않았던 @valid ,@RestControllerAdvice,Jpql 의 내용들이 다른 분들의 코드를 봤을때는 들어있어서 더 좋아 보이는게있으면 내코드를 리팩토링하면서 적용해보고있다.다른 분들의 코드를보면 아직도 배울게 한참 남았다고 느껴진다. 취업준비를 하면서 스프링강의를 듣고있었는데 좋은기회에 프로젝트를 시작할 계기가 된것같다. 그동안 머릿속으로 생각만했던 프로젝트도 시작하기가 무서워서 못하고있었는데 이번기회로 프로젝트를 구성하고 시작해봐도 재미있을것같고 공부할게 많을것같다. 우선은 미니프로젝트를 4단계까지 모두 리팩토링하고 타임리프를 사용해서 프론트까지 구성해볼생각이다.길게는 걸리지않았으면 좋겠다. 모두 작성하면 다른사람들과 공유해서 코드리뷰도 해보고싶다.

백엔드jpa워밍업클럽

김 동현

2주차 강의 정리 [인프런 워밍업 클럽 0기 BE]

2주차의 주 내용은 Jpa를 사용해서 데이터베이스에 접근하는 개발방법이었다.이전에는 일일이 쿼리를 만들고 쿼리를 실행해서 받아온 결과를 응답객체에맵핑해서 사용했다. Jpa를 사용하면서 데이터베이스에 접근하는 객체가 명확하게 구분되고 간단한 조회나 저장 업데이트는 트랜잭션과 영속성컨텍스트를 통해서 자동으로 되도록 처리할수있었다.  1.ORM(Object Relation Mapping)객체 와 DB의 테이블을 매핑 시켜서 RDB테이블을 객체지향적으로 사용하게 해주는 기술이다. 자바 진영에는 대표적으로 JPA와 MyBatis가 있는데 MyBatis는 java의 클래스코드와 작성한 SQL코드를 매핑시켜주어야하는 불편함이있었다. 반면에 JPA는 객체가 db에 연결되어서 SQL을 작성하지않고 표준 인터페이스를 기반으로 처리해준다는 차이가있다. feat. 이전 si에서 3달정도 일해본적이있었는데 지금 돌아와서 생각해보면 거기서는 Mybatis를 썼던것같다. 에러코드에서 본듯한 기억이.. 그때는 프론트엔드과정을 마치고 입사해서 자바에대한 기초도없고 스프링도모르고 사용해서 뭐가뭔지 몰랐는데. 지금보니 그게 이거였다는 생각이.. 비즈니스 로직이다보니 쿼리가 길면 몇십줄에서 거의 백줄까지 가는경우도 있었다.안정성 때문이라그랬나? Jpa를 썻다면 쿼리는 짧게쓰고 객체에서 처리할수도있지않을까 싶은 쿼리문들이 몇개 생각이난다..  2.트랜잭션트랜잭은 db에서 한 로직이 실행되어야하는 단위를 의미한다. 예를들어 은행에서 한쪽에서는 돈이빠지고 한쪽 에서는 돈이 들어가는걸 각각의 트랜잭션으로 돌린다면한쪽에서 돈이 빠졌는데 다른쪽에서는 에러가 발생해서 돈이 들어가지않을수도 있다. 이런 경우는 비즈니스로직상 대형 사고이기때문에 이런 한번에 일어나야하는 일련의 작업들을 트랜잭션으로 묶는다.두개를 트랜잭션으로 묶게되면 한쪽에서 돈이빠지고 중간에 에러가발생해서 뒤의작업이 실행되지않더라도 DB가 롤백되면서 작업을 취소시켜버린다. 만약 트랜잭션안의 쿼리가 모두 실행되었다면 커밋을 하게되고 그렇게되면 db에 반영이 되게되는것이다.  3.영속성컨텍스트JPA를 사용하고 service로직에 @Transactional 어노테이션을 달아주게되면 영속성컨텍스트가 적용된다.영속성 컨텍스트가 적용되면 해당 로직내에서 entity객체가 수정되면 따로 업데이트를 해주지않아도 영속성 컨텍스트가 변화를 저장해두었다가 로직이끝날때 변화를 모두 업데이트해주는 작업을 자동으로 해준다. Jpa를 사용하면서 굉장히 편해졌지만 아직 익숙하지않아서 사용하는게 어색하고 찾아보면서하느라 시간이 조금 걸리는것같다. 익숙해진다면 많은 시간을 단축시킬수있을것같다. 2주차 후반부터는 미니프로젝트 실습을 하고있는데 이전처럼DB의 테이블에 어떤테이블에 어떤필드가 들어가야할지 다 나와있지는 않아서 테이블을 구성하는것도 필요한 기능을보고 고민하면서 하게되는것같다. erd도 작성해보고 이후에는 명세서나 필요한 레퍼런스도 노션에 간단하게나마 작성해볼 생각이다. 

백엔드워밍업백엔드jpa

인프런 워밍업 클럽 0기 - 백엔드 코스 (과제7)

문제 1. JPA 적용하기이전의 프로젝트 상에서 JdbcTemplate을 사용해 DB에 접근했었습니다. JdbcTemplate과 같은 경우 쿼리문을 string으로 작성하기 때문에 오류를 실제 동작 상황에서 파악할 수 있는 안 좋은 단점이 있었습니다. 이를 보완하기 위해 좀 더 객체와 테이블을 바로 매핑해서 코드로써 DB에 접근할 수 있는 JPA를 활용해보도록 하겠습니다. 그 중에서 JPA를 Spring에서 좀 더 간편하게 사용할 수 있는 Spring Data Jpa를 활용하고 Hibernate를 구현체로 사용해보도록 하겠습니다. Entity 생성@Entity public class Fruit { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private LocalDate warehousingDate; private long price; private boolean isSold; }Jpa에서 테이블과 매핑할 Entity를 생성해줍니다. Repository 생성public interface FruitJpaRepository extends JpaRepository<Fruit,Long> { }Jpa를 활용할 Repository를 사용하는 방법은 JpaRepository<Entity_CLASS,ID_TYPE>를 상속받은 인터페이스를 선언해주는 것입니다. 이렇게 되면 JpaRepository에서 제공하는 간단한 메서드를 사용할 수 있고, 간단한 쿼리메서드를 작성해서 자신만의 쿼리를 작성할 수도 있습니다.저와 같은 경우 이전의 합계 계산 쿼리를 직접 작성해봤었습니다. 이전의 GROUP_BY를 활용한 코드를 이번에는 JPQL로 작성해보도록 하겠습니다. @Query(value = "SELECT new com.group.libraryapp.dto.fruit.FruitStatProjection(SUM(f.price),f.isSold) FROM Fruit f GROUP BY f.isSold") List<FruitStatProjection> findByIsSold();JPQL 또한 실제 Entity를 통해 쿼리를 작성할 수 있습니다. Fruit 엔티티의 isSold 필드를 통해 그루핑하고 각 그룹의 price 값의 총합을 구하는 쿼리를 작성해보았습니다. 해당 Repository를 활용한 Service는 이전과 거의 동일한 형태이므로 작성하지 않도록 하겠습니다.  문제 2. count queryDB에 해당 이름을 가진 과일 개수를 세는 쿼리를 작성해보도록 하겠습니다.Jpa는 count로 시작하는 쿼리 메서드를 COUNT 쿼리가 나가도록 정해두었습니다. 그리고 이름을 통해 필터링할 것이기 때문에 countByName(String name)으로 쿼리메서드를 지정하면 Spring Data Jpa가 이를 통해 실제 구현 코드를 스스로 작성해줍니다. public interface FruitJpaRepository extends JpaRepository<Fruit,Long> { ... Long countByName(String name); }위와 같이 작성한다면 바로 사용할 수 있습니다.이를 사용하는 서비스와 컨트롤러 메서드는 별다른 점이 없으므로 따로 작성하지는 않겠습니다.  문제 3. option querypath: /api/v1/fruit/listquery: option, priceresponse{ "name": String, "warehousingDate: LocalDate, "price": long }option이 GTE일 경우 Greater Than Equal 연산을 수행하고 LTE일 경우 Less Than Equal 연산을 수행합니다. 우선 option과 price를 담아줄 DTO를 선언해주었습니다.public class FruitResponse { private String name; private LocalDate warehousingDate; private long price; }response가 FruitResponse의 list 형식으로 반환될 수 있도록 했습니다. @RequestParam과 같은 경우 option, price 두 인자를 따로 따로 받아야 함으로, @ModelAttribute을 활용해 두 쿼리파라미터를 한 번에 받아오도록 하겠습니다.FruitSearchpublic class FruitSearch { private String option; private long price; }FruitController @GetMapping("/api/v1/fruit/list") public List<FruitResponse> getFruitList(@ModelAttribute FruitSearch fruitSearch){ return fruitServiceV2.getFruitList(fruitSearch); }JpaRepositorypublic interface FruitJpaRepository extends JpaRepository<Fruit,Long> { ... List<Fruit> findByPriceGreaterThanEqual(long price); List<Fruit> findByPriceLessThanEqual(long price); }쿼리 메서드를 두 개 선언해준 뒤 옵션에 따라 각각 실행될 수 있도록 해주었습니다. Service public List<FruitResponse> getFruitList(FruitSearch fruitSearch) { List<Fruit> result = fruitSearch.isGTE() ? fruitJpaRepository.findByPriceGreaterThanEqual(fruitSearch.getPrice()) : fruitJpaRepository.findByPriceLessThanEqual(fruitSearch.getPrice()); return result.stream().map(FruitResponse::new).collect(Collectors.toList()); } 응답 예시[ { "name": "사과", "warehousingDate": "2024-02-25", "price": 8000 }, { "name": "사과", "warehousingDate": "2024-02-25", "price": 3000 }, { "name": "배", "warehousingDate": "2024-02-25", "price": 4000 }, { "name": "귤", "warehousingDate": "2024-02-25", "price": 1000 }, { "name": "수박", "warehousingDate": "2024-02-25", "price": 10000 } ]

백엔드springdatajpajpajpqlmodelattribute쿼리메서드

채널톡 아이콘