스프링은 자바 어플리케이션 쉽게 잘 만들수 있는 컨테이너 프레임워크다.
웹에 너무 매몰되서 이해하면 산으로 갈 수 있다.
김영한님의 강의를 들으며, 이 부분부터 다시 초점을 잡아 정리하는 것이 포스팅 목표이다.
근데 강의가 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가 자동으로 된다.
- 웰컴페이지 만들기 : src/main/resources/static/index.html 생성
- 동적 렌더링 : 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}
- 템플릿 엔진 동작 성공에 따라 아래 두가지로 표기됨
- <p th:text="'hello ' +${name}">hello! empty</p>
- 서버에서 변형해서 넘겨주는 방식
- 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
- 정적 컨텐츠(Static Content)
- 섹션 3. 회원 관리 예제 - 백엔드 개발
- 비즈니스 요구사항 정리
- 컨트롤러(Web MVC의 Controller) > 서비스(핵심 business로직) > 레포지토리(DB접근, 객체 관리) > DB
- + 도메인(비즈니스 도메인 객체) ex)회원, 주문, 쿠폰 ...
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- 아직 데이터 저장소가 선정되지 않음.
- Class Diagram : MemberService -> MemberRepository(Interface) -> MemoryMemberRepository(Impl)
- 인터페이스로 구현 클래스를 변경할 수 있도록 설계
- 메모리 기반의 DB사용(일단 간단)
- 컨트롤러(Web MVC의 Controller) > 서비스(핵심 business로직) > 레포지토리(DB접근, 객체 관리) > DB
- 회원 도메인과 리포지토리 만들기
- <Optional> : JAVA8 기능. null 대신 Optional로 감싸서 반환하기 위한 목적의 객체.
- null대신 optional 반환으로 client에서 뭔가(추가행위)를 할 수있음.
- hashMap<>() 사용
- 실무에서는 동시성 문제가 있어서(공유되는 변수라서), concurrent HashMap 사용해야 함.
- Long : 실무에서는 AtomicLong으로 동시성 문제 해결
- <Optional> : JAVA8 기능. null 대신 Optional로 감싸서 반환하기 위한 목적의 객체.
- 회원 리포지토리 테스트 케이스 작성
- /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 기능.
- Application main 위치 package의 하위에 있는 것만 scan 한다.(default, 예외 가능)
- Bean(~~객체) 은 Singleton(유일하게, 1개만) 등록한다. 즉, 호출시 같은 주소값이 나온다.
- 예외로 non-singleton 가능하긴 하나, 하지말자.
- 컴포넌트 스캔 자동 의존관계 설정 방식 : @Component(@Service, @Repository, @ Controller..)
- 자바 코드로 직접 스프링 빈 등록하기
- 정형화 되지 않은 Bean(@controller,@service 등이 아님), 구현 클래스 변경 가능성 있는 것에 사용한다.
- 설정파일 하나만 변경 가능하면 되도록.
- @configuration이 붙는 파일 생성
- @Bean 추가해서 각 객체 생성방식 선언.
- Controller는 어쩔수 없이 @Autowired로 만들어야 함. 어디서 설정 할 수있는 것이 아님. : ?????
- 정형화 되지 않은 Bean(@controller,@service 등이 아님), 구현 클래스 변경 가능성 있는 것에 사용한다.
- pre-
- cf) Dependancy Injection 설명
- constructor 주입 : 생성자 통해서 dependancy 있는 객체 할당
- Application 조립 시점에 딱 한번 생성되므로 안전.
- field 주입 : 생성자 없애서 field에 바로 @autowired
- 별로 안좋음. 중간에 바꿔치기 불가
- setter 주입 : setter를 통해서 set하면서 dependancy 있는 객체 할당.
- public으로 setting이 노출되어 중간에 바뀔 위험에 노출.
- constructor 주입 : 생성자 통해서 dependancy 있는 객체 할당
- 섹션 5. 회원 관리 예제 - 웹 MVC 개발 : Controller -> ? 기능 구현
- 회원 웹 기능 - 홈 화면 추가
- 우선순위 : Spring Container - Controller > /static *.html
- index.html < "/"
- 우선순위 : Spring Container - Controller > /static *.html
- 회원 웹 기능 - 등록
- Get방식 : url 직접 치기. 단순 이동. static에서 찾음
- form type의 html을 선언하고, action : url + method : post 로 선언
- controller 에 해당 url + PostMapping 메소드 선언
- PostMapping 선언 Method 실행됨.
- name : 서버로 넘어갈 때 key의 역할.
- mapping 되는 class에 같은 이름을 가지는 변수 자동으로 찾아서 mapping함
- 우와 신기하다~~
- placeholder : 아무것도 없을 때 표시될 것.
- submit 버튼 누르면 action 동작함.
- name : 서버로 넘어갈 때 key의 역할.
- 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);
}
- public JdbcTemplateMemberRepository(DataSource 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;
}
- private final MemberRepository memberRepository;
- CRUD
- 공통이 아닌 메소드
- 이름으로 Query 메소드 생성
- <Member, Long> findByName(String name)
- JPQL 번역을 거친다 : select m from Member m where m.name = ?
- 결국 where절이 변경 되는 것이다.
- 원리는 reflection 기술로 읽어서 풀어 내는 것.
- 공통이 아닌 메소드
- Repository : Interface extends JpaRepository
- JPA를 편하게 쓸 수 있도록 wrapping 한 것.
- H2 데이터베이스 설치 : socket으로 연결..?

- 섹션 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하여 위아래 넣는 경우도 있다.
- AOP가 필요한 상황