인치어
수강평 작성수
-
평균평점
-
블로그
전체 10
2024. 03. 10.
0
인프런 워밍업 클럽 백엔드 - 3주차 발자국
11 ~ 12일차간단하게 배포를 하는 과정을 배웠다. 먼저 profile이라는 개념을 다루어 컴퓨터와 서버에서 원하는 DB 시스템을 사용할 수 있게 되었다. 배포를 하기위해 git과 github를 통해 코드를 원격에 저장하고, AWS의 EC2를 통해 무료로 간단한 클라우드 컴퓨터를 빌리는 과정이었다. 13일차11 ~ 12일차에는 배포 준비를 했다면 13일차에는 배포를 해보았다. 서버는 Linux 환경이기 때문에 먼저 명령어들을 다루었다. 여러 명령어를 통해 배포를 위한 프로그램인 git, java, mysql을 설치했다.DB를 만들고 프로젝트를 가져와서 빌드하고 실행시켜 배포를 할 수 있었다. 14일차배포까지 마무리하고 Spring Boot의 이모저모를 배웠다. 먼저 build.gradle에 작성되어있는 문구에 대해 파악해보았다. 그리고 간편한 설정, 의존성 관리, 확장성 등 Spring과 Spring Boot의 차이점에 대해서도 알 수 있었다. application.properties에 대해서도 배웠는데 이는 yml같은 기능이지만 다른 문법을 가지는 파일이다. lombok에 대해서도 배웠는데 lombok은 getter나 생성자 등을 자동으로 만들어주어 코드를 작성하는 번거로움을 줄여준다. 마지막으로 Spring Boot의 버전을 업데이트하는 것을 배웠다. 달라진 점이 있기에 여러 부분을 바꿔주어야 하고 버전 바꾸는 것이 왜 오래 걸리는 작업인지 알 수 있었다.3주가 짧은 시간이었지만 많은 것을 배울 수 있었다. 작고 간단하지만 배포를 처음해봐서 신기했고, 미니프로젝트와 과제를 하면서 클린 코드를 배우고 신경을 많이 쓴 것 같다. 다음에 프로젝트나 작업을 할 때 이번 스터디를 통해 코드 작성에 심혈을 기울이게 되는 경험을 배운 것 같다.

2024. 03. 03.
0
인프런 워밍업 클럽 백엔드 - 2주차 발자국
요약6일차스프링 컨테이너와 스프링 빈에 대해서 배웠다. 스프링 컨테이너는 객체를 생성하고 관리하여 애플리케이션의 유연성을 높일 수 있다. 스프링 빈은 컨테이너가 관리하는 객체로, 어노테이션을 통해 정의할 수 있었다. 이를 통해 코드의 재사용성과 유지보수성을 향상시키는 방법을 배웠다. 7 ~ 8일차JPA, Spring Data JPA, 트랜잭션, 영속성 컨텍스트에 대해 배웠다. MySQL 쿼리문을 계속 작성해서 힘들고 복잡했던 코드를 간단하게 작성할 수 있게 되었다. 여러 줄의 코드를 하나의 기능으로 만들어 실행할 수도 있게 되었다. 이를 통해, 훨씬 더 쉽게 실제 테이블에 저장할 수 있다. 9 ~ 10일차책 생성, 대출, 반납 API을 개발했고 연관관계를 이해함으로써 객체지향적으로 설계하는 방법을 배웠다. 객체들 간의 의미있는 상호작용을 표현할 수 있게 되었다. 미션6일차 과제 - https://www.inflearn.com/blogs/67557일차 과제 - https://www.inflearn.com/blogs/6870 2주차에는 미니 프로젝트를 진행하면서 배웠던 내용을 응용할 수 있는 시간이 많았다. 미니 프로젝트를 진행하니까 이해가 잘 되지 않았던 부분도 직접 코드를 작성해 복습하며 이해할 수 있는 시간이 되는 것 같다.

2024. 02. 26.
0
인프런 워밍업 클럽 백엔드 - 일곱 번째 과제
문제 1과제 #6에서 만들었던 Fruit 기능들을 JPA를 이용하도록 변경해보세요.먼저 domain 패키지를 만들어서 Fruit와 FruitRepository를 생성했습니다.Fruit@Entity public class Fruit { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 20) private String name; private LocalDate warehousingDate; private Long price; private boolean stat = false; protected Fruit() { } public Fruit(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 isStat() { return stat; } public void updateFruitStat(boolean stat) { this.stat = stat; } }FruitRepositorypublic interface FruitRepository extends JpaRepository { Optional> findAllByName(String name); }이름으로 검색할 수 있는 메소드, findByAllName을 생성해줍니다.기존에 있던 서비스를 수정했습니다.FruitServiceV2@Service public class FruitServiceV2 { private final FruitRepository fruitRepository; public FruitServiceV2(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public void saveFruit(FruitRequest request) { fruitRepository.save(new Fruit(request.getName(), request.getWarehousingDate(), request.getPrice())); } public void soldFruit(FruitRequest request) { Fruit fruit = fruitRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); fruit.updateFruitStat(true); fruitRepository.save(fruit); } public FruitSaleResponse readFruitSoldPrice(String name) { List fruits = fruitRepository.findAllByName(name) .orElseThrow(IllegalArgumentException::new); long salesAmount = 0; long notSalesAmount = 0; for (Fruit fruit : fruits) { if (fruit.isStat()) { salesAmount += fruit.getPrice(); } else { notSalesAmount += fruit.getPrice(); } } return new FruitSaleResponse(salesAmount, notSalesAmount); } }이렇게 작성하고 실행했을 때 문제가 발생합니다.java.sql.SQLSyntaxErrorException: Unknown column 'warehousing_date' in 'field list'이런 에러가 발생하는데 기본적으로 쿼리를 요청할 때 스네이크 케이스를 기준으로 요청하기 때문입니다. 데이터베이스에서 fruit 테이블을 생성할 때 컬럼명을 카멜케이스로 작성했기 때문에 에러가 발생합니다. 이 문제를 해결하기 위해서 application.yml에 네이밍 전략을 추가하도록 하겠습니다.spring: jpa: hibernate: ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl properties: hibernate: show_sql: true format_sql: true dialect: org.hibernate.dialect.MySQL8Dialectphysical-strategy를 org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl로 설정하면 정상적으로 작동하게 됩니다. 문제 2우리는 특정 과일을 기준으로 지금까지 우리 가게를 거쳐갔던 과일 개수를 세고 싶습니다.에서 만들었던 과일 Entity Class를 이용해 기능을 만들어 보세요!예를 들어(1, 사과, 3000원, 판매 O)(2, 바나나, 4000원, 판매 X)(3, 사과, 3000원, 판매 O)와 같은 세 데이터가 있고, 사과를 기준으로 과일 개수를 센다면, 우리의 API는 2를 반환할 것입니다.구체적인 스펙은 다음과 같습니다.HTTP method : GETHTTP path : /api/v1/fruit/countHTTP queryname : 과일 이름예시 GET /api/v1/fruit/count?name=사과HTTP 응답 Body{ "count": long }HTTP 응답 Body 예시{ "count": 2 } 저는 판매된 특정 과일만 count 했습니다.먼저 컨트롤러에 추가해줍니다.Controller@GetMapping("/api/v1/fruit/count") public FruitSoldCountResponse countSoldFruit(String name) { return fruitService.countSoldFruit(name); } RepositoryInteger countByNameAndStat(String name, boolean stat);리포지토리에 이름과 판매 상태로 조회하는 메소드를 작성합니다. SELECT * FROM fruit WHERE name = ? AND stat = ? Servicepublic FruitSoldCountResponse countSoldFruit(String name) { return new FruitSoldCountResponse(fruitRepository.countByNameAndStat(name, true)); }판매된 과일만 조회하기 때문에 stat을 받는 부분에 true를 넣었습니다. 그리고 Body 형태로 받기 위해 Response를 하나 생성해주었습니다. Responsepublic class FruitSoldCountResponse { private Integer count; public FruitSoldCountResponse(Integer count) { this.count = count; } public Integer getCount() { return count; } }예시대로 실행했을 때,결과가 잘 나오는 것을 볼 수 있습니다. 문제 3우리는 아직 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록을 받아보고 싶습니다.구체적인 스펙은 다음과 같습니다.HTTP method : GETHTTP path : /api/v1/fruit/listHTTP queryoption : "GTE" 혹은 "LTE"라는 문자열이 들어온다.GTE : greater than equal의 의미LTE : less than equal의 의미price : 기준이 되는 금액이 들어온다.예시 1 - GET /api/v1/fruit/list?option=GTE&price=3000판매되지 않은 3000원 이상의 과일 목록을 반환해야 한다.예시 2 - GET /api/v1/fruit/list?option=LTE&price=5000판매되지 않은 5000원 이하의 과일 목록을 반환해야 한다.HTTP 응답 Body[{ "name": String, "price": long, "warehousingDate": LocalDate, }, ...]HTTP 응답 Body 예시[ { "name": "사과", "price": 4000, "warehousingDate": "2024-01-05", }, { "name": "바나나", "price": 6000, "warehousingDate": "2024-01-08", } ]먼저 컨트롤러에 추가해줍니다.Controller@GetMapping("/api/v1/fruit/list") public List getFruitsByPriceRange(@RequestParam String option, @RequestParam Long price) { return fruitService.getFruitsByPriceRange(option, price); } RepositoryOptional> findAllByPriceGreaterThanEqual(Long price); Optional> findAllByPriceLessThanEqual(Long price);"GTE" 문자열을 받으면 실행 findAllByPriceGreaterThanEqual = SELECT * FROM fruit WHERE price >= ?"LTE" 문자열을 받으면 실행 findAllByPriceLessThanEqual = SELECT * FROM fruit WHERE price Servicepublic List getFruitsByPriceRange(String option, Long price) { if ("GTE".equals(option)) { return fruitRepository.findAllByPriceGreaterThanEqual(price).orElseThrow(IllegalArgumentException::new); } else if ("LTE".equals(option)) { return fruitRepository.findAllByPriceLessThanEqual(price).orElseThrow(IllegalArgumentException::new); } else { throw new IllegalArgumentException(); } }option으로 받는 문자열을 비교해서 각 문자열에 맞는 쿼리 실행할 수 있도록 if문을 사용했습니다. "GTE"나 "LTE"가 아닌 다른 문자열이 오면 예외 처리를 할 수 있도록 했습니다. 응답 예시처럼 결과가 나올 수 있도록 Response 생성했습니다. Responsepublic class FruitJpaResponse { private String name; private Long price; private LocalDate warehousingDate; public String getName() { return name; } public Long getPrice() { return price; } public LocalDate getWarehousingDate() { return warehousingDate; } public FruitJpaResponse(String name, Long price, LocalDate warehousingDate) { this.name = name; this.price = price; this.warehousingDate = warehousingDate; } } 결과

2024. 02. 25.
0
인프런 워밍업 클럽 백엔드 - 1주차 발자국
스터디를 신청한 이유는 동기부여를 얻기 위해서 였습니다. 스터디를 통해 환경 설정, 네트워크 기초, HTTP API, 데이터베이스, 클린 코드, 스프링 컨테이너 등의 내용을 배웠습니다. 스터디를 진행하면서 진도표와 과제를 통해 목표를 정하고 노력할 수 있었으며, 과제를 블로그처럼 기록하고 발자국(회고록)을 작성하면서 다시 한번 내용을 정리하고 동기부여를 얻을 수 있었습니다.스터디를 진행하면서 좋았던 점은 목표를 달성하기 위한 구체적인 계획과 다른 참여자들과의 공유가 있어서 더욱 몰입할 수 있었습니다. 또한, 과제를 통해 스스로 내용을 이해하고 정리할 수 있는 기회를 얻었고, 발자국을 작성하면서 스스로를 돌아보는 시간을 가질 수 있었습니다. 이런 경험을 통해 더 많은 동기부여를 얻을 수 있었습니다.스터디를 진행하면서 발자국을 작성하고 블로그에 기록하는 것이 중요함을 깨달았습니다. 공부한 내용을 정리하고 다시 한번 되짚어보는 것은 내용을 깊이 이해하고 지식을 고정하는 데에 도움이 되었습니다. 또한, 다른 참여자들의 발자국을 통해 다양한 시각과 경험을 접할 수 있어서 보다 넓은 시야를 가질 수 있었습니다.앞으로도 스터디나 공부를 할 때에는 발자국을 작성하고 블로그에 기록하는 습관을 가지고 싶습니다. 이를 통해 공부한 내용을 정리하고 다시 한번 복습할 수 있으며, 다른 사람들과의 공유를 통해 더 많은 지식을 얻을 수 있을 것 같습니다.

2024. 02. 24.
0
인프런 워밍업 클럽 백엔드 - 여섯 번째 과제
문제 1과제 #4에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다! 문제 2문제 1에서 코드가 분리되면 FruitController / FruitService / FruitRepository가 생겼을 것입니다.기존에 작성했던 FruitRepository를 FruitMemoryRepository와 FruitMysqlRepository로 나누고@Primary 어노테이션을 활용해 두 Repository를 바꿔가며 동작시킬 수 있도록 코드를 변경해보세요!@Qualifier 어노테이션을 사용해도 좋습니다! 기존의 Controller@RestController public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/api/v1/fruit") public void warehousing(@RequestBody FruitRequest request) { String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); } @PutMapping("/api/v1/fruit") public void sold(@RequestBody FruitRequest request) { String readSql = "SELECT * FROM fruit WHERE id = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String sql = "UPDATE fruit SET stat = true WHERE id = ?"; jdbcTemplate.update(sql, request.getId()); } @GetMapping("/api/v1/fruit/stat") public FruitSaleResponse readSoldFruit(@RequestParam String name) { String readSql = "SELECT * FROM fruit WHERE name = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String sql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY stat"; List list = jdbcTemplate.query(sql, (rs, rowNum) -> { long amount = rs.getLong("SUM(price)"); return amount; }, name); return new FruitSaleResponse(list.get(0), list.get(1)); } } 수정된 Conroller@RestController public class FruitController { private final FruitService fruitService; public FruitController(FruitService fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public void warehousing(@RequestBody FruitRequest request) { fruitService.saveFruit(request); } @PutMapping("/api/v1/fruit") public void sold(@RequestBody FruitRequest request) { fruitService.soldFruit(request); } @GetMapping("/api/v1/fruit/stat") public FruitSaleResponse readSoldFruit(@RequestParam String name) { return fruitService.readFruitSoldPrice(name); } } Service@Service public class FruitService { private final FruitRepository fruitRepository; public FruitService(@Qualifier("사용할 리포지토리") FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public void saveFruit(FruitRequest request) { fruitRepository.saveFruit(request.getName(), request.getWarehousingDate(), request.getPrice()); } public void soldFruit(FruitRequest request) { if (fruitRepository.isFruitNotExistById(request.getId())) { throw new IllegalArgumentException(); } fruitRepository.soldFruit(request.getId()); } public FruitSaleResponse readFruitSoldPrice(String name) { if (fruitRepository.isFruitNotExistByName(name)) { throw new IllegalArgumentException(); } return fruitRepository.readFruitSoldPrice(name); } }@Qualifier 어노테이션안에 사용할 리포지토리를 작성하면 된다.'fruitMemoryRepository'를 작성하면 FruitMemoryRepository를 사용하고,'fruitMysqlRepository'를 작성하면 FruitMysqlRepository를 사용할 수 있다. Repositorypackage com.example.task.repository.fruit; import com.example.task.dto.fruit.response.FruitSaleResponse; import java.time.LocalDate; import java.util.Date; public interface FruitRepository { // 과일 저장 void saveFruit(String name, LocalDate warehousingDate, long price); // 과일 판매 void soldFruit(long id); // 해당 이름의 과일 판매 금액, 판매되지 않은 금액 FruitSaleResponse readFruitSoldPrice(String name); // 해당 ID의 과일이 존재하는지 (없으면 true) boolean isFruitNotExistById(long id); // 해당 이름의 과일이 존재하는지 (없으면 true) boolean isFruitNotExistByName(String name); } MemoryRepository@Repository public class FruitMemoryRepository implements FruitRepository { private List fruitResponses = new ArrayList(); @Override public void saveFruit(String name, LocalDate warehousingDate, long price) { fruitResponses.add(new FruitResponse(fruitResponses.size() + 1, name, warehousingDate, price, false)); print(); } @Override public void soldFruit(long id) { for (FruitResponse fruitResponse : fruitResponses) { if (fruitResponse.getId() == id) { fruitResponse.setStat(true); return; } } print(); } @Override public FruitSaleResponse readFruitSoldPrice(String name) { long salesAmount = 0; long notSalesAmount = 0; for (FruitResponse fruitResponse : fruitResponses) { if (fruitResponse.isStat()) { salesAmount += fruitResponse.getPrice(); } else { notSalesAmount += fruitResponse.getPrice(); } } print(); return new FruitSaleResponse(salesAmount, notSalesAmount); } @Override public boolean isFruitNotExistById(long id) { for (FruitResponse fruitResponse : fruitResponses) { if (fruitResponse.getId() == id) { return false; } } return true; } @Override public boolean isFruitNotExistByName(String name) { for (FruitResponse fruitResponse : fruitResponses) { if (fruitResponse.getName().equals(name)) { return false; } } return true; } // 잘 실행되었는지 확인하기 위해 출력 private void print() { for (FruitResponse response : fruitResponses) { System.out.println(response); } } } Memory에 저장할 FruitResponsepackage com.example.task.dto.fruit.response; import java.time.LocalDate; public class FruitResponse { private long id; private String name; private LocalDate warehousingDate; private long price; private boolean stat; public FruitResponse(long id, String name, LocalDate warehousingDate, long price, boolean stat) { this.id = id; this.name = name; this.warehousingDate = warehousingDate; this.price = price; this.stat = stat; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDate getWarehousingDate() { return warehousingDate; } public void setWarehousingDate(LocalDate warehousingDate) { this.warehousingDate = warehousingDate; } public long getPrice() { return price; } public void setPrice(long price) { this.price = price; } public boolean isStat() { return stat; } public void setStat(boolean stat) { this.stat = stat; } @Override public String toString() { return "FruitResponse{" + "id=" + id + ", name='" + name + '\'' + ", warehousingDate=" + warehousingDate + ", price=" + price + ", stat=" + stat + '}'; } }MemoryRepository 사용HTTP method : POSTHTTP path : /api/vi/fruit{ "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 } { "name": "사과", "warehousingDate": "2024-01-01", "price": 1000 } { "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 }세 데이터를 보냈을 때,메모리에 잘 저장된 것을 볼 수 있습니다. HTTP method : PUTHTTP path : /api/v1/fruit{ "id": 1 } { "id": 3 }두 데이터를 보냈을 때,id가 1, 3인 stat이 true로 잘 바뀐 것을 볼 수 있습니다. HTTP method : GETHTTP path : /api/vi/fruit/stat?name=사과 MysqlRepository@Repository public class FruitMysqlRepository implements FruitRepository { private final JdbcTemplate jdbcTemplate; public FruitMysqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void saveFruit(String name, LocalDate warehousingDate, long price) { String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, name, warehousingDate, price); } @Override public void soldFruit(long id) { String sql = "UPDATE fruit SET stat = true WHERE id = ?"; jdbcTemplate.update(sql, id); } @Override public FruitSaleResponse readFruitSoldPrice(String name) { String sql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY stat"; List list = jdbcTemplate.query(sql, (rs, rowNum) -> { long amount = rs.getLong("SUM(price)"); return amount; }, name); return new FruitSaleResponse(list.get(0), list.get(1)); } @Override public boolean isFruitNotExistById(long id) { String readSql = "SELECT * FROM fruit WHERE id = ?"; return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty(); } @Override public boolean isFruitNotExistByName(String name) { String readSql = "SELECT * FROM fruit WHERE name = ?"; return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty(); } }MysqlRepository 사용HTTP method : POSTHTTP path : /api/vi/fruit{ "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 } { "name": "사과", "warehousingDate": "2024-01-01", "price": 5000 } { "name": "사과", "warehousingDate": "2024-01-01", "price": 4000 }결과HTTP method : PUTHTTP path : /api/v1/fruit{ "id": 1 } { "id": 3 }결과HTTP method : GETHTTP path : /api/vi/fruit/stat?name=사과

2024. 02. 23.
0
인프런 워밍업 클럽 백엔드 - 다섯 번째 과제
최대한 클린하지 않게 작성된 아래 코드는 다음과 같이 동작합니다.주어지는 숫자를 하나 받는다.해당 숫자만큼 주사위를 던져, 각 숫자가 몇 번 나왔는지 알려준다.[제시된 코드]import java.util.Scanner; public class Main { public static void main(String[] args) { System.out.println("숫자를 입력하세요: "); Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0; for (int i = 0; i = 0 && b = 1 && b = 2 && b = 3 && b = 4 && b = 5 && b 한 걸음 더!현재 코드는 주사위가 1~6까지만 있다는 가정으로 작성되어 있습니다.따라서 주사위가 1~12까지 있거나 1~20까지 있다면 코드를 많이 수정해야 하죠!주사위의 숫자 범위가 달라지더라도 코드를 적게 수정할 수 있도록 고민해 봅시다![수정 코드]import java.util.Scanner; public class Main { private static Scanner scanner = new Scanner(System.in); private static int[] num = new int[20]; public static void main(String[] args) { int roll = inputNumber(); rollTheDice(roll); print(); } public static int inputNumber() { System.out.print("숫자를 입력하세요: "); return scanner.nextInt(); } public static void rollTheDice(int roll) { for (int i = 0; i [참고]https://www.samsungsds.com/kr/insights/cleancode-0823.html

2024. 02. 22.
0
인프런 워밍업 클럽 백엔드 - 네 번째 과제
문제 1우리는 작은 과일 가게를 운영하고 있습니다. 과일 가게에 입고된 "과일 정보"를 저장하는 API를 만들어 봅시다.스펙은 다음과 같습니다.HTTP method : POSTHTTP path : /api/v1/fruitHTTP 요청 Body{ "name": String, "warehousingDate": LocalDate, "price": long }HTTP 요청 Body 예시{ "name": "사과", "warehousingDate": "2024-02-01", "price": 5000 }응답 : 성공 시 200"과일 정보"를 저장하기 위해서 테이블을 만들었습니다.create table fruit ( id bigint auto_increment, name varchar(20), warehousingDate date, price bigint, stat boolean default false, primary key (id) ); 'stat'은 과일의 판매 여부입니다. 팔린 과일이면 true(1), 팔리지 않은 과일이면 false(0) 값이 입력됩니다. 처음에는 팔리지 않은 과일이기 때문에 기본값을 false로 입력했습니다. Controller@RestController public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/api/v1/fruit") public void warehousing(@RequestBody FruitRequest request) { String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); } } Requestpackage com.example.task.dto.fruit.request; import java.time.LocalDate; public class FruitRequest { private String name; private LocalDate warehousingDate; private long price; public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } }결과응답 성공 시 200 OK와 테이블에 정상적으로 삽입됩니다. 한 걸음 더!자바에서 정수를 다루는 가장 대표적인 두 가지 방법은 int와 long입니다.이 두 가지 방법 중 위 API에서 long을 사용한 이유는 무엇일까요?int는 32비트를 사용하여 정수를 표현하며, long은 64비트를 사용합니다. 따라서 long은 int보다 훨씬 큰 정수를 표현할 수 있습니다. 문제 2과일이 팔리게 되면, 우리 시스템에 팔린 과일 정보를 기록해야 합니다. 스펙은 다음과 같습니다.HTTP method : PUTHTTP path : /api/v1/fruitHTTP 요청 Body{ "id": long }HTTP 요청 Body 예시{ "id": 3 }응답 : 성공 시 200Controller@PutMapping("/api/v1/fruit") public void sold(@RequestBody FruitRequest request) { String readSql = "SELECT * FROM fruit WHERE id = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String sql = "UPDATE fruit SET stat = true WHERE id = ?"; jdbcTemplate.update(sql, request.getId()); }'id'가 존재하지 않을 때 응답 결과가 200이 나오지 않게 예외 처리를 했습니다.Requestpublic class FruitRequest { private Long id; private String name; private LocalDate warehousingDate; private long price; public Long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } }기존에 있던 FruitRequest에 id를 추가해서 사용했습니다.결과해당 id가 존재하지 않으면 500 상태 코드를 받게 됩니다.id가 데이터에 존재하면 200을 받을 수 있고,'stat'의 값이 바뀔 수 있는 것을 볼 수 있습니다. 문제 3우리는 특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액을 조회하고 싶습니다.예를 들어,1. (1, 사과, 3000원, 판매 O)2. (2, 사과, 4000원, 판매 X)3. (3, 사과, 3000원, 판매 O)와 같은 세 데이터가 있다면 우리의 API는 판매된 금액 : 6000원, 판매되지 않은 금액 : 4000원 이라고 응답해야 합니다.구체적인 스펙은 다음과 같습니다.HTTP method : GETHTTP path : /api/v1/fruit/statHTTP queryname : 과일 이름HTTP 요청 Body{ "salesAmount": long, "notSalesAmount": long }HTTP 응답 Body 예시{ "salesAmount": 6000, "notSalesAmount": 4000 }테이블의 데이터를 입력했습니다.Controller@GetMapping("/api/v1/fruit/stat") public FruitSaleResponse readSoldFruit(@RequestParam String name) { String readSql = "SELECT * FROM fruit WHERE name = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String sql1 = "SELECT * FROM fruit WHERE name = ? AND stat = true"; List salesAmount = jdbcTemplate.query(sql1, (rs, rowNum) -> { long amount = rs.getLong("price"); return amount; }, name); String sql2 = "SELECT * FROM fruit WHERE name = ? AND stat = false"; List notSalesAmount = jdbcTemplate.query(sql2, (rs, rowNum) -> { long amount = rs.getLong("price"); return amount; }, name); long salesAmountSum = 0; for (long a : salesAmount) { salesAmountSum += a; } long notSalesAmountSum = 0; for (long a : notSalesAmount) { notSalesAmountSum += a; } return new FruitSaleResponse(salesAmountSum, notSalesAmountSum); }판매가 완료된 상태인 과일들의 총 가격과 판매가 되지 않은 상태인 과일들의 총 가격을 따로 구해서 FruitResponse의 인자로 넣어 인스턴스를 생성하도록 했습니다.Responsepublic class FruitSaleResponse { private long salesAmount; private long notSalesAmount; public FruitSaleResponse(long salesAmount, long notSalesAmount) { this.salesAmount = salesAmount; this.notSalesAmount = notSalesAmount; } public long getSalesAmount() { return salesAmount; } public long getNotSalesAmount() { return notSalesAmount; } }결과없는 이름의 값을 보내면 500 에러 발생 한 걸음 더!SQL의 sum, group by 키워드를 검색해 적용해 보세요!Controller@GetMapping("/api/v1/fruit/stat") public FruitSaleResponse readSoldFruit(@RequestParam String name) { String readSql = "SELECT * FROM fruit WHERE name = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String sql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY stat"; List list = jdbcTemplate.query(sql, (rs, rowNum) -> { long amount = rs.getLong("SUM(price)"); return amount; }, name); return new FruitSaleResponse(list.get(0), list.get(1)); }SQL문을 실행했을 때 결과가이렇게 나오기 때문에 list의 0번 인덱스와 1번 인덱스를 받아 인스턴스를 생성하도록 했습니다.결과

2024. 02. 21.
0
인프런 워밍업 클럽 백엔드 - 세 번째 과제
[키워드]익명 클래스 / 람다 / 함수형 프로그래밍 / @FunctionalInterface / 스트림 API / 메소드 레퍼런스 [질문]자바의 람다식은 왜 등장했을까?람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까?익명 클래스란?익명 클래스는 이름이 없는 클래스로, 객체 사용 시에 클래스의 선언과 객체 생성이 동시에 이루어집니다. 이미 정의되어 있는 클래스의 멤버들을 재정의하여 사용할 필요가 있을 때, 그리고 그것이 일회성으로 이용될 때 사용하는 기법입니다. 람다란?람다는 어떤 함수의 매개변수로 다른 함수를 넣고 싶을 때 사용합니다. 람다는 anonymous function이라는 명칭을 갖고 있으며, 선언 없이 또는 이름 없이 사용할 수 있는 함수입니다. 선언 예제@FunctionalInterface public interface CoffeeMachine { public abstract String getCoffee(int coin); }익명 클래스를 이용하는 방식CoffeeMachine machine = new CoffeeMachine() { @Override public String getCoffee(int coin) { return coin + "원 커피"; } }; 람다식을 이용하는 방식CoffeeMachine lambdaMachine = coin -> coin + "원 커피"; 함수형 프로그래밍함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나 입니다. 함수형 프로그래밍은 함수의 응용을 강조하며, 프로그래밍이 문이 아닌 식이나 선언으로 수행되는 선언형 프로그래밍 패러다임을 따르고 있습니다. @FunctionalInterface@FunctionalInterface는 Java 8에서 도입된 어노테이션으로, 인터페이스가 함수형 인터페이스로서 작동하도록 지정합니다. 함수형 인터페이스는 단 하나의 추상 메소드를 가지는 인터페이스를 의미하며, 람다 표현식의 타겟 타입으로 사용됩니다.@FunctionalInterface public interface RunSomething { void doIt(); } 스트림 API스트림 API는 Java 8에 추가된 기능으로, 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공합니다. 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방법으로 다룰 수 있게 됩니다.// 스트림 API를 사용하지 않은 경우 List numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = 0; for (int n : numbers) { if (n % 2 != 0) { sum += n * n; } } // 스트림 API를 사용한 경우 List numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .filter(n -> n % 2 != 0) .mapToInt(n -> n * n) .sum(); 메소드 레퍼런스메소드 레퍼런스는 메소드를 가리키는 참조입니다. 메소드 레퍼런스를 사용하면 람다 표현식을 더 간결하게 표현할 수 있습니다. 메소드 레퍼런스는 '::' 연산자를 사용하여 표현합니다.public class Main { public static void main(String[] args) { List list = Arrays.asList("Apple", "Banana", "Cherry"); // 람다 표현식 list.forEach(s -> System.out.println(s)); // 메소드 레퍼런스 list.forEach(System.out::println); } } 람다식은 왜 등장했을까?자바의 람다식이 등장한 이유는 불필요한 코드를 줄이고, 가독성을 높이기 위함입니다. 함수형 인터페이스의 인스턴스를 생성하여 함수를 변수처럼 선언하는 람다식에서는 메소드의 이름이 불필요하다고 여겨져서 이를 사용하지 않습니다. 람다식과 익명 클래스는 어떤 관계가 있을까?람다식과 익명클래스의 관계는 다음과 같습니다. 람다식은 익명 클래스와 동등한 기능을 하는 식(Expression)입니다. 익명 클래스로 거추장스럽게 정의했던 것을 벗어나 간결한 식만으로 익명 클래스를 구현할 수 있습니다. 하지만, 람다는 함수형 인터페이스에서만 사용됩니다. 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 사용해야 합니다. 또한, 람다는 자신을 참조할 수 없습니다. 람다에서의 this 키워드는 바깥 인스턴스를 가리킵니다. 반면 익명 클래스에서의 this 는 익명 클래스의 인스턴스 자신을 가리킵니다. 그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 사용해야 합니다. 람다식의 문법은 어떻게 될까?매개 변수가 하나 인 경우 (괄호 생략 가능, 두 개인 경우 생략 불가능)msg -> {System.out.println(msg)}; 중괄호 안의 구현부가 한 문장인 경우 (중괄호 생략)msg -> System.out.println(msg); 중괄호 안에 구현부가 한 문장이라도 return문은 중괄호를 생략할 수 없음msg -> return msg.length(); // 오류 중괄호 안에 구현부가 반환문 하나라면 return과 중괄호 모두 생략 가능(x, y) -> x + y; // 두 값을 더하여 반환 msg -> msg.length(); // 문자열 길이 반환[출처]https://itkjspo56.tistory.com/289https://velog.io/@cocodori/%EC%9E%90%EB%B0%94-%EB%9E%8C%EB%8B%A4%EC%8B%9DJava-Lambda-Expression[Java/자바] - 람다식(lambda) : 네이버 블로그 (naver.com)

2024. 02. 20.
0
인프런 워밍업 클럽 백엔드 - 두 번째 과제
문제 1두 수를 입력하면, 다음과 같은 결과 나오는 GETAPI를 만들어 보자!path : /api/v1/calc이다.쿼리 파라미터 : num1, num2CalculatorRequestpackage com.group.libraryapp.dto.test.request; public class CalculatorRequest { private int num1; private int num2; public CalculatorRequest(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public int getNum2() { return num2; } } Calculatorpackage com.group.libraryapp.domain.test; public class Calculator { private int add; private int minus; private int multiply; public Calculator(int add, int minus, int multiply) { this.add = add; this.minus = minus; this.multiply = multiply; } public int getAdd() { return add; } public int getMinus() { return minus; } public int getMultiply() { return multiply; } }문제 2날짜를 입력하면, 몇 요일인지 알려주는 GET API를 만들어 보자!path와 쿼리 파라미터는 임의로 만들어도 상관없다.DateRequestpackage com.group.libraryapp.dto.test.request; public class DateRequest { private String date; public DateRequest(String date) { this.date = date; } public String getDate() { return date; } }DayOfTheWeekpackage com.group.libraryapp.domain.test; public class DayOfTheWeek { private String dayOfTheWeek; public DayOfTheWeek(String date) { this.dayOfTheWeek = date; } public String getDayOfWeek() { return dayOfTheWeek; } }문제 3여러 수를 받아 총 합을 반환하는 POST API를 만들어 보자!API에서 받는 Body는 다음과 같은 형태이다. (HINT : 요청을 받는 DTO에서 List를 갖고 있으면 JSON의 배열을 받을 수 있습니다.)SumRequest package com.group.libraryapp.dto.test.request; import java.util.List; public class SumRequest { private List numbers; public List getNumbers() { return numbers; } }Controllerpackage com.group.libraryapp.controller; import com.group.libraryapp.domain.test.Calculator; import com.group.libraryapp.domain.test.DayOfTheWeek; import com.group.libraryapp.dto.test.request.CalculatorRequest; import com.group.libraryapp.dto.test.request.DateRequest; import com.group.libraryapp.dto.test.request.SumRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.time.DayOfWeek; import java.time.LocalDate; @RestController public class TestController { @GetMapping("/api/v1/calc") public Calculator calc(CalculatorRequest request) { int num1 = request.getNum1(); int num2 = request.getNum2(); int add = num1 + num2; int minus = num1 - num2; int multiply = num1 * num2; return new Calculator(add, minus, multiply); } @GetMapping("/api/v1/day-of-the-week") public DayOfTheWeek dayOfTheWeek(DateRequest request) { DayOfWeek dayOfWeek = LocalDate.parse(request.getDate()).getDayOfWeek(); int week = dayOfWeek.getValue(); String date = ""; switch (week) { case 1: date = "MON"; break; case 2: date = "TUE"; break; case 3: date = "WED"; break; case 4: date = "THU"; break; case 5: date = "FRI"; break; case 6: date = "SAT"; break; case 7: date = "SUN"; break; } return new DayOfTheWeek(date); } @PostMapping("/api/v1/sum") public Integer sum(@RequestBody SumRequest request) { int sum = 0; for (int i : request.getNumbers()) { sum += i; } return sum; } }

2024. 02. 19.
0
인프런 워밍업 클럽 백엔드 - 첫 번쨰 과제
[질문]어노테이션을 사용하는 이유 (효과) 는 무엇일까?나만의 어노테이션은 어떻게 만들 수 있을까?어노테이션(Annotation)어노테이션(Annotation)은 소스 코드에 메타데이터(metadata)를 추가하기 위한 방법으로, 클래스, 메서드, 필드 등의 코드 요소에 정보를 부여하는 특별한 형식의 마커(marker)나 태그(tag)입니다. 어노테이션은 주석(comment)처럼 코드의 설명을 추가하는 용도로도 사용할 수 있지만, 주로 런타임 동작이나 컴파일 시의 특별한 처리를 위해 사용됩니다. 어노테이션을 사용하는 이유간결한 코드 작성어노테이션은 XML보다 더 간결하고 직관적인 코드를 작성할 수 있게 해주며, 공통적인 코드 패턴이나 설정을 재사용하여 코드의 중복을 줄일 수 있습니다.가독성 향상어노테이션은 코드 내에 직접 주석 형태로 작성되므로, 설정 정보를 한눈에 파악할 수 있습니다. 이로 인해 코드의 가독성이 향상되고 설정이 직관적으로 이해됩니다.개발 생산성 향상어노테이션을 사용하면 설정 파일을 따로 작성할 필요가 없어져서 개발 생산성이 향상됩니다.유지보수 용이어노테이션은 코드와 함께 관리되므로, 설정 정보가 변경될 경우 해당 어노테이션만 수정하면 됩니다.컴파일 시점 오류 검증어노테이션 프로세서를 사용하면 컴파일 시점에 오류를 발견할 수 있습니다. 잘못된 설정이나 부적절한 사용 시 컴파일러가 오류를 감지하여 런타임 오류를 방지할 수 있습니다.DI 및 AOP의 편리한 사용Spring은 DI와 AOP를 효과적으로 지원하는데, 어노테이션을 통해 이러한 기능들을 더 쉽게 사용할 수 있게 됩니다. 예를 들어, ‘@Autowired’, ‘@Component’, ‘@Aspect’ 등의 어노테이션을 사용하여 의존성 주입 및 관점 지향 프로그래밍을 쉽게 적용할 수 있습니다.메타데이터 포함어노테이션은 클래스, 메소드, 필드 등에 메타데이터를 직접 포함할 수 있습니다. 이는 리플렉션을 사용하여 런타임에 클래스의 정보를 쉽게 추출할 수 있도록 해줍니다. 나만의 어노테이션은 어떻게 만들 수 있을까?사용자 정의 어노테이션을 만들기 위해서는 @interface 키워드를 이용하여 인터페이스를 정의할 수 있습니다.다음은 사용자 정의 어노테이션을 만드는 간단한 예제입니다.public @interface MyAnnotation { String value() default ""; }위의 코드는 MyAnnotation이라는 이름의 어노테이션을 정의하고 있습니다. value()는 이 어노테이션의 멤버로, 기본값으로 빈 문자열을 가집니다.이렇게 정의한 어노테이션은 다음과 같이 사용할 수 있습니다.@MyAnnotation("Hello, World!") public class MyClass { // ... }이 경우, MyClass는 MyAnnotation이라는 어노테이션을 가지며, 그 값으로 "Hello, World!"를 가집니다.어노테이션 멤버의 타입은 기본형, String, Class, enum, annotation, 이들의 배열만 가능합니다. 또한, 어노테이션은 상속을 지원하지 않습니다.어노테이션을 사용하면 코드에 메타데이터를 추가할 수 있어, 코드의 가독성을 높이고, 특정 기능을 쉽게 구현할 수 있습니다. [출처] [Java]어노테이션(Annotation) 개념 및 사용법|로그https://blog.naver.com/PostView.naver?blogId=seek316&logNo=223326750861|seek316




