웹 애플리케이션 제작 ① - 프로젝트 환경설정
- 웹 애플리케이션 만들기 진행 순서
- 스프링 프레임워크와 JPA를 사용해서 웹 애플리케이션을 개발할 수 있도록 프로젝트 환경 설정
- 요구사항을 분석해서 도메인 모델과 테이블을 설계
- 실제 애플리케이션 기능 구현
- 프로젝트 환경설정 진행 순서
- 프로젝트 폴더 구조를 분석
- 메이븐에 사용할 라이브러리를 지정하도록 설정
- 스프링 프레임워크 환경 설정
- 또한 웹 서버를 실행하기 위해 메이븐의 톰캣 플러그인을 사용하도록 설정함
- 프로젝트 구조
- 메이븐이 제공하는 표준 프로젝트 구조를 사용하도록 함
- 메이븐과 사용 라이브러리 관리
- 메이븐 설정 파일인 pom.xml을 열어서 현재 프로젝트 정보와 사용할 라이브러리를 지정하도록 함
- 라이브러리의 경우 <dependencies> 부분의 <dependency>에 groupId + artifactId + version만 적어주면
라이브러리를 메이븐 공식 저장소에서 자동으로 내려받아 라이브러리에 추가해주게 됨 - 필요한 핵심 라이브러리에는 스프링 MVC, 스프링 ORM, JPA, 하이버네이트가 존재
- 기타 라이브러리에는 H2 데이터베이스, 커넥션 풀, WEB, 로딩 SLF4J & LogBack, 테스트가 존재
- 스프링 프레임워크 설정
- 웹 애플리케이션을 실행하기 위한 환경 설정으로는 web.xml, webAppConfig.xml, appConfig.xml이 존재
- web.xml은 웹 애플리케이션 환경설정 파일로 웹 애플리케이션에서 스프링 프레임워크를 구동하기 위한 설정을 하며
웹 계층을 담당하는 WebAppConfig.xml을 설정하는 부분과
비즈니스 도메인 계층을 담당하는 appConfig.xml을 설정하는 부분으로 나누어짐 - webAppConfig.xml은 JSP와 JSTL을 사용하도록 뷰 리졸버를 스프링 빈에 등록하고
어노테이션이 붙어있는 클래스들을 스프링 빈으로 등록하도록 스프링 MVC 기능을 활성화하도록 설정함 - appConfig.xml은 스프링 프레임워크가 제공하는 어노테이션 기반의 트랜잭션 관리자를 활성화하고 등록함
또한 데이터베이스에 접근할 데이터 소스를 설정한 후 JPA 설정을 통해 엔티티 매니저 팩토리를 등록하고
사용할 데이터소스를 등록하도록 한 후 jpaProperties를 사용해서 하이버네이트 구현체의 속성을 설정할 수 있음
JPA 예외 변환 AOP 설정을 하면 JPA 예외를 스프링 프레임워크가 추상화한 예외로 변환해줄 수도 있음
웹 애플리케이션 제작 ② - 도메인 모델과 테이블 설계
- 요구사항 분석
- 회원 기능에는 회원 등록, 회원 조회
- 상품 기능에는 상품 등록, 상품 수정, 상품 조회
- 주문 기능에는 상품 주문, 주문 내역 조회, 주문 취소
- 상품의 종류는 도서, 음반, 영화가 있으며 상품은 카테고리로 구분할 수 있고 상품 주문 시 배송 정보를 입력할 수 있음
- 도메인 모델 설계
- 엔티티에는 Member, Order, Item, Delivery, Category가 있으며
한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품은 다대다 관계이지만
다대다 관계는 관계형 데이터베이스와 엔티티에서 거의 사용하지 않으므로
주문상품이라는 새로운 엔티티를 추가해서 일대다, 다대일 관계로 풀어내었음 - 회원(Member)는 name, orders, 그리고 임베디드 타입인 address를 가짐
- 주문(Order)은 member, orderItems, delivery, orderDate, status를 가지며 status는 열거형으로 주문, 취소를 표현함
- 주문상품(OrderItem)은 item, order, orderPrice, count를 가짐
- 상품(Item) name, price, stockQuantity, categories를 가지며 상품을 주문하면 재고수량이 줄어들게 되며
상품의 종류로는 Album, Book, Movie가 있고 각각 사용하는 속성이 조금씩 다르므로 상속 구조로 표현함 - 배송(Delivery)은 order, address, status를 가짐
- 카테고리(Category)는 name, items, parent, child를 가짐
- 주소(Address)는 값 타입 중 임베디드 타입이며 city, street, zipcode를 가지고 회원과 배송에서 사용됨
- 엔티티에는 Member, Order, Item, Delivery, Category가 있으며
- 테이블 설계
- 회원 엔티티와 배송 엔티티의 Address 임베디드 타입 정보가 각 테이블에 그대로 들어가게 됨
- 상품은 앨범, 도서, 영화 타입을 통합해서 하나의 테이블로 만들고 DTYPE 컬럼으로 타입을 구분함
- 연관관계 정리
- 회원과 주문은 일대다 양방향 관계이며 외래 키가 있는 주문이 연관관계의 주인이므로
Order.member를 ORDERS.MEMBER_ID 외래 키와 매핑 - 주문상품과 주문을 다대일 양방향 관계이며 외래 키가 있는 주문상품이 연관관계의 주인이므로
OrderItem.order를 ORDER_ITEM.ORDER_ID 외래 키와 매핑 - 주문상품과 상품은 다대일 단방향 관계이므로 OrderItem.item을 ORDER_ITEM.ITEM_ID 외래 키와 매핑
- 주문과 배송은 일대일 양방향 관계이므로 Order.delivery를 ORDERS.DELIVERY_ID 외래 키와 매핑
- 카테고리와 상품은 다대다 양방향 관계이므로 @ManyToMany와 @JoinTable을 사용해서 매핑하도록 함
- 회원과 주문은 일대다 양방향 관계이며 외래 키가 있는 주문이 연관관계의 주인이므로
- 엔티티 클래스
- 위를 참고하여 엔티티 클래스 코드를 작성
웹 애플리케이션 제작 ③ - 애플리케이션 구현
- 메인화면과 구현 기능
- 회원 기능에는 회원 등록, 회원 조회 기능을 구현
- 상품 기능에는 상품 등록, 상품 수정, 상품 조회 기능을 구현
- 주문 기능에는 상품 주문, 주문 내역 조회, 주문 취소 기능을 구현
- 개발 방법
- Controller는 MVC의 컨트롤러가 모여있는 곳으로 서비스 계층을 호출하고 결과를 뷰에 전달함
- Service는 비즈니스 로직이 있고 트랜잭션을 시작하며, 데이터 접근 계층인 리포지토리를 호출함
- Repository는 JPA를 직접 사용하며 엔티티 매니저를 사용해서 데이터베이스에서 엔티티를 저장하고 조회함
- Domain은 엔티티가 모여 있는 계층으로 모든 계층에서 사용하게 됨
- 개발 순서는 계층형 구조를 사용하며
비즈니스 도메인 계층에 해당하여 비즈니스 로직을 수행하는 서비스와 리포지토리 계층을 먼저 개발하며
테스트 케이스를 작성해서 검증한 후에는 웹 계층에 해당하는 컨트롤러와 뷰를 개발하는 순서로 진행함
- 회원 기능
- 회원 기능에는 회원 등록, 회원 조회 기능을 구현
- 회원 기능을 위해서는
1. 도메인 모델에서 설명한 회원 엔티티 코드
2. 회원 엔티티를 저장하고 관리할 회원 리포지토리 코드
3. 회원과 관련된 비즈니스 로직이 있는 회원 서비스 코드 순으로 개발 - 이후 개발한 회원 비즈니스 로직이 정상 동작하는지 JUnint으로 회원 기능을 테스트함
- 상품 기능
- 상품 기능에는 상품 등록, 상품 수정, 상품 조회 기능을 구현
- 상품 기능을 위해서는
1. 도메인 모델에서 설명한 상품 엔티티 코드
2. 상품 엔티티를 저장하고 관리할 상품 리포지토리 코드
3. 상품 리포지토리에 위임만 하는 단순한 상품 서비스 코드 순으로 개발 - 이후 개발한 상품 비즈니스 로직이 정상 동작하는지 JUnint으로 상품 기능을 테스트함
- 주문 기능
- 주문 기능에는 상품 주문, 주문 내역 조회, 주문 취소 기능을 구현
- 주문 기능을 위해서는
1. 도메인 모델에서 설명한 주문 엔티티 코드
2. 도메인 모델에서 설명한 주문상품 엔티티 코드
3. 주문 엔티티를 저장, 검색하고 관리할 주문 리포지토리 코드
4. 주문과 관련된 비즈니스 로직이 있는 주문 서비스 코드
5. 주문 검색 기능 순으로 개발 - 이후 개발한 주문 비즈니스 로직이 정상 동작하는 JUnit으로 주문 기능을 테스트함
- 상품 관련 웹 계층 구현
- 상품 등록을 선택하면
/items/new URL을 HTTP GET 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를 실행하게 되며
스프링 MVC의 뷰 리졸버가 렌더링할 뷰를 찾아 해당 위치의 JSP를 실행한 상품 등록 폼 HTML을 클라이언트에 응답
이후 상품 등록 폼에서 데이터를 입력하고 전송 버튼을 누르면
/items/new URL을 HTTP POST 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를 실행하게 되며
파라미터로 전달된 값에 대해 상품 저장을 요청하고 저장이 끝나면 상품 목록 화면으로 리다이렉트됨 - 상품 목록을 선택하면
/items URL을 HTTP GET 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를 실행하게 되며
스프링 MVC의 뷰 리졸버가 렌더링할 뷰를 찾아 모델에 담아둔 조회한 상품 목록에서 하나씩 꺼내 상품 정보를 출력 - 상품 수정을 선택하면
/items/{itemId}/edit URL을 HTTP GET 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를
실행하게 되며 스프링 MVC의 뷰 리졸버가 렌더링할 뷰를 찾고 모델에 담아둔 수정할 상품 조회 정보 모델 객체를 담아
해당 위치의 JSP를 실행할 상품 수정 폼 HTML을 클라이언트에 응답
이후 상품 수정 폼에서 데이터를 입력하고 전송 버튼을 누르면
/items/{itemId}/edit URL을 HTTP POST 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를
실행하게 되며 파라미터로 전달된 준영속 상태의 엔티티 인스턴스에 대해 식별자가 있으므로
병합을 수행하도록 요청하고 수정이 끝나면 상품 목록 화면으로 리다이렉트됨 - 상품 주문을 선택하면
/order URL을 HTTP GET 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를 실행하게 되며
스프링 MVC의 뷰 리졸버가 렌더링할 뷰를 찾고 고객정보와 상품 정보가 담긴 모델 객체를 담아
해당 위치의 JSP를 실행한 상품 주문 폼 HTML을 클라이언트에 응답
이후 상품 주문 폼에서 데이터를 입력하고 전송 버튼을 누르면
/order URL을 HTTP POST 방식으로 요청하게 되고 스프링 MVC는 요청 정보와 매핑되는 메소드를 실행하게 되며
파라미터로 전달된 값에 대해 주문 저장을 요청하고 저장이 끝나면 상품 주문 내역 화면으로 리다이렉트됨
- 상품 등록을 선택하면
스프링 데이터 JPA
- 데이터 접근 계층 개발의 문제
- JPA를 사용할 경우에도 데이터 접근 계층인 DAO는 등록, 조회, 수정, 삭제 코드인 CRUD를 반복해서 개발해야 함
- 이러한 반복 문제를 해결하기 위해 공통 부분 부모 클래스로 만들어 상속으로 처리하게 될 경우
부모 클래스에 너무 종속되고 구현 클래스 상속이 가지는 단점이 노출됨
- 스프링 데이터 JPA 소개
- 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트로
공통 인터페이스인 JpaRepository 인터페이스를 제공해 지루하게 반복되는 CRUD 문제를 해결할 수 있음 - 일반적인 CRUD 메소드 이외에는 스프링 데이터 JPA가 메소드 이름을 분석해서 JPQL을 실행함
- 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트로
- 스프링 데이터 JPA 설정
- 스프링 데이터 JPA는 spring-data-jpa 라이브러리가 필요하며
스프링 설정에 XML을 사용한다면 <jpa:repository>를 사용함 - 스프링 설정에 JavaConfig를 사용하면
EnableJpaRepositories 어노테이션을 추가하고 basePackage에 리포지토리를 검색할 패키지 위치를 적음
그러면 애플리케이션을 실행할 때 basePackage에 있는 리포지토리 인터페이스를 찾아서
해당 인터페이스를 구현한 클래스를 동적으로 생성하여 스프링 빈에 등록하게 됨
- 스프링 데이터 JPA는 spring-data-jpa 라이브러리가 필요하며
- 공통 인터페이스 기능
- 스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공하며
스프링 데이터 JPA를 가장 단순히 사용하기 위해서는 이 인터페이스를 상속받고
제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정함 - JpaRepository 인터페이스를 상속받으면 save, delete, findOne, getOne, findAll 등의 메소드 등으로 CRUD 해결 가능
- 스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공하며
- 쿼리 메소드 기능
- 스프링 데이터 JPA에는 메소드 이름만으로 쿼리를 생성하는 기능이 있어
인터페이스에 메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행함 - 이 외에도 메소드 이름으로 JPA NamedQuery 호출하거나,
@Query 어노테이션을 사용해 리포지토리 인터페이스에 직접 쿼리를 정의할 수 있음 - 스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원함
- 벌크성 수정 워키를 작성하고 싶다면 @Modifying 어노테이션을 사용
- 스프링 데이터 JPA는 반환 타입으로 결과가 한 건 이상이면 컬렉션 인터페이스를 사용하고, 단 건이면 반환 타입을 지정함
- 스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 Pageable와 Sort 파라미터를 제공
- JPA 쿼리 힌트를 사용하려면 @QueryHints 어노테이션을 사용해 JPA 구현체에게 힌트를 제공
- 쿼리 시 락을 걸려면 @Lock 어노테이션을 사용하면 됨
- 스프링 데이터 JPA에는 메소드 이름만으로 쿼리를 생성하는 기능이 있어
- 명세
- 스프링 데이터 JPA는 명세를 사용할 수 있도록 Criteria를 이용해 지원함
- 이를 위해서 Specification 클래스를 사용해 where(), and() 등의 메소드로 데이터를 검색하기 위한 계약 조건을 정의하고
JpaSpecificationExecutor 인터페이스를 상속받아 Specification을 파라미터로 받아서 검색 조건으로 사용함
- 사용자 정의 리포지토리 구현
- 스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않지만
다양한 이유로 메소드를 직접 구현해야 할 때 리포지토리를 직접 구현하면
공통 인터페이스가 제공하는 기능까지 모두 구현해야하므로 이런 문제를 우회해서 필요한 메소드만 구현할 수 있도록 함 - 이를 위해 직접 구현할 메소드를 위한 사용자 정의 인터페이스를 작성한 후,
사용자 정의 인터페이스의 이름에 Impl을 붙여 사용자 정의 인터페이스를 구현한 클래스를 작성하면
스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식하게 되고
이후 리포지토리 인터페이스에서 사용자 정의 인터페이스를 상속받아 사용하면 됨
- 스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않지만
- Web 확장
- 스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편리한 기능인
도메인 컨버터와 페이징 정렬을 위한 HandlerMethodArgumentResolver를 제공함 - 스프링 데이터가 제공하는 Web 확장 기능을 활성화하기 위해서는 SpringDataWebConfiguration을 스프링 빈으로 등록
- 도메인 클래스 컨버터 기능을 사용하면 HTTP 요청으로 넘어온 엔티티 아이디를 받은 후
도메인 클래스 컨버터가 중간에서 해당 엔티티와 관련된 리포지토리를 사용해 엔티티를 찾도록 동작해
아이디를 엔티티 객체로 반환해서 넘겨주기 때문에 컨트롤러가 직접 엔티티를 조회하는 것을 없애 단순하게 사용할 수 있음 - HandlerMethodArgumentResolver를 통해서는
페이징 기능 PageHandlerMethodArgumentResolver와 정렬 기능 SortHandlerMethodArgumentResolver를 제공
- 스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편리한 기능인
- 스프링 데이터 JPA가 사용하는 구현체
- 스프링 데이터 JPA가 제공하는 공통 인터페이스는 SimpleJpaRepository 클래스가 구현함
- JPA 샵에 적용
- 스프링 프레임워크와 JPA로 개발한 웹 애플리케이션에 스프링 데이터 JPA를 적용하기 위해서는
스프링 데이터 JPA 설정을 한 후 기존 리포지토리들이 스프링 데이터 JPA를 사용하도록 리팩토링함
- 스프링 프레임워크와 JPA로 개발한 웹 애플리케이션에 스프링 데이터 JPA를 적용하기 위해서는
- 스프링 데이터 JPA와 QueryDSL 통합
- 스프링 데이터 JPA는 두 가지 방법으로 QueryDSL을 지원
- 리포지토리에서 QueryDslPredicateExecutor를 상속받아 QueryDSL을 사용할 수 있음
- QueryDslPredicateExecutor는 스프링 데이터 JPA에서 편리하게 QueryDSL을 사용할 수 있지만 기능에 한계가 존재함
그러므로 QueryDSL에서 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용하거나
스프링 데이터 JPA가 제공하는 QueryDslRepositorySupoort을 사용함
QueryDslRepositorySupoort를 사용할 때는 스프링 데이터 JPA가 제공하는 공통 인터페이스는 직접 구현할 수 없으므로
사용자 정의 리포지토리를 만든 후 상속받아 사용함
웹 애플리케이션과 영속성 관리
- 트랜잭션 범위의 영속성 컨텍스트
- 순수한 JS2E 환경에서 JPA를 사용할 경우에는 개발자가 직접 엔티티 매니저를 생성하고 트랜잭션도 관리해야 함
하지만 스프링이나 J2EE 컨테이너 환경에서 JPA를 사용하면 컨테이너가 제공하는 전략을 따라야 함 - 스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 하므로
트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 종료함
그러므로 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근하게 됨 - 또한 트랜잭션이 같으면 다양한 위치에서 엔티티 매니저를 주입받아 사용해도 항상 같은 영속성 컨텍스트를 사용하는 반면,
같은 엔티티 매니저를 사용해도 여러 스레드에서 동시에 요청이 오면 트랜잭션에 따라 접근하는 영속성 컨텍스트가 달라짐
이 경우 스레드마다 각각 다른 트랜잭션을 할당하므로 같은 엔티티 매니저를 호출해도 멀티스레드 상황에 안전할 수 있음
- 순수한 JS2E 환경에서 JPA를 사용할 경우에는 개발자가 직접 엔티티 매니저를 생성하고 트랜잭션도 관리해야 함
- 준영속 상태와 지연 로딩
- 트랜잭션은 보통 서비스 계층에서 시작해 서비스 계층이 끝나는 시점에 트랜잭션이 영속성 컨텍스트와 함께 종료됨
- 따라서 조회한 엔티티가 서비스와 리포지토리 계층에서는 영속성 컨텍스트에 관리되면서 영속 상태를 유지하지만
컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태가 됨 - 그러므로 비즈니스 로직에서만 변경 감지에 의한 데이터 변경이 이루어지고
프리젠테이션 계층에서는 단순히 데이터를 보여주는데 집중할 수 있음 - 하지만 지연 로딩을 설정해서 프록시 객체로 조회한 후 준영속 상태가 되었다면
뷰를 렌더링할 때 연관된 엔티티를 불러올 수 없으므로 문제가 발생함
이를 해결하기 위해서는 글로벌 페치 전략을 즉시 로딩으로 수정, JPQL 페치 조인, FACADE 계층을 통한 강제 초기화로
영속성 컨텍스트가 살아있을 때 뷰가 필요한 엔티티를 미리 로딩해두거나
OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하면 됨
- 트랜잭션은 보통 서비스 계층에서 시작해 서비스 계층이 끝나는 시점에 트랜잭션이 영속성 컨텍스트와 함께 종료됨
- OSIV
- OSIV는 Open Session In View를 뜻하며 영속성 컨텍스트를 뷰까지 살아있게 열어두어 뷰에서도 지연 로딩이 가능함
- 과거의 OSIV는 요청 당 트랜잭션을 하여 요청이 들어오면 영속성 컨텍스트를 만들고 요청이 끝날 대 함께 끝내도록 함
하지만 이 경우에는 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있게 되는 심각한 문제가 발생함
이를 해결하기 위해 엔티티를 읽기 전용 인터페이스로 제공하거나 엔티티 레핑, DTO만 반환하는 방법을 사용할 경우
모두 코드량이 상당히 증가하게 되므로 최근에는 거의 사용하지 않음 - 스프링은 OSIV를 제공하며 비즈니스 계층에서 트랜잭션을 사용하는 OSIV이므로 트랜잭션은 비즈니스 계층에서만 사용됨
하지만 앞과 달리 서비스 계층이 끝나면 트랜잭션은 끝내지만 영속성 컨텍스트는 종료되지 않으므로
컨트롤러와 뷰까지 영속성 컨텍스트가 유지되어 조회한 엔티티는 영속 상태를 유지할 수 있음
이 상태에서 변경 감지를 통한 요청이 들어오면 플러시를 호출하지 않고 영속성 컨텍스트를 바로 종료하게 됨 - 그러므로 스프링 OSIV를 사용하면 프리젠테이션 계층에서는 트랜잭션이 없으므로 엔티티를 수정할 수 없지만
트랜잭션이 없어도 조회가 가능한 트랜잭션 없이 읽기를 사용해서 프리젠테이션 계층에서 지연 로딩을 사용할 수 있게 됨 - 하지만 스프링 OSIV는 프리젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출할 경우
수정 사항에 데이터베이스에 반영될 수 있는 문제가 발생할 수 있으므로
트랜잭션이 있는 비즈니스 로직을 모두 호출하고 나서 엔티티를 변경하도록 해야 함
- 너무 엄격한 계층
- 스프링 OSIV를 사용하면 영속성 컨텍스트가 프리젠테이션 계층까지 살아있으므로
지연 로딩된 엔티티를 미리 초기화할 필요가 없으며
단순히 엔티티 조회를 위해 컨트롤러에서 리포지토리를 직접 호출해도 아무런 문제가 발생하지 않음
그러므로 엔티티가 계층을 뛰어넘는 것이 어려웠던 이전과 달리 유연하고 실용적 관점으로 접근이 가능해짐
- 스프링 OSIV를 사용하면 영속성 컨텍스트가 프리젠테이션 계층까지 살아있으므로
+) 자세한 자료 설명
'Community > SOLUX' 카테고리의 다른 글
[221228] 2022 2학기 프로젝트 - 6차 회의 (0) | 2023.01.03 |
---|---|
[221222-221228] 2022 2학기 프로젝트 - 5차 스터디 (0) | 2022.12.27 |
[221208-221214] 2022 2학기 프로젝트 - 3차 스터디 (0) | 2022.12.14 |
[221201-221207] 2022 2학기 프로젝트 - 2차 스터디 (0) | 2022.12.05 |
[221124-221130] 2022 2학기 프로젝트 - 1차 스터디 (0) | 2022.11.29 |