블로그
전체 42024. 06. 03.
0
인프런 워밍업 클럽 BE 1기 후기
워밍업 클럽1기가 벌써 끝나서 후기를 쓰다니.. 시간이 정말 빨리 지난 것 같다.완주가 목표였기에 노션에 계획표를 만들고 매주 지켜나갔다! 원래 계획을 잘 지키지 못하는 스타일인데 꼭 완주하고 싶었기에 계획에 맞추어 공부할 수 있었다.그리고 디스코드를 통해 여러 사람들과 다같이 공부하고 공유하며, 질문도 자유롭게 할 수 있던 환경이 동기가 되었던 것 같다. 개인 프로젝트를 만들던 당시 백엔드 단을 만들었어야 했는데 어떻게 구상할지 고민하다가 들어간 인프런 사이트에 워밍업 클럽 1기가 마침 모집중이였다!!! 백엔드를 전체적으로 훑을 수 있도록 구성되어 있기 때문에 정말 좋았다.실제로 미니프로젝트까지 과제를 진행하면서 많은 공부가 되었고, 아직 부족하지만 개인프로젝트의 백엔드 단도 개발할 수 있을 것 같다!!
백엔드
・
인프런
・
인프런워밍업클럽
・
스터디1기
・
BE
2024. 05. 18.
0
인프런 워밍업 클럽 BE 1기 - 3주차 발자국
조금 더 객체지향적으로 개발할 수 없을까?이전에는 User를 따로 가져와 BookService에서 UserLoanHistory를 만들어 저장하였다.조금 더 객체지향적으로 바꾸면 UserLoanHistory를 User에서 가져와 바로 대출을 처리하자!@ManyToOne : 내가 다수이고 너가 한개대출기록은 여러개이고 그 대출을 소유하고 있는 사용자는 한명이다 → N : 1 관계//private long userId; @ManyToOne private User user;@OneToMany : 나는 한개 너가 다수@OneToMany privateList userLoanHistoryList = new ArrayList();여러개의 대출기록 N개이기 때문에 List로 표현이로써 User와 UserLoanHistory는 서로 연관관계가 되었다. 그 중에 주인은 누구인가?누가 관계의 주도권을 가지고 있는가?현재는 user_loan_history가 user_id를 DB 컬럼으로 가지고 있기 때문에 주인이다.연관관계의 주인이 아닌 쪽에 mappedBy 옵션을 달아 주어야 한다.@OneToMany(mappedBy = "user") privateList userLoanHistoryList = new ArrayList(); 1:1 관계한 사람은 한 개의 실거주 주소 만을 가지고 있다.person 테이블이 address 테이블의 id를 가질 수도 있고, address테이블이 person 테이블의 id를 가질 수도 있다.@Entity public class Person{ ... @OneToOne private Address address; }@Entity public class Address{ ... @OneToOne private Person person; }이렇게 테이블을 생성한다고 가정할때 Person이 address id를 가지고 있다.create table person ( id bigint auto_increment, name varchar(255), address_id bigint, primary key (id) );create table address ( id bigint auto_increment, city varchar(255), street varchar(255), primary key (id) );→ Person이 address 주인이다! 1:1 관계지만..따라서 mappedBy 작성@Entity public class Address{ ... @OneToOne(mappedBy = "address") private Person person; }연관관계의 주인 효과 → 객체가 연결되는 기준이 된다.!@Transactional public void savePerson() { Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); // setter는 임시 }person이 address의 주인이기때문에 이렇게 코드를 작성하고 실행하면 DB에서 정상으로 테이블이 연결된다. 하지만 반대로 1:1관계이더라도 address.setPerson(person); 이렇게 작성할 경우 DB에 저장되지 않는다. DB는 연결되었지만 !!! 객체끼리는 연결되지 않았다. address.getPerson(); --> 트랜잭션이 끝나기전에 get하면 Null 반환@Transactional public void savePerson() { Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); // setter는 임시 address.getPerson(); --> 트랜잭션이 끝나기전에 get하면 Null 반환 }해결책으로 setter 한번에 둘을 같이 이어주면 된다.public void setAddress(Address address) { this.address = address; this.address.setPerson(this); --> 둘을 같이 이어주자 }N : 1 관계이 관계에서 주인은 무조건 숫자가 많은쪽이 주인이다. @OneToMany를 작성하지 않고, @ManyToOne 하나만 작성해도 된다.(단방향) @JoinColumn연관관계의 주인이 활용할 수 있는 어노테이션.필드의 이름이나 null 여부, 유일성 여부, 업데이트 여부 등을 지정@JoinColumn(nullable = false) @ManyToOne private User user; N : M 관계 - @ManyToMany학생과 동아리 관계를 생각하자학생은 여러 동아리를 가입할 수 있고, 한 동아리엔 여러 학생이 있다.→ But, 구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용하지 않는 것을 추천cascade 옵션 cascade : 폭포처럼 흐르다. : 한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 플러 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능유저가 삭제될때 유저가 연결되어 있는 UserLoanHistory도 삭제하고 싶을때 사용하면 된다.@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) privateList userLoanHistoryList = new ArrayList(); orphanRemoval 옵션: 객체간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션관계가 끊어진 데이터 = orphan(고아) removal (제거)한 유저가 빌린 책1, 책2가 있다고 가정할 때, userLoanHistory에서 책1만 리스트(자바단)에서 지웠다. → DB는 아무런 변화가 없다. 이렇게 리스트에서 지우는(연결을 끊는 것 만으로도) 것으로도 DB에서 삭제가 되길바라면 이 옵션을 쓸 수 있다.@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) privateList userLoanHistoryList = new ArrayList(); @Transactional public void deleteUserHistory() { User user = userRepository.findByName("ABC") .orElseThrow(IllegalArgumentException::new); user.removeOneHistory(); } public void removeOneHistory() { userLoanHistories.removeIf(history -> "책1".equals(history.getBookName())); } 책 대출/반납 기능 리팩토링과 지연로딩User와 UserLoanHistory가 직접적으로 연결되어 있다.대출기능 리팩토링public void loanBook(String bookName) { this.userLoanHistories.add(new UserLoanHistory(this, bookName)); } User.java에서 새로운 UserLoanHistory객체를 만들고 넣어주기 때문에 service단에서는 loanBook메소드를 호출해주기만 하면 된다.@Transactional public void loanBook(BookLoanRequest request) throws IllegalAccessException { Book book = bookRespository.findByName(request.getBookName()) .orElseThrow(IllegalAccessException::new); if (userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(), false)) { throw new IllegalAccessException("이미 대출중인 책입니다."); } User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } //userLoanHistoryRepository.save(new UserLoanHistory(user, book.getName())); user.loanBook(book.getName()); }반납기능 리팩토링public void returnBook(String bookName) throws IllegalAccessException { UserLoanHistory targetHistory = this.userLoanHistories.stream() .filter(history -> history.getBookName().equals(bookName)) .findFirst() .orElseThrow(IllegalAccessException::new); targetHistory.doReturn(); }함수형 프로그래밍을 할 수 있게 .stream을 작성해주고.filter를 통해 들어오는 객체들 중에 다음 조건을 충족하는 것만 필터링 한다 ..findFirst()를 통해 첫번째로 해당하는 UserLoanHistory를 찾는다. 이 결과는 Optional이기에 Optional을 제거하기 위해 없으면 예외를 던진다. orElseThrow그렇게 찾은 UserLoanHistory를 반납처리 한다.@Transactional public void returnBook(BookReturnRequest request) throws IllegalAccessException { User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } // UserLoanHistory history = userLoanHistoryRepository.findByUserIdAndBookName(user.getId(), request.getBookName()) // .orElseThrow(IllegalAccessException::new); // history.doReturn(); user.returnBook(request.getBookName()); }userLoanHistoryRepository.findByUserIdAndBookName()은 사용할 일이 없어져서 삭제해었다.→ Domain 계층에 비즈니스 로직이 들어갔다.영속성 컨텍스트의 4번째 능력 - 지연 로딩 (Lazy Loading)예시 - User를 가져오는 부분과, 도메인 로직 실행 중간에 Print 출력을 해보자@Transactional public void returnBook(BookReturnRequest request) throws IllegalAccessException { User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } Systme.out.println("Hello"); user.returnBook(request.getBookName()); }실행결과를 확인하면 user를 가져오는 쿼리, hello 출력 후 userLoanHistory 쿼리를 출력하는 것을 확인할 수 있다. → 시작하자마다 유저와 대출기록을 다 가져올 수 있지만 그러지 않고, 꼭 필요한 순간에 데이터를 가져온다. 이러한 것을 지연 로딩이라 한다. @OneToMany에 fetch 옵션에 기본 디폴트 값이다 LAZY만약 한번에 가지고 오고 싶다면 LAZY → EAGER를 사용하면 된다.@OneToMany(mappedBy = “user”, cascade = CadcadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)연관관계를 사용하면 무엇이 좋을까?각자의 역할에 집중하게 된다. 계층별로 응집성이 높아진다.새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다. (모두 다 service단에 있다면 ?)테스트 코드 작성이 쉬워진다.연관관계를 사용하는 것이 항상 좋을까?지나치게 사용하면 성능상 문제가 생길 수 있고, 도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.너무 얽혀 있으면 A를 수정했을 때 , B ~ D까지 고쳐야할 수 있다.→ 여러부분을 생각해서 구조를 짜자!배포란 무엇인가현재 우리 컴퓨터에서만 작동할 수 있는 환경..배포란 : 최종 사용자에게 SW를 전달하는 과정 → 전용 컴퓨터에 우리의 서버를 옮겨 실행시키는 것전용 컴퓨터가 없는데..? → AWS를 빌리자!!AWS에서 컴퓨터를 빌릴 때 한 가지 알아두어야 할점!서버용 컴퓨터는 보통 리눅스를 사용한다.profile과 H2 DB똑같은 서버 코드를 실행시키지만, 우리 컴퓨터에서 실행할 때는 우리 컴퓨터의 MySQL을 사용하고,전용 컴퓨터에서 실행할 때는 전용 컴퓨터의 MySQL을 사용하고 싶다→ profile 개념 똑같은 서버 코드를 실행시키지만, 실행될 때 설정을 다르게 하고 싶을 때(DB 이외에도 다른 API, 다른 기능 등 ..)Profile을 적용해보자똑같은 서버 코드를 실행시키지만, local이라는 Profile을 입력하면, H2 DB를 사용하고 dev라는 profile을 입력하면 MySQL DB를 사용하게 바꾸자 H2 DB란 : 경량 Database로, 개발 단계에서 많이 사용하며 디스크가 아닌 메모리에 데이터를 저장할 수 있다. 메모리에 데이터를 저장하면 휘발되지만, 개발 단계에서 테이블이 계속 변경 되기 때문에 데이터가 휘발해야하고 ddl-auto 옵션을 create로 주면 테이블을 신경쓰지 않고 코드에만 집중할 수 있다.profile 설정나는 인텔리제이 유료버전이라 강사님 처럼 active profile 부분이 안뜨기 때문에 다른방법으로 진행하였다.-Dspring.profiles.active=locallocal 또는 dev로 입력해주면 된다. AWS의 EC2 사용하기AWS 가입 후 지역을 서울로 변경EC2인스턴스 - 내가 빌린 컴퓨터 (현재 0)인스턴스 시작 버튼 클릭인스턴스 유형은 컴퓨터의 사양t2.micro 선택 !회고벌써 워밍업도 마지막을 향해 달려가는 중이다!!7번째 과제에서 아직 나는 JPA에 익숙하지않아서 뭔가 직접 쿼리를 쓰는게 더 편했던 것 같다. @Query를 통해 직접 작성해주었다가 어? 이렇게하면 JPA에서 알아서 해주는구나 하고 여러번 바꾸기도 했다!근데 만약 쿼리에 조건이 너무 많을 경우에는 이름이 너무 길어지지 않을까라는 생각도 했었다 ㅎ..중간에 자꾸 테이블에 해당 컬럼이 없다고 오류가 나길래 .. 그럴리가 없는데라고 생각했지만 그럴리 있었다. 역시 컴퓨터는 거짓말을 하지 않는다 하하확실히 강의만 보는 것보다 직접 내가 생각하고 타이핑하는 것이 내가 어느정도까지 실제적으로 이해했는지 확인할 수 있는 길인 것 같다.앞으로 남은 마지막 미니 프로젝트까지 잘 마무리하면서 개념 부분을 더 공부해보자!
백엔드
・
워밍업
・
백엔드
・
스프링
2024. 05. 13.
0
인프런 워밍업 클럽 BE 1기 - 2주차 발자국
UserController의 의아한점static이 아닌 코드를 사용하려면 인스턴스화 (new)가 필요하다UserController를 인스턴스화하고 있지 않은데 누가 하고 있는것인가???UserController는 JdbcTemplate에 의존하고 있다. 그런데 JdbcTemplate란 클래스도 설정해준적이 없다..! 어떻게 가져온거지?⇒ @RestController가 다해주고 있었다!!!!!⇒ UserController클래스를 API의 진입지점으로 만들 뿐 아니라 UserController 클래스를 스프링 빈으로 등록 시킨다. 스프링 빈(Bean)이란?서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너를 만들게 되고, 컨테이너 안에는 클래스가 들어가게 된다.!이때 다양한 정보도 함께 들어있고, 인스턴스화도 이루어진다.JdbcTemplate도 스프링 빈에 등로되어 있었기에 사용할 수 있었다. (dependencies로 의존)UserRepository는 JdbcTemplate을 가져오지 못할까?JdbcTemplate을 가져오려면 UserRepository가 스프링 빈이어야 하는데 UserRepository는 스프링 빈이 아니다!!→ UserRepository를 스프링 빈으로 등록하자!스프링 컨테이너를 왜 사용할까?메모리에 저장하는 레포를 만든다고 가정하고 코딩…→ MySQL로 저장하고 싶은데?→ 관련된 모든 것들을 다 MySQL로 변경하는 레포로 변경해야함!!! 데이터를 메모리에 저장할지, MySQL에 저장할지 Repository의 역할에 관련된 것만 바꾸고 싶은데 BookService까지 바꿔야 한다. (OMG)⇒ Java의 interface를 활용하자!그래도 Service를 수정해야하는 상황이 발생.. 스프링 컨테이너를 사용하면?BookMemoryRepository, BookMySqlRepository 둘 중 BookService에 쓰일 것을 컨테이너가 선택한다!!→ 이런 방식을 제어의 역전(IoC, Inversion of Control)이라 한다.→ 컨테이너가 선택해 BookService에 넣어주는 과정을 의존성 주입(DI, Dependency Injection)라고 한다.@Primary를 붙여주면 해당 어노테이션이 있는 곳이 사용된다. (우선권 결정)⇒ 나중에 바꿔야 Memory에서 mysql로 바꾸는 작업이 있어서 코드를 다 바꿔야 할 경우 @Primary를 붙여 스프링이 자동으로 해당 레포로 선택하게 하면 된다! 스프링 컨테이너를 다루는 방법빈을 등록하는 방법@Configuration클래스에 붙이는 어노테이션@Bean을 사용할 때 함께 사용해 주어야 한다.@Bean메소드에 붙이는 어노테이션메소드에서 반환되는 객체를 스프링 빈에 등록한다.UserRepository.java에 있는 @Repository 어노테이션 삭제 후 @Bean으로 등록언제 @Service, @Repository를 사용해야하나??→ 개발자가 직접 만든 클래스를 스프링 빈으로 사용할 때@Configuration, @Bean는 언제 사용?→ 외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때@Component주어진 클래스를 ‘컴포넌트’로 간주한다.이 클래스들은 스프링 서버가 뜰 때 자동으로 감지된다.→ 사실 숨겨져 있었다.. 타고 들어가면 사용되고 있는것을 확인할 수 있다.→ 컨트롤러, 서비스, 리포지토리가 모두 아니고, 개발자가 직접 작성한 클래스를 스프링 빈으로 등록할 때 사용되기도 한다. 스프링 빈을 주입 받는 몇 가지 방법생성자를 이용해 주입받는 방식 (가장 권장)기존에는 @Autowired 어노테이션을 붙여야했는데 스프링 버전이 업데이트 되면서 안붙여도 자동으로 등록되게 되었다.setter와 @Autowired 사용 → setter를 사용할 경우 오류발생 확률 높아짐필드에 직접 @Autowired 사용 → 테스트하기 어렵다.@Qualifier: 여러개의 후보군이 있을때 그 중 하나를 특정해서 가져올 수 있게 끔 한다.스프링 빈을 사용하는 쪽, 스프링 빈을 등록하는 쪽 모두 @Qualifier를 사용할 수 있다.스프링 빈을 사용하는 쪽에서만 쓰며, 빈의 이름을 적어주어야 한다.양쪽 모두 사용하면, @Qualifier끼리 연결된다.@Primary vs @Qualifier → 동시에 사용할 경우 어떤게 우선일까?사용하는 쪽에서 직접 적어준 @Qualifier가 이긴다.SQL을 직접 작성하게되면..SQL을 직접 작성할 경우 오류가 나도 확인하기 힘들다⇒ 컴파일 시점에 발견되지 않고 런타임 시점(서버가 이미 가동된 후)에 발견된다특정 데이터베이스에 종속적이게 된다.⇒ 다른 DB를 사용할경우 다 바꿔줘야 한다.반복 작업이 많아진다. 테이블을 하나 만들 대마다 CRUD쿼리가 항상 필요하다.데이터베이스의 테이블과 객체는 패러다임이 다르다.→ JPA( Java Persistence API) 등장!: 객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙 유저테이블에 대응되는 Entity Class 만들기@Entity 어노테이션을 User 클래스에 붙인다.→ @Entity : 스프링이 User객체와 user 테이블을 같은 것으로 바라본다. (저장되고, 관리되어야 하는 데이터)우선 테이블의 PK인 id를 만들어보자@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null;@Id : 이 필드를 primary key로 간주한다.@GeneratedValue : primary key는 자동 생성되는 값이다.(strategy = GenerationType.IDENTITY)여기 부분은 DB종류마다 자동생성 전략이 다르다!! 주의!!MySQL의 auto_increment를 사용했기에 IDENTITY를 매칭@Entity를 사용할 경우 - 매개변수가 하나도 없는 기본 생성자가 꼭 필요하기 때문에 다음 코드를 작성!protected User() {}@Column : 객체의 필드와 Table의 필드를 매핑한다.→ null이 들어갈 수 있는지 여부, 길이 제한, DB에서의 column이름 등등…@Column(nullable = false, length = 20, name = "name") //name varchar(20) private String name;null이 들어갈 수 없거, 길이는 20자, 필드의 이름은 name (이 경우는 name = “name” 동일하기에 생략 가능) @Column 은 생략가능하기 때문에 널이 들어갈 수 있고 굳이 특정할필요 없다면 생략이 가능하다. JPA 설정 추가 - application.ymljpa: hibernate: ddl-auto: none properties: hibernate: show_sql: true format_sql : true dialect: org.hibernate.dialect.MySQL8Dialectddl-auto : 스프링이 시작할 때 DB에 있는 테이블을 어떻게 처리할지none 외에 다른 종류create : 기존 테이블이 있다면 삭제 후 다시 생성create-drop : 스프링이 종료될 때 테이블을 모두 제거 -… 사용 조심할것 ! 데이터가 모두 날라갈 수있음.update : 객체와 테이블이 다른 부분만 변경validate : 객체와 테이블이 동일한지 확인none : 별다른 조치를 하지 않는다.show_sql : JPA를 사용해 DB에 SQL을 날릴 때 SQL을 보여줄 것인가format_sql : SQL을 보여줄 때 예쁘게 포맷팅 할 것인가dialect : 방언, 사투리.. → 이 옵션으로 DB를 특정하면 조금씩 다른 SQL을 수정해준다. Spring Date JPA를 이용하여 자동으로 쿼리 날리기기존 UserRepository.java를 UserJdbcRepository.java로 변경하고domain > user > UserRepository 인터페이스를 생성하자생성 후 JpaRespository를 상속 받는다.public interface UserRepository extends JpaRepository {} → User의 primary key인 Id의 타입이 Long이라 Long으로 작성한다. 유저 데이터 정보 추가하기 (INSERT)public void saveUser(UserCreateRequest request) { userRepository.save(new User(request.getName(), request.getAge())); }JpaRepository를 상속 받는 userRepository에 save메소드를 객체에 넣어주면 INSERT SQL이 자동으로 날아간다. → save되고 난 후의 User는 id가 들어 있다유저 정보 조회하기 (SELECT)public List getUserList(){ return userRepository.findAll().stream() .map(user -> new UserResponse(user.getId(), user.getName(), user.getAge())) .collect(Collectors.toList()); }findAll() : 해당 테이블의 모든 데이터를 조회한다.user를 stream으로 mapping시켜주고 user에 new UserResponse로 id, name, age를 넣어준다.그 후 결과를 List로 반환유저 정보 조회 후 수정하기 (UPDATE) public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); userRepository.save(user); }findById를 사용하면 id를 기준으로 1개의 데이터를 가져온다..orElseThrow(IllegalArgumentException::new);→ Optional의 orElseThrow를 사용해 User가 없다면 예외를 던진다. 있을경우 user에 담긴다user.updateName(request.getName()); userRepository.save(user);도메인 user의 updateName메소드에 변경할 reqest.getName을 인자로 넣어 이름을 변경해주고save를 통해 user의 정보를 저장한다. (자동으로 UPDATE SQL이 날라가게 된다.) 어떻게 SQL을 작성하지 않아도 동작하지? JPA인가?→ Spring Data JPA가 도와준다.: 복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리 By앞에 들어갈 수 있는 구절 정리find : 1건을 가져온다. 반환타입은 객체가 될수도 있고, Optional이 될 수도 있다.finalAll : 쿼리의 결과물이 N개인 경우 사용. List 반환exists : 쿼리 결과가 존재하는지 확인. 반환 타입은 booleancount : SQL의 결과 개수 반환타입은 longBy뒤에 들어갈 수 있는 구절 정리GreaterThan : 초과GreaterThanEqual : 이상LessThan : 미만LessThanEqual : 이하Between : 사이에SELECT * FROM user WHERE age BETWEEN ? AND ?;List findAllByAgeBetween(int startAge, int endAge);StartsWith : ~로 시작하는EndsWith : ~로 끝나는트랜잭션 이란 : 쪼갤 수 없는 업무 최소 단위→ 모든 SQL을 성공시키거나 하나라도 실패하면 모두 실패시키자!트랜잭션 시작하기start transaction;트랜잭션 정상 종료하기 (SQL 반영)commit;트랜잭션 실패처리(SQL 미반영)rollback; 트랜잭션 적용과 영속성 컨텍스트우리가 원하는 것은서비스 메소드가 시작할 때 트랜잭션이 시작되어서비스 메소드 로직이 모두 정상적으로 성공하면 commit 되고서비스 메소드 로직 실행 도중 문제가 생기면 rollback 되는 것@Transactional 어노테이션으로 우리가 원하는 것을 할 수 있다!SELECT 쿼리만 사용할경우, readOnly 옵션을 사용하여 데이터 변경을 위한 기능이 빠져 약간의 성능 향상이 있다. 영속성 컨텍스트란?테이블과 매핑된 Entity 객체를 관리/보관하는 역할⇒ 스피링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.변경 감지 (Dirty Check): 영속성 컨텐스트 안에서 불러와진 Entity는 명시적으로 save하지 않더라고, 변경을 감지해 자동으로 저장된다.@Transactional public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); //userRepository.save(user); }유저의 정보가 업데이트가 되었네? 바뀌었구나? 하고 자동으로 저장된다. 따라서 save메소드를 작성해주지 않아도 된다.쓰기 지연: DB의 INSERT / UPDATE / DELETE SQL을 바로 날리는 것이 아니라, 트랜잭션이 commit될 때 모아서 한 번만 날린다.예시로 유저가 저장되는 부분이 여러개 일때, 하나씩 날려서 저장하는것이 아니라 우선 기억해두고 한번에 저장하여 DB에 보내게 된다. 1차 캐싱: ID를 기준으로 Entity를 기억한다.쓰기 지연과 비슷하게 이전에 캐싱된 객체를 기억하고 있다가 동일한 값이면 DB에 계속해서 요청하지 않고 알려준다.책 생성 API 개발하기create table book (id bigint auto_increment, name varchar(255), primary key(id));name varchar(255)을 사용한 이유?@Column의 length 기본값이 255문자열 필드는 최적화를 해야 하는 경우가 아닐 때 조금 여유롭게 설정하는 것이 좋다.회고4번째과제는 과일가게 판매에 관련된 내용이었다. 삼단분리 후 뭔가 직접 구현해봐야하는 문제라 그런지 사실 과제를 하면서 재밌게 느껴졌다. 단순히 요구사항에 나와있는데로만 작성하다보니 id를 왜 long 타입으로 써야했을까? 등 '왜' 라는 생각을 잘 하지 못했는데 앞으로는 왜? 의문점을 가지고 생각해봐야겠다!!5번째 과제에선 클린코드에 나와있는 내용을 바탕으로 더 좋은 코드로 변경해보는 과제이었다.문제를 보고 우선 각각의 기능을 하는 함수로 나누어 분리해야겠다는 생각이 들었다. 또 if-else문을 지양해야한다고 했지만, 너무 많은 조건을 가지고 있는 if-else문의 경우에는 사용을 하는것이 오히려 나을수도 있다는 블로그 글을 참고하여조건부분을 따로 함수로 만들어 그 함수를 호출하는 if-else문을 생각하고 1차 리팩토링 하였다.하지만 그래도 if-else문에는 너무 반복되는 부분이 많았고 이부분을 결국 for문으로 변경하였다..!금요일에 깜짝 라이브를 참석하지 못해서 너무 아쉬웠지만 따로 내용을 올려주셔서 참고할 수 있었다 : )
백엔드
・
워밍업
・
BE
・
Spring
2024. 05. 04.
0
인프런 워밍업 클럽 BE 1기 - 1주차 발자국
1강spring.io ⇒ springboot 프로젝트 만들기spring boot는 톰캣이 내장되어 있음 (Packaging - Jar)2강 @SpringBootApplication과 서버@ → 어노테이션@SpringBootApplication ⇒ 자동으로 설정서버란 ?: 기능을 제공하는 것, 어떠한 기능을 제공하는 프로그램, 그 프로그램을 실행시키고 있는 컴퓨터기능을 제공하기 위해서는 누군가의 요청이 있어야 한다.3강 네트워크란 무엇인가?각각의 PC에는 고유 IP 존재IP를 모두 외우기 어렵기 때문에 도메인 이름을 사용 (ex - naver.com / spring.com ) ⇒ DNS (Domain Name System)4강 HTTP와 API란 무엇인가?데이터를 주고 받는 표준이 존재HTTP (HyperText Transfer Protocol)GETPOSTPUTDELETEAPI (Application Programming Interface): 클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하는데 이때 정해진 규칙을 API라고 한다.URL (Uniform Resource Locator)HTTP 응답 코드2003004005005강 GET API 개발하고 테스트하기@RestController→ API의 진입지점 만들기@RestController를 사용하면 API의 진입지점인것을 알수 있다.6강 POST에서는 데이터를 어떻게 받을까 ?HTTP Body를 이용!10강 Database와 MySQL컴퓨터의 핵심 부품CPU : 연산RAM : 임시 기억장치DISK : 장기 기억장치DatabaseRDB (Relational Database)SQL (Structured Query Language)MySQL 접근intellij ultimateCLImysql -u root -p11강 MySQL에서 테이블 만들기 DDL (Data Definition Language)데이터베이스 만들기create database [데이터베이스 이름];데이터베이스 목록보기show databases;데이터베이스 지우기drop database [데이터베이스 이름];데이터베이스 안으로 들어가기use [데이터베이스 이름];테이블 목록 확인하기show tables;테이블 만들기create table [테이블 이름]([필드1 이름][타입][부가조건],[필드2 이름][타입][부가조건],…primary key ([필드이름]));create table fruit (id bigint auto_increment, name varchar(20), price int, stocked_date date, primary key (id)); 테이블 제거하기drop table [테이블 이름];MySQL 타입tinyint : 1바이트 정수int : 4바이트 정수bigint : 8바이트 정수 (21억건이상)double : 8바이트 정수decimal(A,B) : 소수점을 B개 가지고 있는 전체 A자릿수 실수char(A) : A글자가 들어갈 수 있는 문자열varchar(A) : 최대 A글자가 들어갈 수 있는 문자열date : 날짜, yyyy-MM-ddtime : 시간, HH:mm:ssdatetime : 날짜와 시간을 합친 타입, yyyy-MM-dd HH:mm:ss12강 DML (Data Manipulation Language)CRUD데이터 넣기INSERT INTO [테이블 이름] (필드1이름, 필드2이름,…) VALUES (값1, 값2,…)INSERT INTO fruit (name, price, stocked_date) values ('사과', 1000, '2023-05-01');데이터 조회, 업데이트, 삭제select * from [테이블명] where [조건]update [테이블 이름] set 필드1이름 = 값, 필드2 이름 = 값,… where [조건]delete From [테이블 이름] where [조건];13강 Spring에서 Database 사용spring 프로젝트 Resource폴더 아래에 application.yml 파일 생성spring: datasource: url: "jdbc:mysql://localhost/library" //jdbc를 이용해 Mysql에 접근 username : "root" password: "" driver-class-name: com.mysql.cj.jdbc.Driverapplication.yml @GetMapping("/user") public List getUsers() { String sql = "SELECT * FROM user"; jdbcTemplate.query(sql, new RowMapper() { @Override public UserResponse mapRow(ResultSet rs, int rowNum) throws SQLException { long id = rs.getLong("id"); String name = rs.getString("name"); int age = rs.getInt("age"); return new UserResponse(id, name, age); } }); }익명 클래스 사용 RowMapper@GetMapping("/user") public List getUsers() { String sql = "SELECT * FROM user"; return jdbcTemplate.query(sql, (rs, rowNum) -> { long id = rs.getLong("id"); String name = rs.getString("name"); int age = rs.getInt("age"); return new UserResponse(id, name, age); }); }RowMapper에 option + enter ⇒ 람다식으로 변경14강 ~ 16강public void updateUser( UserUpdateRequest request) throws IllegalAccessException { boolean isUserNotExist = userRepository.isUserNotExist(request.getId()); if(isUserNotExist){ throw new IllegalAccessException(); } userRepository.updateNameUser(request.getName(), request.getId()); } public void deleteUser(String name) throws IllegalAccessException { boolean isUserNameNotExist = userRepository.isUserNameNotExist(name); if(isUserNameNotExist){ throw new IllegalAccessException(); } userRepository.deleteUser(name); }수정, 삭제 전 사용자가 DB에 존재하는 데이터인지 확인하기 위해 예외처리를 해주었다.jdbcTemplate.query ……. ⇒ List로 반환된다.isEmpty()를 붙여 boolean 타입인지 확인하도록 한 후 조건문 실행만약 존재 하지 않는 유저라면 IlleagalAccessException()이 호출된다.PostMan으로 확인한 결과홍합이란 유저가 없어서 500이 떴다!17강 좋은 코드란 무엇인가코드는 요구사항을 표현하는 언어개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다. 코드를 읽는 것은 필수적이고 피할 수 없다안좋은 코드가 쌓이면, 시간이 지날 수록 생산성이 낮아진다! Controller에서 모든 기능을 구현하면 왜 안될까?⇒ 함수는 최대한 작게 만들고 한 가지 일만 수행하는 것이 좋다. 18강. Controller를 3단 분리하기 - Service와 Repository기존 Controller의 함수 1개가 하고 있던 역할API의 진입 지점으로써 HTTP Body를 객체로 변환하고 있다. ⇒ Controller현재 유저가 있는지, 없는지 등을 확인하고 예외 처리를 해준다. ⇒ ServiceSQL을 사용해 실제 DB와의 통신을 담당한다. ⇒ RepositoryLayered ArchitectureController ← Service ← Repository미션 & 일주일 회고미션 (1 ~ 3번째 과제)https://charm-vise-f40.notion.site/016b87f39bbe4c31991ab1c51632bd73https://charm-vise-f40.notion.site/2-372b48a579674c988a990fa4b9a14ebb?pvs=4https://charm-vise-f40.notion.site/3-212dca4aeb81494c9d63f66689b90e4f?pvs=4워밍업 클럽이 벌써 시작한 지 일주일 정도가 흘렀다.진도표에 맞춰 강의를 듣고 과제를 진행하다 보니 시간이 금방 흐른 느낌이다!!강의를 보다 보면 내가 다 아는 것 같은 느낌이 들었는데 역시 과제를 하면 내가 어느 정도 까지 이해했는지 알수있었다 ㅎㅎ두 번째 과제를 할 때에는 출력값이 JSON으로 문제에는 나와 있지만 처음에 무작정 하다 보니 나는 그냥 단순 값으로 포스트맨에 출력되었다.뭐지? 뭐가 잘못된 거지 하면서 고민하다 보니까 객체로 반환을 하면 되는 것이었다 ㅜ세 번째 과제는 강의를 들으면서 익명, 람다식 등 잘 기억이 나지 않았는데 마침 강사님께서 과제로 내주셨다나도 코딩 님의 자바 강의에 들으면서 익명함수, 람다 식을 다시 공부했다. 좀 더 익숙해질 때까지 계속 사용해 봐야겠다.아직 초반이라 그런지 사실 강의는 매우 재밌었다. 그렇지만 스스로 자바개념이 아주 부족하다고 생각하기에주말을 활용하여 자바 개념과 강의 내용을 복습해 봐야겠다!
웹 개발
・
인프런
・
워밍업
・
회고