킹재
수강평 작성수
1
평균평점
5.0
블로그
전체 8#카테고리
- 백엔드
#태그
- 워밍업클럽
![[인프런 워밍업 클럽 0기] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/71d5c242-7489-4c5b-82f8-acbd5004a9da/comm-warm-up-page-main.png?w=260)
2024. 03. 08.
0
[인프런 워밍업 클럽 0기] 3주차 발자국
이번 주에 배운 내용[JPA에서 연관관계 맵핑하기]create table user ( id bigint auto_increment, name varchar(25), age int, primary key (id) )create table user_loan_history ( id bigint auto_increment, user_id bigint, book_name varchar(255), is_return tinyint(1), primary key (id) )user 테이블과 user_loan_history를 보면, user_loan_history 테이블은 user를 알고 있지만 (user_id 컬럼), user 테이블은 user_loan_history 테이블을 알지 못한다. 즉, 관계의 주도권을 user_loan_history 테이블이 갖고 있는 것이다. 이러한 사실을 JPA 기술을 이용해 반영한다. // User.java @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private final List checkoutHistories = new ArrayList(); // UserLoanHistory.java @ManyToOne private User user;주도권이 없는 쪽에서 @OneToMany와 같은 연관관계 어노테이션을 선언한다. mappedBy = "user"라고 작성하면, JPA가 UserLoanHistory의 user 필드를 연관관계의 주인으로 인식한다. 연관관계의 주인이 설정되어야, 이를 기준으로 테이블이 연결된 채 DB 데이터가 제대로 저장된다.cascade 옵션을 사용하면 User가 삭제 및 저장될 때 연결된 UserLoanHistory도 같이 처리된다.orphanRemoval 옵션을 사용하면 연관관계가 끊어진 데이터를 자동으로 제거해준다. [배포]우리 컴퓨터 대신 외부 컴퓨터 (보통 리눅스 OS 사용)를 가져와 코드를 옮기고, 스프링, MySQL 등을 설치한다면 외부 컴퓨터는 필요한 프로그램만 실행할 것이며, 이용자들은 24시간 우리의 애플리케이션에 접속할 수 있다. 이 과정을 배포라 한다. spring: config: activate: on-profile: local datasource: url: "jdbc:h2:mem:library;MODE=MYSQL;NON_KEYWORDS=USER" # 생략 --- spring: config: activate: on-profile: dev datasource: url: "jdbc:mysql://localhost/library" # 생략똑같은 서버 코드를 실행시키되 실행 환경에 따라 설정을 다르게 하고 싶다면 profile을 이용해보자. application.yml 파일에 예제처럼 작성하면, local profile에선 H2 DB를 사용하고 dev profile에선 MySQL DB를 사용하게 된다. AWS의 EC2 서비스를 이용해 외부 컴퓨터를 빌려보자. EC2는 Elastic Compute Cloud의 약자로, 탄력적인 방식으로 원격 컴퓨터를 사용할 수 있음을 뜻한다. 이때 우리가 빌린 컴퓨터를 AWS에선 인스턴스라고 일컫는다. 인스턴스에서 애플리케이션을 실행하기 위해선 리눅스 명령어를 배울 필요가 있다. 주요 명령어는 다음과 같다. mkdir / rmdir: 디렉토리 생성 / 삭제cd: 현재 위치 변경pwd: 현재 위치 확인ls: 현재 위치에 있는 폴더와 파일 확인 (ls -l로 하면 더 자세히 볼 수 있음) 미니 프로젝트링크: https://github.com/areyouhun/emp-app출퇴근 사내 시스템을 만드는 미션으로, 단계별로 요구되는 기능을 구현해야 한다. 1단계에선 팀 등록, 직원 등록, 팀 조회, 직원 조회 기능을 구현해야 한다. 회고드디어 목표로 했던 서버 배포를 했습니다. 알찬 강의, 아낌없이 주는 선생님, 함께 공부하는 사람들이 있어 커리큘럼을 완수할 수 있었습니다. 하지만 이제 시작이겠죠? 배포 자동화라든지 AWS에는 EC2 말고 어떤 서비스가 있는지를 좀 더 배워야 할 것 같습니다. 3주 동안 함께 공부한 여러분들을 존중하고 존경하며 여기서 마무리하도록 하겠습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] 2주차 발자국](https://cdn.inflearn.com/public/files/blogs/d8237842-3811-47ef-a5df-f4c29d4d8509/comm-warm-up-page-main.png?w=260)
2024. 03. 03.
0
[인프런 워밍업 클럽 0기] 2주차 발자국
이번 주에 배운 내용스프링 컨테이너의 역할스프링 컨테이너가 시작되면 기본으로 등록되는 스프링 빈들이 있다. (JdbcTemplate)이어서 개발자가 직접 만든 클래스가 스프링 빈으로 등록된다. 이때 스프링 컨테이너가 필요한 의존성을 자동으로 주입해준다. (UserRepository를 빈으로 등록할 때 JdbcTemplate이 자동으로 DI)애플리케이션에 필요한 설정은 이제 컨테이너가 처리한다. 이를 제어의 역전이라 부른다. 개발자는 이제 비즈니스 로직에만 집중하면 된다.스프링 빈 등록 방법@Configuration + @Bean 조합@Component, @Controller, @Service, @Repository@Primary나 @Qualifier를 이용하면 원하는 빈을 우선 적용할 수 있다.JPA 사용하기 1 - 이론SQL을 직접 작성하면 실수가 발생할 확률도 높고, 특정 DB에 종속되는 단점도 있다. 결정적으로 DB 테이블과 Java의 객체는 패러다임이 다르다. 이런 문제를 해결하기 위해 등장한 데이터 접근 기술이 JPA (Java Persistence API)이다. JPA는 객체와 관계형 데이터베이스의 테이블을 맵핑하여, Java 데이터를 영구적으로 저장할 수 있도록 정해 놓은 규칙이다.JPA는 인터페이스이고, 이를 구현한 대표 기술로 Hibernate가 있다. Hibernamte는 내부적으로 JDBC를 사용한다.JPA 사용하기 2 - 실전@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @Column(nullable = false, length = 20, name = "name") private String name; private Integer age; protected User() { } // 생략 }public interface UserRepository extends JpaRepository { }@Service public class UserServiceV2 { private final UserRepository userRepository; public UserServiceV2(UserRepository userRepository) { this.userRepository = userRepository; } public void saveUser(UserCreateRequest request) { User user = userRepository.save(new User(request.getName(), request.getAge())); System.out.println("id: " + user.getId()); } // 생략 }스프링 트랜잭션스프링의 @Transactional은 트랜잭션 관리를 지원한다. 주의할 점으로는 org.springframework.transaction.annotation.Transactional을 붙여야 한다는 것이다. 다른 패키지의 @Transactional을 붙이면 정상 동작하지 않을 수 있다.데이터의 변경이 없고, 조회 기능만 있을 때는 readOnly 속성을 설정할 수 있다.@Service public class UserServiceV2 { private final UserRepository userRepository; public UserServiceV2(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional public void saveUser(UserCreateRequest request) { User user = userRepository.save(new User(request.getName(), request.getAge())); System.out.println("id: " + user.getId()); } @Transactional(readOnly = true) public List getUsers() { return userRepository.findAll().stream() .map(user -> new UserResponse(user.getId(), user.getName(), user.getAge())) .collect(Collectors.toList()); } // 생략 } 미션[6일차 - 어노테이션]링크: https://www.inflearn.com/blogs/6805레포지토리를 메모리 DB 버전과 MySQL 버전으로 나누고 교체해가며 빈으로 등록할 수 있도록 @Primary 어노테이션 선언하기@Repository public class FruitMemoryRepository implements FruitRepository { // 생략 }@Repository @Primary public class FruitMySqlRepository implements FruitRepository { // 생략 } 회고그간 MyBatis 같은 SQL Mapper만 써오다가 JPA를 이용해 애플리케이션을 개발했습니다. 기능을 추가할 때마다 SQL문도 일일이 만드는 과정이 번거로운데 JPA는 그럴 필요가 없으니 확실히 개발 생산성을 높일 수 있었습니다. 다만 새로운 유형의 기술이고 난이도도 있는 편이라 이에 적응하는 데는 시간이 걸릴 것 같습니다. 오늘도 함께 스터디 중인 여러분들을 존중하고 존경하며 여기서 마무리하도록 하겠습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] BE 6일차 과제](https://cdn.inflearn.com/public/files/blogs/d847481e-00fc-48c5-b233-2b91a5f2f71a/comm-warm-up-page-main.png?w=260)
2024. 02. 25.
0
[인프런 워밍업 클럽 0기] BE 6일차 과제
4일차 (https://www.inflearn.com/blogs/6687)에서 이미 애플리케이션을 계층별로 나눠서 개발했기에 여기선 복습하는 느낌으로 정리하겠습니다. [컨트롤러]@RestController public class FruitController { private final FruitService fruitService; public FruitController(FruitService fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public void saveFruit(@RequestBody FruitCreateRequest request) { fruitService.saveFruit(request); } @PutMapping("/api/v1/fruit") public void updateFruitStock(@RequestBody FruitStockUpdateRequest request) { fruitService.updateFruitStock(request); } @GetMapping("/api/v1/fruit/stat") public FruitSalesRecordResponse getFruitSalesRecord(@RequestParam(value="name") String name) { return fruitService.getFruitSalesRecord(name); } } [서비스]@Service public class FruitService { private final FruitRepository fruitRepository; public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public void saveFruit(FruitCreateRequest request) { fruitRepository.save(request.getName(), request.getWarehousingDate(), request.getPrice()); } public void updateFruitStock(FruitStockUpdateRequest request) { if (fruitRepository.isFruitEmpty(request.getId())) { throw new IllegalStateException("등록되지 않은 과일입니다."); } fruitRepository.updateStock(request.getId()); } public void getFruitSalesRecord(String name) { if (fruitRepository.isFruitEmpty(name)) { throw new IllegalStateException("등록되지 않은 과일입니다."); } return fruitRepository.getSalesRecord(name); } } [레포지토리]public interface FruitRepository { void save(String name, LocalDate warehousingDate, Long price); void updateStock(Long id); FruitSalesRecordResponse getSalesRecord(String name); boolean isFruitEmpty(Long id); boolean isFruitEmpty(String name); }@Repository public class FruitMySqlRepository implements FruitRepository { private final JdbcTemplate jdbcTemplate; public FruitMySqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void save(String name, LocalDate warehousingDate, Long price) { String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)"; jdbcTemplate.update(sql, name, warehousingDate, price); } @Override public void updateStock(Long id) { String sql = "update fruit set is_sold = 1 where id = ?"; jdbcTemplate.update(sql, id); } @Override public FruitSalesRecordResponse getSalesRecord(String name) { String sql = "select is_sold, sum(price) as total_price from fruit where name = ? group by is_sold"; FruitSalesRecordResponse response = new FruitSalesRecordResponse(); jdbcTemplate.query(sql, (rs, rowNum) -> { if (rs.getBoolean("is_sold")) { response.setSalesAmount(rs.getLong("total_price")); } else { response.setNotSalesAmount(rs.getLong("total_price")); } return 0; }); return response; } @Override public boolean isFruitEmpty(Long id) { String sql = "select * from fruit where id = ?"; return jdbcTemplate.query(sql, (rs, rowNum) -> 0, id).isEmpty(); } @Override public boolean isFruitEmpty(String name) { String sql = "select * from fruit where name = ?"; return jdbcTemplate.query(sql, (rs, rowNum) -> 0, name).isEmpty(); } } @Repository public class FruitMemoryRepository implements FruitRepository { @Override public void save(String name, LocalDate warehousingDate, Long price) { } @Override public FruitSalesRecordResponse getSalesRecord(String name) { return null; } @Override public void updateStock(Long id) { } @Override public boolean isFruitEmpty(Long id) { return false; } @Override public boolean isFruitEmpty(String name) { return false; } }메모리 DB를 이용하는 레포지토리를 추가로 생성하고 @Repository를 선언했습니다. 이 상태에서 애플리케이션을 시행하면 에러가 발생합니다. 서비스의 fruitRepository 필드에는 fruitMySqlRepository 빈과 fruitMemoryRepository 빈 모두 들어갈 수 있다 보니 스프링 부트가 결정을 내리지 못하기 때문입니다. @Repository @Primary public class FruitMySqlRepository implements FruitRepository { // 생략 }이럴 땐 @Primary를 선언해 본인이 사용할 레포지토리한테 등록 우선권을 주면 됩니다.
![[인프런 워밍업 클럽 0기] 1주차 발자국](https://cdn.inflearn.com/public/files/blogs/8b9a4505-2e30-4c3b-b04b-caeee846d1bb/comm-warm-up-page-main.png?w=260)
2024. 02. 25.
0
[인프런 워밍업 클럽 0기] 1주차 발자국
워밍업클럽에 참여한 이유서버 배포 경험을 쌓기 위함입니다. 국비학원에선 선생님의 도커 서버를 통해 프로젝트를 배포했습니다. 그렇기에 저만의 운영 서버를 통해 프로젝트를 배포하고 싶었습니다.함께 공부하는 사람들의 모습을 보며 자극을 받고 싶었습니다. 이번 주에 배운 내용스프링 이니셜라이저를 이용해 스프링 부트 프로젝트를 만들고 실행하였습니다.@SpringBootApplication을 통해 어노테이션의 개념과 역할을 이해했습니다.서버의 개념과 역할을 파악하고 데이터 통신에 필요한 네트워크 환경, HTTP 프로토콜, API 개념을 배웠습니다.유저를 조회하는 API를 개발했습니다.컨트롤러에서 응답 결과를 그대로 (객체) 반환하면, 내부에서 JSON으로 변환되어 HTTP 메세지 body에 담깁니다. 이때 객체에는 getter가 있어야 합니다.디스크와 메모리의 차이를 이해하고, 스프링 부트의 데이터 접근 기술을 이용해 애플리케이션 서버에서 DB로 데이터를 저장하는 법을 배웠습니다.클라이언트의 요청이 적절하지 않을 때 API 예외를 일으키는 법을 배웠습니다.클린 코드의 중요성을 이해하고, 이를 기반으로 컨트롤러를 분리했습니다.컨트롤러 - 서비스 - 레포지토리 미션[1일차 - 어노테이션]링크: https://www.inflearn.com/blogs/6541어노테이션을 사용하는 이유: 컴파일 시점에 에러를 확인할 수 있다. //에 비해 더 나은 정보를 제공한다.나만의 어노테이션 만들기 package playground.myannotation; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { } [3일차 - 람다식]링크: https://www.inflearn.com/blogs/6651Java의 람다식: 함수를 하나의 식으로 표현한 것으로, 함수형 인터페이스를 구현하는 용도로 자주 쓰인다. 익명 클래스의 구현 과정을 보다 간결히 나타낼 수 있다.public class MyLambdaExpression2 { public static void main(String[] args) { Runnable runnableByAnonymous = new Runnable() { @Override public void run() { System.out.println("익명 클래스로 구현했습니다"); } }; Runnable runnableByLambda = () -> { System.out.println("람다식으로 구현했습니다"); }; } } [4일차 - 과일 API 개발]링크: https://www.inflearn.com/blogs/6687문제 3 - 특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액 조회: 4일차 미션의 하이라이트로, 아래의 ResultSet을 갖고 { "salesAmount": 6000, "notSalesAmount": 4000 }를 어떻게 만들지 고민했습니다. [5일차 - 리팩토링]링크: https://www.inflearn.com/blogs/6715문제 - 주사위의 숫자가 6, 8, 12로 바뀔 수 있다면입력값을 받는 부분을 메소드로 추출했습니다.기록 횟수를 배열로 관리했습니다.주사위 숫자와 도전 횟수를 임의로 받을 수 있도록 했습니다. public class Main { private static final Scanner scanner = new Scanner(System.in); public static void main(String[] args) { int number = readInput("주사위 숫자를 입력하세요 : "); int attempts = readInput("도전 횟수를 입력하세요 : "); int[] counts = new int[number]; for (int i = 0; i 회고국비학원을 다닐 땐 응답 결과를 웹 페이지로만 전달하다 보니 API 개발은 다소 생경했습니다. 그렇지만 한 단계 성장한 것 같아 이런 내가 싫진 않은 느낌? API 설계에도 익숙해져서 얼른 서버 배포를 해보고 싶네요. 역시 함께 하면 멀리 간다고, 워밍업클럽에 참여한 러너들을 보며 많은 자극과 도움을 받았습니다. 강의에서 배운 걸로 끝내지 않고 주도적으로 공부할 내용을 찾아 나가는 모습에 저 또한 공부 방향을 재설정할 수 있었습니다. 오늘도 함께 스터디 중인 여러분들을 존중하고 존경하며 여기서 마무리하도록 하겠습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] BE 5일차 과제](https://cdn.inflearn.com/public/files/blogs/ce0d3879-1372-404d-9325-e3b057b304e8/comm-warm-up-page-main.png?w=260)
2024. 02. 23.
0
[인프런 워밍업 클럽 0기] BE 5일차 과제
풀이public class Main { private static final Scanner scanner = new Scanner(System.in); public static void main(String[] args) { int number = readInput("주사위 숫자를 입력하세요 : "); int attempts = readInput("도전 횟수를 입력하세요 : "); int[] counts = new int[number]; for (int i = 0; i 입력값을 받는 부분을 메소드로 추출했습니다.기록 횟수를 배열로 관리했습니다.주사위 숫자와 도전 횟수를 임의로 받을 수 있도록 했습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] BE 4일차 과제](https://cdn.inflearn.com/public/files/blogs/384ef613-9f59-4884-8a67-e8258c5dc486/comm-warm-up-page-main.png?w=260)
2024. 02. 22.
0
[인프런 워밍업 클럽 0기] BE 4일차 과제
[1-1. 프로젝트 생성]스프링 이니셜라이저에서 문제 풀이를 위한 스프링 프로젝트를 생성합니다. Project: Gradle - GroovyLanguage: JavaSpring Boot: 3.2.2Project MetadataGroup: com.groupArtifact: fruit-appName: fruit-appDescription: practicePackage-name: com.group.fruit-appPackaging: JarJava: 17Dependencies: Spring Web / JDBC API / MySQL Driver spring: datasource: url: "jdbc:mysql://localhost/playground" username: "root" password: "1234" driver-class-name: com.mysql.cj.jdbc.Driverapplication.yml을 만들고 DB 연결 세팅을 해줍니다. 일단 실행은 됩니다. 휴우 [1-2. 테이블 설계]create table fruit ( id bigint auto_increment, name varchar(20), warehousing_date datetime, price int, is_sold boolean default 0, primary key (id) ); 과일 테이블을 설계합니다. 기본키인 ID 컬럼은 auto_increment를 통해 데이터가 추가될 때마다 값이 자동으로 증가하도록 했습니다. [1-3. 애플리케이션 개발]public class FruitCreateRequest { private String name; private LocalDate warehousingDate; private Long price; public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public Long getPrice() { return price; } }POST 요청 데이터를 담을 과일 생성 DTO를 제작합니다. price 필드의 타입이 long인 점이 눈에 띕니다. 이는 더 넓은 범위의 숫자를 담기 위함인데요. int 타입의 경우 대략 21억 (2,147,483,647)까지만 담을 수 있습니다. 과일값 가지고 너무 호들갑 떠는 게 아닌가 싶지만 요즘 같은 고물가 시대엔 혹시 모릅니다. @RestController public class FruitController { private final FruitService fruitService; public FruitController(FruitService fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public void saveFruit(@RequestBody FruitCreateRequest request) { fruitService.saveFruit(request); } }@Service public class FruitService { private final FruitRepository fruitRepository; public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public void saveFruit(FruitCreateRequest request) { fruitRepository.save(request.getName(), request.getWarehousingDate(), request.getPrice()); } }public interface FruitRepository { void save(String name, LocalDate warehousingDate, Long price); }@Repository public class FruitMySqlRepository implements FruitRepository { private final JdbcTemplate jdbcTemplate; public FruitMySqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void save(String name, LocalDate warehousingDate, Long price) { String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)"; jdbcTemplate.update(sql, name, warehousingDate, price); } }이어서 컨트롤러, 서비스, 레포지토리를 만들어 줍니다. 레포지토리는 인터페이스와 구현 클래스로 나눠서 제작했습니다. 애플리케이션을 실행하고 JSON 데이터와 함께 POST 요청을 보내봅시다 두근두근 200 OK 응답을 받았고, DB에도 정상 등록되었습니다. [2-1. 애플리케이션 개발]public class FruitStockUpdateRequest { private Long id; public Long getId() { return id; } }PUT 요청 데이터를 담을 과일 재고 갱신 DTO를 생성합니다. @RestController public class FruitController { // 생략 @PutMapping("/api/v1/fruit") public void updateFruitStock(@RequestBody FruitStockUpdateRequest request) { fruitService.updateFruitStock(request); } }@Service public class FruitService { // 생략 public void updateFruitStock(FruitStockUpdateRequest request) { if (fruitRepository.isFruitEmpty(request.getId())) { throw new IllegalStateException("등록되지 않은 과일입니다."); } fruitRepository.updateStock(request.getId()); } }public interface FruitRepository { void save(String name, LocalDate warehousingDate, Long price); boolean isFruitEmpty(Long id); void updateStock(Long id); }@Repository public class FruitMySqlRepository implements FruitRepository { // 생략 @Override public boolean isFruitEmpty(Long id) { String sql = "select * from fruit where id = ?"; return jdbcTemplate.query(sql, (rs, rowNum) -> 0, id).isEmpty(); } @Override public void updateStock(Long id) { String sql = "update fruit set is_sold = 1 where id = ?"; jdbcTemplate.update(sql, id); } }컨트롤러, 서비스, 레포지토리에도 필요한 기능을 추가합니다. 문제에선 팔린 과일을 기록하라고 돼있습니다만, 저는 DB에 있는 과일 테이블의 IS_SOLD 컬럼값을 1로 바꾸는 식으로 진행했습니다. 이때 등록되지 않는 과일 정보를 수정하려는 경우 IllegalStateException을 일으키도록 했습니다. 현재 과일 테이블엔 ID 컬럼값이 1인 사과 데이터만 존재합니다. id를 5로 해서 PUT 요청을 보내니 예외가 정상적으로 발생했습니다. 이번엔 id를 1로 해서 PUT 요청을 보냈습니다. 200 OK 응답이 왔고, DB에도 잘 반영되었습니다. [3-1. 테이블 세팅]우선 과일 테이블을 문제 조건과 동일하게 맞춰줍니다. [3-2. 애플리케이션 개발]public class FruitSalesRecordResponse { private Long salesAmount; private Long notSalesAmount; public Long getSalesAmount() { return salesAmount; } public void setSalesAmount(Long salesAmount) { this.salesAmount = salesAmount; } public Long getNotSalesAmount() { return notSalesAmount; } public void setNotSalesAmount(Long notSalesAmount) { this.notSalesAmount = notSalesAmount; } } 특정 과일의 판매된 총 금액과 판매되지 않은 총 금액을 담을 DTO입니다. @RestController public class FruitController { // 생략 @GetMapping("/api/v1/fruit/stat") public FruitSalesRecordResponse getFruitSalesRecord(@RequestParam(value="name") String name) { return fruitService.getFruitSalesRecord(name); } }컨트롤러에 맵핑 메소드를 추가합니다. @RequestParam에 value 속성을 추가하지 않으면 예외가 발생하더군요. name이란 명칭 자체에 문제가 있지 않나 추측해봅니다. @Service public class FruitService { // 생략 public void getFruitSalesRecord(String name) { if (fruitRepository.isFruitEmpty(name)) { throw new IllegalStateException("등록되지 않은 과일입니다."); } return fruitRepository.getSalesRecord(name); } }public interface FruitRepository { // 생략 FruitSalesRecordResponse getSalesRecord(String name); boolean isFruitEmpty(String name); } @Repository public class FruitMySqlRepository implements FruitRepository { // 생략 @Override public FruitSalesRecordResponse getSalesRecord(String name) { String sql = "select is_sold, sum(price) as total_price from fruit where name = ? group by is_sold"; FruitSalesRecordResponse response = new FruitSalesRecordResponse(); jdbcTemplate.query(sql, (rs, rowNum) -> { if (rs.getBoolean("is_sold")) { response.setSalesAmount(rs.getLong("total_price")); } else { response.setNotSalesAmount(rs.getLong("total_price")); } return 0; }); return response; } @Override public boolean isFruitEmpty(String name) { String sql = "select * from fruit where name = ?"; return jdbcTemplate.query(sql, (rs, rowNum) -> 0, name).isEmpty(); } }오늘 과제의 하이라이트는 여기인 것 같습니다. 일단 2번 문제와 동일하게 등록되지 않는 과일에 대해선 예외를 일으키도록 했습니다. 제가 작성한 SQL을 실행하면 ResultSet이 사진처럼 나오는데요. 이를 FruitSalesRecordResponse에 바로 담을 수가 없습니다. DB 컬럼과 엔티티의 필드가 일치하지 않기 때문입니다. 그래서 어떻게 쑤셔 넣을까 고민하다 JDBC 템플릿으로 맵핑하는 과정에서 IS_SOLD 컬럼값이 1이면 TOTAL_PRICE 컬럼값을 FruitSalesRecordResponse의 salesAmount 필드에 넣고, 그렇지 않으면 notSalesAmount 필드에 넣는 식으로 처리했습니다. 이제 GET 요청을 보내봅시다. 요청 파라미터로 "오렌지"를 전달하니 예외가 정상적으로 발생했습니다. 이번엔 "사과"를 전달하니 정상적으로 응답되었습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] BE 3일차 과제](https://cdn.inflearn.com/public/files/blogs/4a88c074-e435-456a-84a0-8ff7e3344785/comm-warm-up-page-main.png?w=260)
2024. 02. 21.
0
[인프런 워밍업 클럽 0기] BE 3일차 과제
Java의 람다식// 함수형 인터페이스 @FunctionalInterface public interface MyFunctionalInterface { void sayHi(String name); }public class MyLambdaExpression1 { public static void main(String[] args) { // 람다식으로 함수형 인터페이스 구현 MyFunctionalInterface greeting = (String name) -> System.out.println("Hi, " + name + "!"); // 함수형 인터페이스의 메소드 호출 greeting.sayHi("Son"); } }람다식은 함수를 하나의 식으로 표현한 것입니다. 람다식으로 표현된 함수는 다른 함수의 매개값으로 전달하거나 반환값으로 사용할 수 있습니다. Java에선 버전 8부터 도입되었으며 함수형 인터페이스의 인스턴스를 생성하는 용도로 쓰입니다. 함수형 인터페이스함수형 인터페이스는 추상 메서드를 단 하나만 가지고 있는 인터페이스를 말합니다. Java에선 @FunctionalInterface로 함수형 인터페이스임을 표시합니다. 함수형 인터페이스는 람다식을 사용해 구현할 수 있으며, 이를 통해 함수를 변수처럼 선언할 수 있습니다. 람다식과 익명 클래스public class MyLambdaExpression2 { public static void main(String[] args) { Runnable runnableByAnonymous = new Runnable() { @Override public void run() { System.out.println("익명 클래스로 구현했습니다"); } }; Runnable runnableByLambda = () -> { System.out.println("람다식으로 구현했습니다"); }; } }익명 클래스는 한 번만 사용되고 버려지기 때문에 재사용하지 않는 클래스를 만들 때 유용하지만, 구현부를 작성하는 게 번거롭습니다. 람다식을 이용하면 익명 클래스의 구현 과정을 보다 간결하게 나타낼 수 있습니다. 람다식 문법람다식은 매개변수, 화살표 (->), 실행문으로 구성됩니다. // 매개변수 하나 int number -> number + 10; // 매개변수 여러 개 (int x, int y) -> System.out.println("x:" + x + " / " + "y:" + y); // 매개변수 없음 () -> System.out.println("히히");매개변수가 하나만 있는 경우 괄호를 생략할 수 있습니다. // return문만 있는 경우 (int x, int y) -> {return x + y;} (int x, int y) -> x + y; // 동일하게 작동 // 실행문이 한 줄만 있는 경우 (String name) -> {System.out.println("Hi, " + name);} (String name) -> System.out.println("Hi, " + name); // 동일하게 작동실행문이 복잡하지 않다면 중괄호를 생략할 수 있습니다.
백엔드
・
워밍업클럽
![[인프런 워밍업 클럽 0기] BE 1일차 과제](https://cdn.inflearn.com/public/files/blogs/84f7e771-d957-46ea-b81e-16ada7281870/comm-warm-up-page-main.png?w=260)
2024. 02. 19.
0
[인프런 워밍업 클럽 0기] BE 1일차 과제
어노테이션을 사용하는 이유컴파일 시점에 에러를 확인할 수 있다: 어노테이션을 사용하면 컴파일러를 통해 문법 에러를 확인할 수 있습니다. 이를 통해 문제를 조기에 발견할 수 있게 해줍니다. 예컨대 @Override는 상위 클래스의 메서드를 재정의하는 것을 의미합니다. 해당 어노테이션의 오버라이딩 조건을 지키지 않으면 컴파일 오류를 일으킵니다.//에 비해 더 나은 정보를 제공한다: 어노테이션의 사전적 의미는 '주석'으로 Java의 //와 비슷한 용도로 쓰입니다. 하지만 //은 코드를 이용하는 사람 입장에서 달리 해석할 수 있으며 쉽게 무시될 수 있습니다. 이와 달리 어노테이션은 뛰어난 가독성을 제공하며 코드 레벨에서 메타 데이터를 제공합니다. 이를 통해 애플리케이션에 필요한 기능을 수행하거나 제약을 부여할 수 있습니다. 나만의 어노테이션 만들기package playground.myannotation; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }인터페이스를 생성합니다. 이때 interface 키워드 앞에 @를 부착합니다.메타 어노테이션을 지정합니다. 어노테이션의 사용 및 유지 범위를 지정할 수 있습니다.@Target: 어노테이션의 사용 범위로 ElementType.PARAMETER는 메서드 파라미터로 사용됨을 의미합니다.@Retention: 어노테이션의 유지 범위로 RetentionPolicy.RUNTIME는 런타임까지 어노테이션이 유지됩니다.
백엔드
・
워밍업클럽




