Spring boot camp #3
기본적인 웹 Template
- application과 사용자의 접점인 boundary
- 조회 화면, 글쓰기 화면 등이 boundary의 예
- 사용자가 요청한 업무를 처리하는 control
- 게시물과 같은 entity
- 외부 시스템(ex. 외부 결제 시스템)과의 접점도 필요함
- 이 역시 boundary로 구현하여 control과 연동시킴
- Transaction은 Spring JDBC, Spring Data JPA 그리고 외부 웹에서 자료를 받아오기 위한 Rest template 등에 의해 수행
- DBMS 관리와 SQL에 대한 지식을 DBA 만큼은 아니더라도 갖추어야 함
- DBMS 관리와 SQL에 대한 지식을 DBA 만큼은 아니더라도 갖추어야 함
Java와 Database programming
- 입력, 수정, 삭제
-
- DB connection: connection Interface, DriverManager class 사용
-
- SQL 준비(ex. insert into table명 values(...))
- DB가 sql을 이해하기 위해 문장을 parsing 하는데, 이는 큰 overhead를 발생시킴
- parsing 결과를 DB 자체에 저장해두었다가, 완전히 동일한 sql문이 오면 이전에 저장해둔 값 사용
- insert의 경우, values가 계속 변화할 수 있기 때문에 '?'로 채워뒀다가 ? 값만 바꾸어 사용 -> 성능 개선
-
- 바인딩: ?에 값을 채워주는 것
- PreparedStatement interface 사용
-
- PreparedStatement의 executeUpdate method 호출
-
- DB connection close
-
- 데이터 조회
-
- DB connection
-
- SQL 준비
-
- 바인딩
-
- executeQuery() method 실행
-
- ResultSet Interface의 next() method 이용하여 row 한 건 씩 읽어옴
- 즉, SQL의 select 문에는 ResultSet interface가 사용됨
-
- 읽어온 row를 column별로 잘라서 사용
- ResultSet의 getXXX method 사용
- ex) getName(), getTitle()...
-
- 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 중심으로 database 코드를 짜는 SQL mapper
- 그러나 SQL은 객체지향적이지 않음
- ex) 게시물과 댓글의 1:n 관계
- 게시물의 getComments()와 같은 method로 댓글을 읽어오고 싶음
- 두 Entity 간의 관계를 객체지향적으로 표현하기가 어려움
- 패러다임의 충돌: SQL적 사고와 객체지향적 사고에는 근본적인 차이가 존재
- ex) 게시물과 댓글의 1:n 관계
- 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도 존재
- Data 저장소는 DBMS만 존재하는 것이 아님
- 결과적으로 JDBC -> Hibernate & JPA -> Spring Data의 발전 구조
- 수업에서는 이를 다 다룰 수는 없기 때문에 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로 클라이언트 접속이 가능
-
DataSource) (Interface: Connection Pool)
- 아래와 같은 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 처리
- PlatformTransactionManager가 이 작업을 수행해줌
- 같은 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 사용)
- 구현 방법
-
- EntityManager를 직접 주입하여, JPQL 조작 -> JPA 공부하면 상대적으로 수월
- @Autowired
- EntityManager
- 문자열에서 오타 낼 가능성이 많음..
-
- JPQL을 다루기 위한 문법인 JPQL criteria 참조 -> 어려움
-
- QueryDSL 사용
- Entity 정보들을 읽어들여 소스 코드 생성하는 라이브러리
- ex) QBoard, QCategory...
- 환경설정 어렵지만, 이후에 편리
- maven 설정
-
- 즉, BoardRepository는 JpaRepository와 BoardRepositoryCustom을 상속 받게 되는 것
etc
- 커뮤니티가 많이 발달한 Java: 느리지만 지속적으로 발전하는 이유
- 비슷한 기술이 등장하면 JSR에서 표준을 정해줌
- 기술이 나오고 표준이 이후에 나오기 때문에, 동일한 기능을 하는 방식이 여럿 일 수 있음
- 기술적 성숙도에 따라 아키텍쳐가 달라짐
- JPA 같은 경우 난이도가 있기 때문에 주변에 같이 고민할 수 있는 사람이 있어야 함
- 때문에 회사의 기술 stack을 보면 해당 회사의 기술적 수준을 알 수 있는 경우 많음
'Software Convergence > Spring, Spring boot' 카테고리의 다른 글
백선장의 스프링 부트 (3) (0) | 2018.11.06 |
---|---|
백선장의 스프링 부트 (2) (0) | 2018.11.05 |
백선장의 스프링 부트 (1) (0) | 2018.11.05 |
[패스트캠퍼스] Spring boot camp #2 (0) | 2018.10.13 |