스프링 부트 시작하기

스프링 부트 소개

  • 토이 수준이 아닌 Spring 기반의 Production-grade의 독립적 어플리케이션을 빠르고 쉽게 만들 수 있도록 도와줌
  • Opinionated view: 개발자가 직접 설정하지 않고도, Spring boot가 정의하는 Convention을 자동으로 설정해주는 것
    • 따라서 모든 것을 일일이 설정하지 않아도 됨
    • Spring platform 뿐 아니라, third-party library에 대한 설정도 자동으로 해줌
      • ex) Tomcat의 8080 port 설정...
  • 스프링 부트의 목표
    • 모든 스프링 개발에 있어 빠르고, 폭 넓은 사용성을 제공
    • Opinionated view를 제공하지만, 개발자가 원할 시 쉽고 빠르게 설정 변경 가능
    • 비즈니스 로직을 구현하는데 필요한 기능 뿐 아니라 Non-functional한 기능도 제공
    • xml 설정을 더 이상 하지 않고, code generation을 하지 않음
      • 쉽고 명확하며, 더 편리하게 customizing이 가능해짐!

스프링 부트 시작하기

  • group id는 보통 package명으로 설정
  • spring boot의 parent로 spring-boot-starter-parent 추가해줌
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>
  • Maven 간 프로젝트 계층 구조 만드는 것

    • spring-boot-starter-parent를 부모 프로젝트로 설정
    • Spring boot가 제공하는 의존성 관리와 매우 관련이 깊은 설정!
  • spring-boot-starter-web 의존성 추가

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
  • build plugin 추가
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • IntelliJ에서 psvm 으로 Main 함수 추가 가능
  • mvn package로 maven project build 가능
    • jar 파일을 결과물로 생성!
  • 이후, java -jar 'jar 명'으로 jar 파일 생성 가능

스프링 부트 프로젝트 구조

  • 메이븐 기본 프로젝트 구조와 동일
    • 소스 코드
    • 소스 리소스
      • classpath가 root dir 의미
    • 테스트 코드
    • 테스트 리소스
  • @SpringbootApplication이라는 annotation이 달려 있는 MainApplication class는 가장 최상위 패키지에 두는 것이 추천됨
    • @SpringbootApplication을 기점으로 Component scan을 하기 때문
    • java 폴더 바로 밑에 Main class 두게 될 경우, 프로그램에 존재하는 모든 패키지를 뒤져서 component를 찾는 비효율 발생
    • 따라서, 다른 패키지에 있는 moduleㅡㄹ은 component로 등록이 안되기 때문에 디렉토리 구조를 설정는데 주의!

다형성: Polymorphism

  • Super class 타입의 변수가 Sub class 타입의 객체를 참조할 수 있다!
  • 예시) Notebook class가 Computer class를 상속 받고 있다고 가정
Computer computer = new Notebook("Bravo", "Intel", 4, 240, 2/4, 15)
  • Computer 타입의 변수 computer가 Notebook 타입의 객체를 참조하고 있음!

  • 다음의 예를 생각해보자
  • 가정) Computer와 Notebook class는 모두 toString method를 지니고 있음
System.out.println(compter.toString());
  • 위 예에서의 toString은 두 class 중 어느 class의 method를 호출하게 될까?
  • 답은 Notebook class의 toString !
    • 즉, 동적 바인딩(dynamic binding)이 일어남

      • Runtime 중에 참조 객체를 결정
    • cf. Computer class의 toString을 호출하는 경우, 정적 바인딩
      • 컴파일러가 참조 객체를 결정


'Software Convergence > Java' 카테고리의 다른 글

Object 클래스와 Wrapper 클래스  (0) 2018.11.13
static과 접근제한자  (0) 2018.10.19
상속의 단점과 Strategy Pattern  (0) 2018.09.03
Generic 자료형 실습  (0) 2018.07.12
instanceof / encapsulation  (0) 2018.07.04

Spring boot camp #3

기본적인 웹 Template

  • application과 사용자의 접점인 boundary
    • 조회 화면, 글쓰기 화면 등이 boundary의 예
  • 사용자가 요청한 업무를 처리하는 control
  • 게시물과 같은 entity
  • 외부 시스템(ex. 외부 결제 시스템)과의 접점도 필요함
    • 이 역시 boundary로 구현하여 control과 연동시킴
  • Transaction은 Spring JDBC, Spring Data JPA 그리고 외부 웹에서 자료를 받아오기 위한 Rest template 등에 의해 수행
    • DBMS 관리와 SQL에 대한 지식을 DBA 만큼은 아니더라도 갖추어야 함

Java와 Database programming

  • 입력, 수정, 삭제
      1. DB connection: connection Interface, DriverManager class 사용
      1. SQL 준비(ex. insert into table명 values(...))
      • DB가 sql을 이해하기 위해 문장을 parsing 하는데, 이는 큰 overhead를 발생시킴
      • parsing 결과를 DB 자체에 저장해두었다가, 완전히 동일한 sql문이 오면 이전에 저장해둔 값 사용
        • insert의 경우, values가 계속 변화할 수 있기 때문에 '?'로 채워뒀다가 ? 값만 바꾸어 사용 -> 성능 개선
      1. 바인딩: ?에 값을 채워주는 것
      • PreparedStatement interface 사용
      1. PreparedStatement의 executeUpdate method 호출
      1. DB connection close
  • 데이터 조회
      1. DB connection
      1. SQL 준비
      1. 바인딩
      1. executeQuery() method 실행
      1. ResultSet Interface의 next() method 이용하여 row 한 건 씩 읽어옴
      • 즉, SQL의 select 문에는 ResultSet interface가 사용됨
      1. 읽어온 row를 column별로 잘라서 사용
      • ResultSet의 getXXX method 사용
      • ex) getName(), getTitle()...
      1. DB connection close
  • connection Interface는 DBMS 프로그램별(MySQL, Oracle..)로 다름
    • 따라서, 이 Interface를 구현하는 class는 해당 DBMS 프로그램에 존재
    • 이 class를 Driver class라 함
  • DBMS를 다루다 보면 기능별로 중복된 코드가 발생할 수 있음
    • 그리고 이는 지루한 프로그래밍을 유발
    • 또한 Java 코드로 관리하기가 어려워짐
      • 문자열이 단순히 + 연산으로 이어지기 때문!
    • xml 파일에 id값 부여하여, SQL 코드 따로 관리하자!
  • 객체에 있는 정보를 ?에 넣어주고, data를 조회하면 객체로 받아올 수는 없을까?
    • SQL 중심으로 database 코드를 짜는 SQL mapper
      • MyBatis, Spring JDBC 등이 그 예
    • SQL만 작성하면 되기 때문에 더 쉽게 프로그래밍이 가능해짐
  • 그러나 SQL은 객체지향적이지 않음
    • ex) 게시물과 댓글의 1:n 관계
      • 게시물의 getComments()와 같은 method로 댓글을 읽어오고 싶음
      • 두 Entity 간의 관계를 객체지향적으로 표현하기가 어려움
        • 패러다임의 충돌: SQL적 사고와 객체지향적 사고에는 근본적인 차이가 존재
  • DBMS 프로그래밍을 객체지향적으로 할 수 있지 않을까?
    • 그러려면 우선 SQL 자체를 제거해야 한다!
    • 개발자는 객체만 생각하고, SQL은 어디선가 자동으로 만들어주도록 하자
    • 이렇게 나온 기술이 ORM(Object Relation Mapping) -> Hibernate
      • Hibernate는 어렵지만, 객체지향적 사고를 좋아하는 사람들에게 환영 받는 기술
      • Java 측에서 JPA(Java의 ORM 표준)이 등장!
      • JPA를 사용하면 SQL을 모르더라도 DBMS 사용 가능
  • Spring은 또 다른 고민에 빠짐
    • Data 저장소는 DBMS만 존재하는 것이 아님
      • ex) NoSQL(like. MongoDB), Hadoop...
    • 한 단계 더 추상화하여, Spring Data라는 module 제공
      • Spring Data JPA를 사용하여, module이 JPA 관리하도록 함
      • 때문에 Spring Data MongoDB와 같은 module도 존재
  • 결과적으로 JDBC -> Hibernate & JPA -> Spring Data의 발전 구조
    • 수업에서는 이를 다 다룰 수는 없기 때문에 Spring Data 위주로 다룸

about JPA

  • Spring Data JPA를 추가하면?
    • DataSource) (Interface: Connection Pool)
      • Spring Boot 2 에서는 HikariCP 사용
    • PlatformTransactionManager (transaction 관리자)
      • Spring은 AOP로 트랜잭션 관리 지원
    • JDBC Driver
      • h2: 인메모리 DBMS
      • web 환경 같이 사용할 경우에는 localhost:8080/h2-console로 클라이언트 접속이 가능
  • 아래와 같은 starter와 jdbc 드라이버 의존성을 추가하면, 자동으로 DataSource와 PlatformTransactionManager에 대한 설정이 추가 됨
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
  • DataSource ---> Connection Pool ---> DBMS
    • Connection Pool은 DBMS와 미리 여러 개의 connection을 하고 있음
    • 즉, DataSource를 사용한다는 것은 Connection을 DataSource에게 요청하는 것
  • DataSource에게서 받은 Connection의 close() method는 Connection을 끊는다는 것이 아니라, CP에 되돌려주는 것!
    • Connection은 받아서 빨리 돌려주어야 함
    • 그러기 위해서는 SQL이 빠르게 동작해야 함
    • Slow query를 해결하여 성능 향상을 해주어야 하는 이유
  • spring.datasource.initialization-mode= always 설정은 sample data 생성하기 때문에 test 용이하게 할 수 있음
  • spring.jpa.hibernate.ddl-auto= create-drop 설정은 table이 존재하지 않을 시, 자동으로 hibernate가 table 만들도록 하는 설정. 존재한다면 삭제 후 재생성

  • JPA에서의 '저장'은 영속적이다?
    • ex) 회원이 탈퇴할 때 까지 회원 정보는 유지되어야 함
    • entity 객체를 영속적 정보를 저장해주어야 함
  • 영속 상태가 되기 위해서는 반드시 식별자 값을 가지고 있어야 함
  • insert 뿐 아니라 조회할 때도 해당 데이터는 영속적이어짐
  • EntityManager가 영속성 관리
    • 때문에 EntityManager는 Bean으로 등록되어 Spring이 관리해주도록 해야 함
  • EntityManager는 transaction 단위로 작업을 수행
    • transaction: 하나의 논리적 작업 단위
    • commit이나 rollback이 하나의 transaction을 마치게 함
  • 이처럼 Transaction 단위로 동작하는 것을 Business method라 함
    • 그리고 이를 가지고 있는 객체가 Service 객체
    • 따라서 객체 설계 시에는 해당 객체의 Business method가 무엇이 있을지에 대한 고민이 필요
  • Service 객체는 CRUD를 위해 Repository(DAO) 참조
  • Service 객체의 method에 @Transactional 혹은 @Transactional(readOnly = true) annotation 있으면 proxy 생성되어, Transaction 알아서 관리
    • PlatformTransactionManager가 이 작업을 수행해줌
      • 이는 Connection에 의존하는 관계
      • Connection이 transaction begin, commit, rollback 등을 가지고 있기 때문
    • readOnly 인자는 조회(select)만 하는 method일 경우 사용
      • 조회할 때 매 번 commit을 수행하면 성능이 확 떨어지기 때문!
    • proxy는 try문 내에서 super.'method' 수행 후, commit
    • RuntimeException는 catch 해서 rollback 처리
  • 같은 transaction 안에서는 1차 캐시가 적용됨
    • 이전에 불러온 data를 또 요청하면 다시 가져오는 것이 아니라 이미 가지고 있는 것을 돌려줌
    • 즉, 같은 id에 대한 query는 한 번만 수행되는 것
  • 영속성이 부여되면 snapshot으로 복사본이 하나 만들어짐
    • Entity의 수정 기능은 setter로 행하게 됨
    • 기존에 만들어진 snapshot과 setter로 수정된 entity의 값을 비교해서 다르면 자동으로 update 문장 만들어 update 반영
  • Entity들 간의 관계에서는 방향이 중요함
    • ex) 부서(1) <----> 사원(N) 일 때 부서는 List<사원>, 사원은 부서라는 field를 가지고 있어야 함
    • 이럴 때 List<사원>에는 @OneToMany라는 annotation을, 부서에는 @ManyToOne annotation이 붙음
      • @OneToMany에는 (mappedBy='department')와 같은 것을 추가해주어 어떤 field로 연동되는 것인지 알려줌
      • Foreign Key를 위해 사원에는 @JoinColumn(부서_id)와 같은 annotation을 추가
    • ex) Member(N) <----> MemberRole(N)
    • 실제 다대다 관계는 일대다-다대일로 풀려짐
    • 다대다 관계에는 @JoinTable annotation이 붙어야 함
  • 부서.get사원들() 하면 SQL 실행 안됨 -> 단순히 Persist 속성만 넘어옴
    • 실제로 List를 사용할 때 select 문이 실행됨 -> Lazy Loading
  • Entity를 만들면 hibernate가 해당 entity에 대한 proxy를 생성
    • 실제로 사용되는 것은 우리가 만든 entity가 아닌 해당 proxy
  • 1 + N query 문제
    • 목록 가져오는 query 1번
    • Lazy Loading으로 정보 가져오는 N번 -> 너무 많은 query가 수행되어야 한다!
    • Repository에 method를 추가해서 해결
      • 부서와 사원을 fetch join: 관련된 부서와 사원을 한 꺼번에 가져오는 것!
  • 되도록 id는 개발자가 설정하는 것이 아니라 자동 생성(auto-increment)되도록 해주는 것이 성능 상 좋음

    • 수동으로 설정하면 id가 기존에 존재하는 값인지 검사하는 연산을 수행해야 하므로
  • category.getBoards() 와 같은 method를 사용하면 data가 수 십만 건이 될 수도 있음
    • 따라서 많이 사용되는 method는 아님
    • paging 처리해서 가져오는 것이 better!
      • 이 역시 repository를 통해 가져와야 함
    • 자료의 건수에 따라 전략을 잘 선택하자
  • FetchType.EAGER는 table 간 관계가 뗄 수 없는, 즉 반드시 함께 사용되야 할 때만 사용!
    • 기본은 LAZY로 쓰고 join하고 싶을 경우, repository에서 join 해서 사용
  • Repository에 Query method 생성 가능
    • method 이름을 보고 query 문이 자동으로 생성되게 됨
    • ex) BoardRepository에 findAllByName method 추가하면 이름과 관련한 SQL 검색 가능
  • JPA에 native query도 사용이 가능하나, 그럴 경우 해당 DBMS에 종속되는 것이기 때문에 추천하는 방법은 아님
    • JPA를 사용할 떄는 JPQL을 잘 다루는 것이 더 좋음
  • DBMS를 다루다 보면 Dynamic query도 존재할 수 있음
    • dynamic query: 조건에 따라 where절이 붙거나, 안 붙거나 하는 query
    • 이러한 Dynamic query는 Query method로 해결이 불가능
    • BoardRepositoryCustom과 같은 interface 생성해서 method 정의
    • BoardRepositoryImpl class를 생성해서 interface의 method 실 구현
      • proxy가 이 놈 사용(즉, BoardRepository와 BoardRepositoryImpl 사용)
    • 구현 방법
        1. EntityManager를 직접 주입하여, JPQL 조작 -> JPA 공부하면 상대적으로 수월
        • @Autowired
        • EntityManager
        • 문자열에서 오타 낼 가능성이 많음..
        1. JPQL을 다루기 위한 문법인 JPQL criteria 참조 -> 어려움
        1. QueryDSL 사용
        • Entity 정보들을 읽어들여 소스 코드 생성하는 라이브러리
        • ex) QBoard, QCategory...
        • 환경설정 어렵지만, 이후에 편리
          • maven 설정
    • 즉, BoardRepository는 JpaRepository와 BoardRepositoryCustom을 상속 받게 되는 것

etc

  • 커뮤니티가 많이 발달한 Java: 느리지만 지속적으로 발전하는 이유
    • 비슷한 기술이 등장하면 JSR에서 표준을 정해줌
    • 기술이 나오고 표준이 이후에 나오기 때문에, 동일한 기능을 하는 방식이 여럿 일 수 있음
  • 기술적 성숙도에 따라 아키텍쳐가 달라짐
    • JPA 같은 경우 난이도가 있기 때문에 주변에 같이 고민할 수 있는 사람이 있어야 함
    • 때문에 회사의 기술 stack을 보면 해당 회사의 기술적 수준을 알 수 있는 경우 많음


+ Recent posts