Back/spring

[김영한,Inflearn]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

TimeSave 2022. 12. 26. 14:38

스프링은 자바 어플리케이션 쉽게 잘 만들수 있는 컨테이너 프레임워크다.

웹에 너무 매몰되서 이해하면 산으로 갈 수 있다.

김영한님의 강의를 들으며, 이 부분부터 다시 초점을 잡아 정리하는 것이 포스팅 목표이다.

근데 강의가 web 기준이네..

=> web이 아니고, MVC 구성인 게 있을 수도 있다!(아래 왼쪽)

=> @Bean으로 등록하면 되니까, @component 아니어도 상관 없긴 하다!(아래 오른쪽)

 

 

 

스프링을 왜 공부해야 하는가?
: 실무에서 제대로 동작하는 웹 어플리케이션을 만들기 위해서

 

학습목표

: 사이클을 돌려서 전체 Overview를 갖추기

: 스프링 학습의 첫 길잡이 역할

 
  • spring boot reference 확인 법
    • https://spring.io -> Project > Spring Boot > Learn > 버전 선택, Reference Doc 클릭 > 각 항목 확인
 
 
 
 
 
 
 
  • 섹션 1. 프로젝트 환경설정
    • 프로젝트 생성
      • JAVA 11
      • IntelliJ
      • https://start.spring.io
        • Gradle(groovy) + Java
        • Spring Boot version은 정식버전으로 선택
          • 뒤에 영어가 붙어있지 않은게 정식버전 ex) 2.7.0[ㅇㅇ], 2.7.1(SNAPSHOT)[ㄴㄴ]
        • group : 회사명 등등, artifact : 빌드 결과물
        •  
      •  
    • 라이브러리 살펴보기
group = 'hello' //회사명, 개발그룹 명 등등
version = '0.0.1-SNAPSHOT' //기본으로 설정되는 버전명
sourceCompatibility = '11' //java 11 프로젝트 명시

repositories {
   mavenCentral() //라이브러리 다운로드 사이트 설정
}

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' //템플릿 엔진
   implementation 'org.springframework.boot:spring-boot-starter-web' //Spring Web
   testImplementation 'org.springframework.boot:spring-boot-starter-test' //Test용 Junit5 기본 탑제
}

tasks.named('test') {
   useJUnitPlatform()
}

- Spring core, Spring context, logging, autoconfigure, thymleaf, annotation..

- slf4j(interface) & logback(implementation)

- junit + mokito, assertJ, spring-test(spring과 integration해서 test하는 library)

 

의존관계를 gradle에서 확인할 수 있다.

*은 다른곳에서 이미 포함해서 생략했다는 뜻. 

 

 - 스프링 부트 라이브러리

 --  spring-boot-starter-web : 톰캣 & MVC

 --  spring-boot-starter-thymeleaf : 타임리프 템플릿 엔진(View, html 렌더링)

 -- spring-boot-startet : 스프링부트 + 스프링코어 + 로깅(logback,slf4j)

 

 - 테스트 라이브러리

- Junit : 테스트 프레임워크

- mokito : 목 라이브러리

- assertJ : 테스트코드 작성을 도와주는 라이브러리

- spring-test : 스프링 통합 테스트 지원(스프링 서버 올려서 동작 확인)

 

  • View 환경설정
    • 정적페이지
      • 웰컴페이지 만들기 : src/main/resources/static/index.html 생성
        • spring boot에서는 static/index.html이 welcome page가 자동으로 된다.
    • 동적 렌더링 : thymeleaf
      • Controller(웹에서 가장처음 접근하는 진입점) 작성
      • @GetMapping("hello") : WebApplication에서 /+""을 치면 호출하도록 매핑하는 기능
      • Controller{return String} -> viewResolver(resources:templates/+{viewname}+.html){*.html return}
      • spring-boot-devtools 로 html만 컴파일 하면 서버 재시작 없이 view를 갈아 끼울 수 있다.
        • 메뉴 > build > recompile

 

  • 빌드하고 실행하기
    • http://localhost:8080/  -> Whitelabel Error Page 표시되면 성공.
    • IntelliJ로 프로그램 실행시, gradle 통해서 실행되는 경우가 있다(일반 java도) 아래 Gradle -> IntelliJ로 바꿔주자.
    • cmd > 프로젝트 경로 이동 > gradlew build(gradlew.bat 실행) > /build/libs에 jar 파일 생성됨 >  java - jar 실행
      • 종료 : netstat -ano | find "8080" -> taskkill /pid {pidnum} /f
 
 
 
  • 섹션 2. 스프링 웹 개발 기초
    • 정적 컨텐츠(Static Content)
      • 서버에서 front 파일 그대로 전달. 화면에 프로그래밍 불가
      •  request - > Controller 탐색 -> 찾기 결과 없을 시 -> resources:static/1asd!@#!@.html 발견 > 전달
    • MVC와 템플릿 엔진 : html로 넘기는 것
      • 서버에서 변형해서 넘겨주는 방식
        • 브라우저 -> Controller -> return + model -> viewResolver 에서 변환 return -> 브라우저
      • 템플릿엔진
        • <p th:text="'hello ' +${name}">hello! empty</p>
          • 템플릿 엔진 동작 성공에 따라 아래 두가지로 표기됨
            • 템플릿 엔진 미동작 시 : hello! empty
            • 템플릿 엔진 동작 시 : 'hello' +${name}
    • API : data로 바로 넘기는 것, view 없이 url로 요청 시 바로 data 연결 됨(ViewResolver X)
      • "JSON 데이터 포멧(default)"으로 클라이언트에게 전달.
      • request > Controller > @requestBody > HttpMessageConverter:JsonConverter 에서 return Model("key:value") > return {key:value}
        • 모바일 앱
        • 서버끼리 통신하는 경우 사용
      • @ResponseBody : http통신 body 부분에 해당 데이터를 직접 넣겠다는 뜻.
      • HttpMessageConverter 종류
        • StringHttpMessageConverter : 기본 문자 처리에 사용
        • MappingJackson2HttpMessageConverter : 기본 객체 처리(객체를 JSON변환 해주는 라이브러리)
          • cf) GSON
cf) getter/setter : Java bean 규약에 포함 됨. 그냥 패턴인줄 알았는데, 규약 level까지 해당되는 방식(property접근방식)
 
 
 

 

 
  • 섹션 3. 회원 관리 예제 - 백엔드 개발
    • 비즈니스 요구사항 정리
      • 컨트롤러(Web MVC의 Controller) > 서비스(핵심 business로직) > 레포지토리(DB접근, 객체 관리) > DB
        • + 도메인(비즈니스 도메인 객체) ex)회원, 주문, 쿠폰 ... 
      • 데이터 : 회원ID, 이름
      • 기능 : 회원 등록, 조회
      • 아직 데이터 저장소가 선정되지 않음. 
      • Class Diagram : MemberService -> MemberRepository(Interface) -> MemoryMemberRepository(Impl)
        • 인터페이스로 구현 클래스를 변경할 수 있도록 설계
        • 메모리 기반의 DB사용(일단 간단)
    • 회원 도메인과 리포지토리 만들기
      • <Optional> : JAVA8 기능. null 대신 Optional로 감싸서 반환하기 위한 목적의 객체.
        • null대신 optional 반환으로 client에서 뭔가(추가행위)를 할 수있음.
      • hashMap<>() 사용
        • 실무에서는 동시성 문제가 있어서(공유되는 변수라서), concurrent HashMap 사용해야 함.
      • Long : 실무에서는 AtomicLong으로 동시성 문제 해결
    • 회원 리포지토리 테스트 케이스 작성
      • /test 하위에 동일 package경로로 test코드를 작성한다.(@Test)
      • junit.jupiter.api.Test
      • main 메소드 작성과 상당히 비슷한 느낌으로 작성하면 됨
      • org.junit.jupter.Assertions or org.assertj : valid check 
      • @afterEach() : 각 테스트 끝날 때 마다 호출하는 Callback Method
    • 회원 서비스 개발(Business)
      • Repository Class : 개발스러운 용어로 작성 
      • Service Class : business에 가까운 용어로 작성해야 함.
    • 회원 서비스 테스트
      • IntelliJ 테스트 작성 개꿀팁 : Cmd+Shift+T(ctrl+Shift+T) : create Test
      • given/when/then 문법
      • fail() 메소드 사용해서 성공여부 떠나서 확실하게 noti 주는 방식이 있음.
      • 강의에서 repository 새로 생성해서 clear하는데도 Test 되는 이유
        • 내부 Map이 static으로 선언되어 있음. class단위로 붙기 때문에 이 변수는 동일하게 됨
        • constructor 생성해서, 외부에서 내부변수 넣어주는게 좋음 = Dependancy Injection

 

 
  • 섹션 4. 스프링 빈과 의존관계
    • pre-
      • 섹션 목표 : 화면 붙이기
      • 의존관계가 있다. : Controller call Service
      • Spring load 시 Container 라는 통이 생김
        • 각 annotation의 객체를 생성해서 통에 넣어 둠. 그리고 관리 함.
        • = container에 의해 bean이 관리된다.
        • 때문에, 객체를 spring container에서 받아서 쓰도록 코드를 작성해야 함.(DI)
    • 컴포넌트 스캔과 자동 의존관계 설정
      • 컴포넌트 스캔 자동 의존관계 설정 방식 : @Component(@Service, @Repository, @ Controller..)
        • 객체를 constructor 통해서 만들도록 구현하고, constructor 상단에 @Autowired 선언하기
        • Service는 @Service, Repository는 @Repository
        • Interface의 경우, 구현체를 찾아서 Injection한다.
        • Controller -> Service -> Repository 경로가 이어지도록 외부 생성자 부분에 선언해야 한다.
        • 아무데나 @Component 달면 되는가?? : 안된다.
          •  Application main 위치 package의 하위에 있는 것만 scan 한다.(default, 예외 가능)
            • @Component Scan 기능.
        • Bean(~~객체) 은 Singleton(유일하게, 1개만) 등록한다. 즉, 호출시 같은 주소값이 나온다.
          • 예외로 non-singleton 가능하긴 하나, 하지말자.
    • 자바 코드로 직접 스프링 빈 등록하기
      • 정형화 되지 않은 Bean(@controller,@service 등이 아님), 구현 클래스 변경 가능성 있는 것에 사용한다.
        • 설정파일 하나만 변경 가능하면 되도록. 
      • @configuration이 붙는 파일 생성
        • @Bean 추가해서 각 객체 생성방식 선언.
        • Controller는 어쩔수 없이 @Autowired로 만들어야 함. 어디서 설정 할 수있는 것이 아님. : ?????

 

 

  • cf) Dependancy Injection 설명
    • constructor 주입 : 생성자 통해서 dependancy 있는 객체 할당
      • Application 조립 시점에 딱 한번 생성되므로 안전.
    • field 주입 : 생성자 없애서 field에 바로 @autowired
      • 별로 안좋음. 중간에 바꿔치기 불가
    • setter 주입 : setter를 통해서 set하면서 dependancy 있는 객체 할당. 
      • public으로 setting이 노출되어 중간에 바뀔 위험에 노출.
 
  • 섹션 5. 회원 관리 예제 - 웹 MVC 개발 : Controller -> ? 기능 구현
    • 회원 웹 기능 - 홈 화면 추가
      • 우선순위 : Spring Container - Controller > /static *.html
        • index.html < "/" 
    • 회원 웹 기능 - 등록
      • Get방식 : url 직접 치기. 단순 이동. static에서 찾음
      • form type의 html을 선언하고, action : url + method : post 로 선언
      • controller 에 해당 url + PostMapping 메소드 선언
      • PostMapping 선언 Method 실행됨.
        • name : 서버로 넘어갈 때 key의 역할.
          • mapping 되는 class에 같은 이름을 가지는 변수 자동으로 찾아서 mapping함
          • 우와 신기하다~~
        • placeholder : 아무것도 없을 때 표시될 것.
        • submit 버튼 누르면 action 동작함.
      • Get : 조회(이렇게 외우면 안됨)
      • Post : 데이터 전달(이렇게 외우면 안됨)
        • method에 따라 mapping이 다른 것만 이해하자.
    • 회원 웹 기능 - 조회(회원목록)
      • model로 Parameter 선언하면 모든게 mapping 된다. 
 
  • 섹션 6. 스프링 DB 접근 기술
    • H2 데이터베이스 설치 : socket으로 연결..?
      • project에 sql폴더 만들고 ddl 저장해서 관리하는 게 좋음
    • 순수 JDBC
      • gradle에 library 추가.(jdbc, h2 client) 
      • application.properties에 동일하게 추가
      • repository class 생성
        • javax.sql.Datasource 변수 선언
        • DataSource Constructor 선언 : application.properties 정보로 접속정보 생성해 놓고 조립할 때 주입함
        • insert method 구현 : dbConnection 가져오기 -> prepareStatement(sql read/set parameter "?"/sql executeUpdate) -> ResultSet(Generated Keys)
        • 예외를 엄청 많이 던지므로, try-catch를 잘하기 + 자원 release 잘하기(connection 바로 끊고, resource 바로 반환[생성 역순으로])
        • dataConnection이 계속 쌓이면 장애가 날 수 있다.
        • select method 구현 : dbConnection 가져오기 -> prepareStatement(sql read/set parameter "?"/sql executeQuery) -> ResultSet
        • Spring 통해서 connection 획득/해제하려면 DataSourceUtils.getConnection() 으로 가져와야 함.
          • 이전 transaction 확인을 해서 처리해 줌 
      • 구현 완료 후, implement class 갈아끼우기.(다형성 활용, 스프링 장점)
        • OCP: app코드는 안바꾸고 변경(일종의 확장) 가능해진 예시
        • Spring DI를 사용해 설정파일만 바꿨기 때문.
      • DataSource는 Spring Boot가 application.properties보고 자체적으로 bean 생성함.
    • 스프링 통합 테스트
      • @SpringBootTest,
      • @Transactional : "TestCase에 붙어있을 경우", Transaction 실행하고, 끝나면 rollback 해 줌.
        • @특정 테스트 메소드에 @Commit을 넣으면 반영되어버림.
      • TC는 제일 끝단이므로, 그냥 field injection 하는 편이다(@Autowired)
      • DB는 Transaction이란 개념이 있다. commit을 해야 db에 반영이 됨.
      • 단위테스트 : 순수한 JAVA Test. 훨씬 좋은 테스트. 단위단위 잘 게 쪼개기.
      • 통합테스트 : Spring Container 사용하는 테스트. Container 올려야 하는 상황이면 테스트 설계가 잘 못 되어 있을 확률이 높음.
    • 스프링 JdbcTemplate : 중복을 제거해서 App에서 DB로 sql 쉽게 날림 
      • naming 유래 : Template method 패턴 사용
      • mybatis 비슷함
      • JDBC API의 반복코드를 제거해 줌
      • injection 받을 수 있는 건 아님, 아래와 같이 구현(new~~~)
        • public JdbcTemplateMemberRepository(DataSource dataSource) {
                  jdbcTemplate = new JdbcTemplate(dataSource);
              }
      • Spring bean은 생성자가 오직 1개일 경우 @Autowired를 생략할 수 있다.
      • row Mapper : 결과 매핑...?, resultSet을 row 단위로 mapping 한다.
      • SimpleJdbcInsert : with table name + using generated key column
        • query 짤 필요 없다. table명, generated pk, column 만 있으면 생성되도록.
    • JPA : sql을 제거. 객체를 DB에 쿼리 없이 저장 가능
      • 개발생산성 향상 : 반복코드 + 쿼리 생성 제거하는 장점
      • SQL 보다 객체 중심으로 고민할 수 있다.
      • 설정추가
        • dependancy(gradle)에 implementation 'org.springframework.boot:spring-boot-starter-data-jpa 추가하기
        • spring.jpa.show-sql=true : jpa가 생성한 spl 볼 수 있는 설정
        • spring.jpa.hibernate.ddl-auto={boolean}: auto-ddl. 객체를 보고 table 알아서 생성.
      •  jpa : interface. Object + Object Relational Mapping(by annotation)
      • hibernate : jpa implements
      • @Entity : JPA가 관리하는 Entity 임을 선언
      • @Id : PK임을 선언
      • GeneratedValue(strategy = GenerationType.IDENTITY) : DB가 알아서 생성해주는 value라는 선언 
        • DB가 id 자동생성 해주는 것을 identity(전략) 이라고 함.
      • repository 생성 -> Entity Manager
        • EntityManager : JPA는 EntityManager 통해서 모든 동작 수행한다 정도만 알기. Spring Boot가 자동생성
        • CREATE 문
          • EntityManager.persist({model}) : 영속하다. 영구 저장하다. CREATE query.
        • SELECT 문
          • PK일 경우 : EntityManager.find({modelType},{PK})
          • PK 아닐 경우(JPQL) : EntityManager.createQuery("select m from Member m", Member.class) 
            • 객체를 대상으로 query 요청. 
            • Member (as) m
            • select m : * 이 아니라 객체 자체를 select
            • setparameter()로 다른 변수 추가 할당 가능
      • Service class 
        • @Transactional 추가 : JPA 쓸 때 항상 Transaction 있어야 함. 모든 Data 변경이 Transaction 안에서 실행 되어야 함.
      • Bean Config Class에 EntityManager 추가
        • 방법1. @PersistenceContext 선언 : spec상으론 이렇게
        • 방법2. @Autowired : 그냥 DI 사용하여 연결
    • 스프링 Data JPA
      • JPA를 편하게 쓸 수 있도록 wrapping 한 것.
        • JPA를 무조건 먼저 알아야 제대로 쓴다. 모르면 운영 문제 해결 불가.
      • 인터페이스만으로 개발 완료 가능. RDB 사용 시 필수요소.
      • 인터페이스 통한 기본 CRUD 제공/ 메서드 이름 만으로 조회 제공/ 페이징 제공
      • 동적 쿼리는 Querydsl 라이브러리를 추가 사용한다!
        • 이것도 어렵다면, 네이티브 쿼리 or JdbcTemplate 사용.
      • 구현
        • Repository : Interface extends JpaRepository
          • extends JpaRepository 가 있으면 Spring Data Jpa가 Implements를 자동 생성하여 Bean에 알아서 등록 해 줌. (=proxy)
          • 생성자 주입을 선언하고, 등록된게 없다면 알아서 interface를 찾는다. 그리고 구현체 생성해서 넣는다.
            •   private final MemberRepository memberRepository;

                  @Autowired 
                  public SpringConfig(MemberRepository memberRepository) {
                      this.memberRepository = memberRepository;
                  }
        • CRUD
          • 공통이 아닌 메소드
            • 이름으로 Query 메소드 생성
            • <Member, Long> findByName(String name)
              • JPQL 번역을 거친다 : select m from Member m where m.name = ?
              • 결국 where절이 변경 되는 것이다.
              • 원리는 reflection 기술로 읽어서 풀어 내는 것.
CRUD가 JPA Repository에 구현되어 있다.
  • 섹션 7. AOP : 나는, 네이밍이 좀 헷갈리게되어 문제라고 생각한다. Aspect J
    • AOP가 필요한 상황
      • 메소드 호출 시간 측정 : 메소드 시작~끝에 로깅을 넣어야 함
        • 공통관심사항 : 시간측정 같은 건 핵심 비즈니스 로직이 아니다.
        • 핵심관심사항 : 회원가입, 조회 등등 비즈니스 로직이다.
        • 로깅을 이렇게 넣으면 공통관심사항, 핵심관심사항이 섞여서 유지보수가 어렵다.
    • AOP 적용 : 메소드 호출을 중간에 intercept하는 기술
      • 원하는 곳에 공통관심사항(시간측정 등 공통로직..) 삽입
      • AOP 메소드를 구현할 Class 생성
      • class 상단에 @Aspect 표기 필요
      • public Object 메소드명(ProceedingJoinPoint joinPoint) throws Throwable{  try{}finally{}}
      • Spring Bean 등록(component Scan보다 Bean등록이 더 좋은 방식, 설정보고 AOP쓰는 걸 알 수 있음)
      • @Around("execution(* hello.hellospring..*(..))" : 적용 대상 설정으로, 패키지 경로를 적는다.
      • 메소드 호출마다 joinPoint가 intercept 하여 실행. 
      • 의존관계
        • AsIs : HelloController -> memberService
        • ToBe : HelloController -> meberSevice Proxy(가짜 Spring Bean) -> joinPoint.proceed() -> memberService
        • AOP는 즉 Proxy기술을 사용한다.
          • EnhancedBySpringCGLIB
          • AOP쓰면 Controller, Service, Repository 모두 Proxy로 생성되어 Spring Container서 관리하다 필요할 때 Dependancy Injection을 한다.
        • Spring외적으로, Compile time에 JAVA code Generate하여 위아래 넣는 경우도 있다.