Spring, Spring Boot) 66번째 회고
1. 1차 캐시로 인한 동일 객체 인스턴스 반환 확인
이 코드의 목적은 동일한 트랜잭션 내에서 같은 엔티티를 조회할 때 JPA가 제공하는 1차 캐시 (Entity Manager Context)로 인해 동일한 객체 인스턴스를 반환하는지 확인하는 방법이다.
Member m1 = repo.findById(dto.getNum()).get();
Member m2 = repo.findById(dto.getNum()).get();
boolean isEqual = m1 == m2;
System.out.println("m1과 m2가 같냐?" + isEqual);
📌 JPA의 1차 캐시
1️⃣ 1차 캐시
- JPA Entity Manager는 1차 캐시를 사용하여 동일한 트랙잭션 내에서 동일한 객체 인스턴스를 반환한다.
- 같은 엔티티를 여러 번 조회하더라도, 처음 조회된 엔티티 객체를 1차 캐시에 저장하고, 이후 동일한 엔티티를
다시 조회할 때 1차 캐시에서 반환한다. 이를 통해 데이터베이스 접근을 최소화하고 성능을 최적화할 수 있다.
2️⃣ 트랜잭션 내에서의 동작
- 트랜잭션 내에서 repo.findById(dto.getNum()).get()을 호출하면, JPA는 먼저 1차 캐시에서 해당 엔티티를
찾는다. - 1차 캐시에 엔티티가 존재하면, 데이터베이스를 다시 조회하지 않고 캐시에 저장된 객체를 반환한다.
- 이로 인해 m1과 m2는 동일한 인스턴스를 가리키게 된다.
3️⃣ 동일성 확인
- m1 == m2는 두 객체가 동일한 인스턴스인지를 확인한다.
- 동일한 트랜잭션 내에서 같은 엔티티를 두 번 조회하면 동일한 객체 인스턴스를 반환하는지 확인할 수 있다.
- System.out.println("m1과 m2가 같냐?" + isEqual);는 이를 출력하여 실제로 동일한 객체인지를 확인한다.
💻 예제 코드
@Transactional
@Override
public void update2(MemberDto dto) {
// 수정할 회원의 번호를 이용해서 회원 정보 entity 객체 얻어내기
Member m1 = repo.findById(dto.getNum()).get();
Member m2 = repo.findById(dto.getNum()).get();
// 동일성 검사
boolean isEqual = m1 == m2;
System.out.println("m1과 m2가 같냐?" + isEqual);
// setter 메소드를 이용해서 이름과 주소 수정하기
m1.setName(dto.getName());
m1.setAddr(dto.getAddr());
}
1️⃣ @Transactional 어노테이션
- 이 메소드가 트랜잭션 내에서 실행됨을 보장한다.
트랜잭션이 시작되고 메소드 실행이 완료되면 트랜잭션이 커밋되거나 롤백된다. - 동일한 트랜잭션 내에서 동일한 엔티티를 여러 번 조회해도 1차 캐시로 인해 같은 객체 인스턴스를 반환하게 된다.
2️⃣ 동일한 엔티티 두 번 조회
- Member m1 = repo.findById(dto.getNum()).get();
- Member m2 = repo.findById(dto.getNum()).get();
- 두 번 조회하지만, 동일한 트랜잭션 내에서 JPA 1차 캐시 덕분에 동일한 객체를 가리키게 된다.
3️⃣ 동일성 검사 및 출력
- boolean isEqual = m1 == m2;
- System.out.println("m1과 m2가 같냐?" + isEqual);
- 두 객체가 동일한 인스턴스인지 확인하고 이를 출력한다. 동일한 트랜잭션 내에서는 true가 출력된다.
4️⃣ 엔티티 수정
- m1.setName(dto.getName());
- m1.setAddr(dto.getAddr());
- 동일한 객체의 필드를 수정하면 JPA가 트랜잭션 종료 시점에 변경 사항을 감지하고
데이터베이스에 자동으로 반영한다.
그래서 위 코드는 동일한 트랜잭션 내에서 동일한 엔티티를 조회할 때 JPA가 동일한 객체 인스턴스를 반환하는지를
확인하기 위한 것이다. JPA의 1차 캐시 덕분에 동일한 트랜잭션 내에서는 동일한 엔티티를 여러 번 조회하더라도
동일한 객체를 반환한다. 이를 통해 데이터베이스 접근을 최소화하고 성능을 최적화할 수 있다.
🚀 보충 설명
JPA의 영속성 컨텍스트는 1차 캐시를 제공하여 동일한 엔티티를 조회할 때 데이터베이스 접근 없이 캐시 된 객체를 반환한다. 이는 성능 향상과 트랜잭션 내 데이터 일관성을 보장한다.
💡 주요 특징
- 영속성 컨텍스트 내에서 엔티티 ID로 객체 관리
- 동일 트랜잭션 내에서 항상 같은 인스턴스 반환
- 변경 감지 (Dirty Checking) 기능 제공
메소드 | 설명 |
entityManager.find() | 1차 캐시에서 엔티티 조회 |
entityManager.persist() | 엔티티를 1차 캐시에 저장 |
entityManager.clear() | 1차 캐시 초기화 |
💻 예시 코드
@Test
@Transactional
public void testFirstLevelCache() {
User user = entityManager.find(User.class, 1L);
User sameUser = entityManager.find(User.class, 1L);
assertThat(user).isSameAs(sameUser);
}
1차 캐시로 인한 동일 객체 인스턴스 반환 확인
* 동일한 트랜잭션 내에서 같은 엔티티를 조회할 때 JPA가 제공하는 1차 캐시(엔티티 매니저 컨텍스트)로 인해 동일한 객체 인스턴스를 반환하는지 확인하는 방법 Member m1 = repo.fi
cafe.daum.net
- 출처 : 데이터 과학자 + 프로그래머 세상 다음카페
2. @Transactional
@Transactional은 Spring Framework에서 제공하는 선언적 트랜잭션 관리를 위한 어노테이션이다.
메소드나 클래스에 적용하여 트랜잭션 경계를 설정한다.
💡 주요 속성
- propagation
트랜잭션 전파 방식 - isolation
트랜잭션 격리 수준 - readOnly
읽기 전용 트랜잭션 여부 - rollbackFor
특정 예외 발생 시 롤백
📌 속성과 설명
속성 | 설명 |
propagation | REQUIRED, REQUIRES_NEW, SUPPORTS 등 |
isolation | READ_COMMITTED, REPEATABLE_READ 등 |
timeout | 트랜잭션 제한 시간 |
rollbackFor | 롤백을 발생시킬 예외 클래스 지정 |
💻 예시 코드
@Service
public class UserService {
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
}
@Transactional(rollbackFor = Exception.class)
public User createUser(User user) {
return userRepository.save(user);
}
}
[Spring] 📚 @Transactional 이해하기
🔥 개념부터 시작하는 스프링 트랜잭션 A to Z
velog.io
- 참고 : Better.log
@Transactional 어노테이션의 이해
나는 보통 서비스 코드에 @Transactional 어노테이션을 활용해준다. 그런데 사실 뜻도 잘 모르고 좋다고 그래서 쓴거라...지나고 보니 정확히 설명하기가 어려웠다. 그런고로, 해당 어노테이션의 작
kafcamus.tistory.com
- 참고 : kafcamus
3. JavaScript Inline
JavaScript Inline은 HTML 문서 내에 직접 JavaScript 코드를 작성하는 방식이다.
주로 간단한 스크립트나 이벤트 핸들러에 사용된다.
💡 장점
- 즉시 실행 가능
- 외부 파일 로드 없음
💡 단점
- 코드 재사용성 낮음
- 유지보수 어려움
📌 사용 방식과 설명
사용 방식 | 설명 |
<script> 태그 내 작성 | HTML 문서 내 스크랩트 블록 생성 |
이벤트 속성에 직접 작성 | HTML 요소의 이벤트에 직접 연결 |
💻 예시 코드
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Inline JavaScript Example</title>
</head>
<body>
<button id="exeBTN" class="btn btn-primary">실행</button>
<script type="text/javascript">
document.querySelector("#exeBTN").addEventListener("click", () => {
const sql = document.querySelector("#inputSQL").value;
// URLSearchParams : 주소창의 경로를 다룰 수 있는 브라우저의 내장 객체
// URL의 Query 문자열을 쉽게 구성하고 조작할 수 있는 Web API I
const queryString = new URLSearchParams({query:sql}).toString();
fetch("[[@{/}]]jpql/test", {
method:"POST",
headers:{"Content-Type":"application/x-www-form-urlencoded;charset=utf-8"},
body:queryString
})
.then(res => res.json()) // promise 객체 반환. JSON으로 변환
.then(data => {
console.log(data);
const result = JSON.stringify(data, null, 3);
document.querySelector("#result").innerText = result;
});
});
</script>
</body>
</html>
타임리프 - 자바스크립트 인라인(th:inline)
<script th:inline="javascript">var username = \[\[${user.username}]];인라인 사용전 -> var username = userA;인라인 사용후 -> var username = "userA";인라인은 안에 쌍 따움
velog.io
- 참고 : slee2.log
4. URLSearchParams
URLSearchParams는 URL의 쿼리 문자열을 쉽게 다룰 수 있게 해주는 Web API이다.
URL의 매개변수를 조작하거나 새로운 URL을 생성할 때 유용하다.
📌 주요 메소드
메소드 | 설명 |
append() | 매개변수 추가 |
get() | 매개변수 값 조회 |
set() | 매개변수 값 설정 |
delete() | 매개변수 삭제 |
has() | 매개변수 존재 여부 확인 |
💻 예시 코드
// URL 쿼리 문자열 파싱
let params = new URLSearchParams("?name=John&age=30");
console.log(params.get("name")); // "John"
// 새로운 URLSearchParams 객체 생성
let newParams = new URLSearchParams();
newParams.append("category", "books");
newParams.append("sort", "price");
console.log(newParams.toString()); // "category=books&sort=price"
// 현재 URL의 쿼리 문자열 수정
let currentUrl = new URL(window.location.href);
currentUrl.searchParams.set("page", "2");
history.pushState({}, "", currentUrl);
자바스크립트의 URLSearchParams로 쿼리 스트링 다루기
Engineering Blog by Dale Seo
www.daleseo.com
- 참고 : www.daleseo.com
URLSearchParams 객체에 대해 알아보자! (feat. URL 객체)
Effitizer 프로젝트의 기존 코드를 보다가 new URLSearchParams라는 생소한 표현을 발견했다. 어딘가에서 import해 온 흔적이 보이지 않아서 구글에 검색해보니 주소창의 경로를 다룰 수 있는 브라우저의
velog.io
- 참고 : SheryLog