diff --git a/README.md b/README.md
index e2e9a4f..323ca80 100644
--- a/README.md
+++ b/README.md
@@ -579,4 +579,331 @@ public class OptimisticLockStockFacade {
3-2. Redisson
- pub-sub 기반으로 Lock 구현 제공
-Thread 1 -- 락 해제 메세지 --> Channel -- 락 획득 시도 요청 메시지 --> Thread 2
\ No newline at end of file
+Thread 1 -- 락 해제 메세지 --> Channel -- 락 획득 시도 요청 메시지 --> Thread 2
+
+
+
+
+## 7주차
+
+### 트랜잭션 전파 속성
+
+#### *- 스프링에서 트랜잭션을 관리하는 방법*
+
+Spring의 @Transactional 어노테이션은 여러 트랜잭션을 묶어 하나의 트랜젝션 경계를 만들 수 있게 해준다.
+이때 트랜잭션을 어떻게 처리할 지에 대해 스프링이 제공하는 속성이 전파 속성 (Propagation)이다.
+
+
+스프링에서는 트랜잭션을 '물리 트랜잭션'과 '논리 트랜잭션'으로 나누어 처리한다.
+
+- 물리 트랜잭션
+ 실제 데이터베이스에 적용되는 트랜잭션으로 커넥션을 통해 커밋/롤백하는 단위
+- 논리 트랜잭션
+ 스프링이 처리하는 트랜잭션 영역을 구분하기 위해 만들어진 개념
+
+[트랜잭션 원칙]
+1. 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다
+2. 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다
+
+
+#### *-트랜잭션 전파 속성*
+
+
+1. REQUIRED
+ -> Default 속성. 미리 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션을 생성한다.
+ 부모 메서드와 자식 메서드가 하나의 트랜잭션으로 묶여 동작한다. 하나의 트랜잭션으로 묶이기 때문에 부모나
+ 자식에서 예외가 발생하면 전체 트랜잭션이 롤백된다.
+
+2. SUPPORTS
+ -> 이미 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션 없이 진행한다. 트랜잭션이 없긴 하지만 해당 경계 안에서
+ Connection이나 Hibernate Session 등을 공유할 수 있다.
+
+3. MANDATORY
+ -> REQURIED와 비슷하며, 이미 시작된 트랜잭션이 있으면 참여한다. 하지만 트랜잭션이 없다면 생성하는 것이 아니라
+ 예외를 발생시킨다. 혼자서 독립적으로 트랜잭션을 실행하면 안되는 경우에 사용한다.
+
+4. REQUIRES_NEW
+ -> 항상 새로운 트랜잭션을 시작한다. 이미 진행 중인 트랜잭션이 있다면, 트랜잭션을 보류시킨다.
+ 부모 트랜잭션과 자식 메서드의 트랜잭션이 완전히 분리되어 동작한다. 자식 메서드의 트랜잭션이 완료되면
+ 부모 트랜잭션이 다시 활성화된다. 부모와 자식 트랜잭션이 서로 독립적이므로 하나가 실패하더라도 다른 하나는
+ 영향을 받지 않는다.
+
+5. NOT_SUPPORTED
+ -> 트랜잭션을 사용하지 않게 한다. 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
+
+6. NEVER
+ -> 트랜잭션을 사용하지 않도록 강제한다. 이미 진행 중인 트랜잭션도 존재하면 안되며, 트랜잭션이 있다면 예외를 발생시킨다.
+
+7. NESTED
+ -> 이미 진행중인 트랜잭션이 있으면 중첩 트랜잭션을 시작한다. 중첩된 트랜재션은 먼저 시작된 부모 트랜잭션의
+ 커밋과 롤백에는 영향을 받지만, 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않는다.
+
+*7-1. 부모 트랜잭션의 롤백이 NESTED 속성을 가진 자식에게 전파될 때*
+``` java
+// AService
+@Transactional
+public void saveWithNestedParentException(Member aMember, Member bMember) {
+ memberRepository.save(aMember);
+ bService.saveWithNestedParentException(bMember);
+ throw new RuntimeException();
+}
+
+
+// BService
+@Transactional(propagation = Propagation.NESTED)
+public void saveWithNestedParentException(Member bMember) {
+ memberRepository.save(bMember);
+}
+
+
+// Test Code
+@Test
+@DisplayName("[NESTED] 부모 트랜잭션의 롤백이 자식에게 전파")
+public void saveWithNestedParentExceptionTest() {
+ Member aMember = new Member(1L);
+ Member bMember = new Member(2L);
+
+ assertThatThrownBy(() -> aService.saveWithNestedParentException(aMember, bMember))
+ .isInstanceOf(RuntimeException.class);
+
+ assertThat(memberRepository.findAll()).size().isEqualTo(0);
+}
+
+```
+부모 트랜잭션에서 예외가 터지면 자식 트랜잭션도 함께 롤백된다.
+
+*7-2. NESTED 속성을 가진 자식 트랜잭션이 롤백될 때*
+
+``` java
+// AService
+@Transactional
+public void saveWithNestedChildException(Member aMember, Member bMember) {
+ memberRepository.save(aMember);
+ try {
+ bService.saveWithNestedChildException(bMember); // 중첩 트랜잭션
+ } catch (RuntimeException e) {
+ System.out.println("중첩 트랜잭션 롤백 처리: " + e.getMessage());
+ }
+}
+
+
+// BService
+@Transactional(propagation = Propagation.NESTED)
+public void saveWithNestedChildException(Member bMember) {
+ memberRepository.save(bMember);
+ throw new RuntimeException();
+}
+
+
+// Test Code
+@Test
+@DisplayName("[NESTED] 자식 트랜잭션의 롤백이 부모에게 전파되지 않음")
+public void saveWithNestedChildExceptionTest() {
+ Member aMember = new Member(1L);
+ Member bMember = new Member(2L);
+
+ aService.saveWithNestedChildException(aMember, bMember);
+
+ assertThat(memberRepository.findAll()).size().isEqualTo(1);
+}
+```
+자식의 트랜잭션과는 무관하게 부모 트랜잭션에서 저장한 aMemer가 커밋된다.
+
+### 인덱스 종류
+
+인덱스 타입은 크게 **Primary(클러스터) 인덱스**와 **Secondary(보조) 인덱스**로 나뉘어 진다.
+클러스터 인덱스는 '영어 사전'처럼 처음부터 정렬이 되어 있는 것이고, 보조 인덱스는 '책 뒤의 찾아보기'와 같은 것이다.
+
+| 구분 | 클러스터 인덱스 (Clustered Index) | 보조 인덱스 (Secondary / Non-Clustered Index) |
+|------|------------------------------------|-----------------------------------------------|
+| **속도** | 빠르다 | 느리다 |
+| **사용 메모리** | 적다 | 많다 |
+| **인덱스 구조** | 인덱스가 주요 데이터 | 인덱스가 데이터 사본(Copy) |
+| **개수 제한** | 테이블당 1개 | 여러 개 가능(최대 ~250개) |
+| **리프 노드** | 리프 노드 자체가 데이터 | 리프 노드는 데이터 위치(포인터) 저장 |
+| **저장값** | 데이터 자체가 저장됨 | 값 + 데이터의 위치 포인터 |
+| **정렬 방식** | 인덱스 순서 = 물리적 저장 순서 | 인덱스 순서 ≠ 물리적 저장 순서 |
+
+
+#### 1. 클러스터 인덱스
+- 한 개의 테이블에 한 개씩만 만들 수 있다.
+- 특정 나열된 데이터들을 일정 기준으로 정렬해주는 인덱스이며, 해당 인덱스가 생성될 때 데이터 페이지 전체가
+다시 정렬된다.
+- MySQL에서는 Primary Key가 있다면 Primary Key를 클러스터 인덱스로 지정한다.
+ - Primary Key를 지정하지 않은 경우 -> UNIQUE하면서 NOT NULL인 컬럼을 클러스터 인덱스로 지정한다.
+ - UNIQUE로 지정한 컬럼도 없는 경우 -> 자동으로 유니크한 값을 가지도록 증가되는 컬럼(GET_CLUST_INDEX)를
+ 내부적으로 생성 후 클러스터 인덱스로 지정한다.
+
+#### 2. 세컨더리 인덱스
+- 세컨더리 인덱스는 데이터 테이블과 별개로 정렬된 인덱스 페이지를 생성하고 관리한다. 비클러스터형 인덱스나
+ 보조 인덱스라고 불린다.
+
+
+세컨더리 인덱스는 데이터에 접근하려면 **인덱스 페이지에서 데이터 페이지로 이동하여 실제 데이터 레코드를 가져오는
+과정이 추가** 된다.
+
+#### 3. 커버링 인덱스
+- 원하는 데이터를 인덱스에서만 추출할 수 있는 인덱스
+- 쿼리를 만드는 SELECT / WHERE / GROUP BY / ORDER BY 등에 활용되는 모든 컬럼이 인덱스여야 한다.
+
+
+참고: https://jay-ya.tistory.com/166
+ex) 상품 테이블의 데이터 레코드 수가 대략 1700만 건이 존재하며, 이 테이블을 대상으로 특정 조건에 맞게
+페이지네이션 하는 경우
+
+
+product_id(Primary Key)와 delivery 컬럼에 인덱스가 걸려 있는 상태
+
+``` mysql
+select *
+from product
+where delivery like '초고속 배송%'
+limit 5000000, 10
+```
+실행결과: 10 rows in set (1 in 18.28 sec)
+
+커버링 인덱스 적용 시 >
+
+``` mysql
+select product_id, delivery
+from product
+where delivery like '초고속%'
+limit 5000000, 10;
+```
+실행결과: 10 rows in set (3.60 sec)
+
+커버링 인덱스를 적용할 경우, 실 데이터에 액세스하는 시간이 없어지기 때문에 조회 쿼리에
+낭비되는 시간 없이 데이터를 완성할 수 있다.
+
+
+### 성능 최적화
+
+- 인덱스 대상 컬럼 선정
+ 카디널리티가 높은 컬럼, 즉 중복도가 나증 컬럼을 우선적으로 인덱싱하는 것이 좋다.
+ ex-1) 남 - 여 2가지 값만 존재하는 성별 컬럼의 경우, 중복도가 높으므로 카디널리티가 낮다.
+ ex-2) 주민번호 컬럼은 중복도가 낮기 때문에 카디널리티가 높다.
+
+
+1. 회원 가입 시 중복 아이디를 확인하는 경우
+
+``` java
+@Entity
+@Table(
+name = "member",
+uniqueConstraints = @UniqueConstraint(name = "uk_member_login_id", columnNames = "login_id")
+)
+@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class MemberEntity {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "member_id")
+ private Long id;
+
+ // ...
+
+ private String loginId;
+
+ // ...
+}
+```
+
+@UniqueConstraint로 Table에서 unique를 걸어주거나, @Column에서 unqiue=true를 걸어주면
+DB에 unique index가 생긴다.
+
+``` sql
+create table member
+(
+age int not null,
+member_id bigint auto_increment
+primary key,
+login_id varchar(30) not null,
+name varchar(50) not null,
+password varchar(255) not null,
+gender enum ('FEMALE', 'MALE') null,
+constraint uk_member_login_id
+unique (login_id)
+);
+```
+
+*기존 회원 아이디 중복 여부 조회하던 로직*
+``` java
+public boolean getByLoginId(String loginId) {
+ Optional member = memberRepository.findByLoginId(loginId);
+ // ..
+}
+```
+이 경우 스프링 JPA는 아래와 같은 쿼리를 날린다
+``` sql
+select *
+from
+ member
+where
+ login_id = ?
+```
+
+
+*리팩토링*
+``` java
+public interface MemberRepository extends JpaRepository {
+
+ boolean existsByLoginId(String loginId);
+}
+```
+
+``` java
+/** 회원 아이디 중복 여부 조회 */
+public boolean isDuplicateLoginId(String loginId) {
+ return memberRepository.existsByLoginId(loginId);
+}
+```
+
+스프링 JPARepository exist() 메서드는 첫 row만 찾으면 즉시
+종료되며, login_id에 unique index가 걸려있으므로 효율적으로 조회가 가능하다.
+``` sql
+select
+ 1 as col_0_0_
+from
+ member
+where
+ login_id = ?
+limit 1;
+```
+
+
+2. 예매 시 조회가 많은 상영관에 대해서도 unique 컬럼 추가
+
+``` java
+@Entity @Table(name = "auditorium",
+uniqueConstraints = @UniqueConstraint(name="uk_cinema_name", columnNames = {"cinema_id","name"}))
+@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Auditorium {
+ // ...
+}
+```
+
+3. 예매 내역 조회 시, 한 달 내 예매 내역을 우선 조회할 수 있도록 member_id, reserved_at 컬럼 인덱싱
+
+``` java
+@Entity
+@Table(
+name = "reservation",
+indexes = {
+@Index(name = "idx_member_reserved_at", columnList = "member_id, reserved_at")
+}
+)
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Reservation {
+ // ...
+}
+```
+조회 쿼리 형태
+``` sql
+select *
+from
+ reservation
+where
+ member_id = ?
+ and reserved_at >= ?
+order by reserved_at desc;
+```
\ No newline at end of file