JPA에서 게시한 SQL 문의 소스와 컨텍스트 찾기
44351 단어 performancejpashowdevjava
우리는 여기서 무슨 문제를 해결합니까?
Hibernate와 다른 JPA 실현자와 같은 ORM 프레임워크는 지구층의 개발을 현저하게 간소화할 수 있다.실체 추상 단계를 도입하면 깨끗한 업무 영역을 모델링하고 영역의 지속성을 실현하는 데 사용되는 하위 SQL 문장을 숨깁니다.개발자가 더 이상 응용 프로그램에서 사용하는 모든 SQL 문장을 만들고 유지할 필요가 없기 때문에 이런 방법은 대형 영역에서 특히 유용하다.도메인 클래스 및
@Entity
또는 사용자 정의 저장소에 @ManyToOne
/@OneToMany
/EntityManager
/fetchType = FetchType.EAGER
등의 JPA 주석을 추가하면 도메인 집합 루트를 얻을 수 있으며 지금부터 시작할 수 있습니다.이렇게 하면 지속성과 관련된 절대 다수의 용례는 역 모델에서 JPA 등급을 연결하고 반복적으로 연결하며 이를 실행하는 조작을 통해 처리할 수 있다.모든 것은 대가가 있는 것이다.청결하고 편리한 발전에 대한 교환으로 몇 가지 문제가 조만간 나타날 것이다.
application.yml
관련 문제)Hibernate를 통해 JPA가 게시한 SQL 문을 기록합니다.
현재로서는 JPA가 어떤 SQL 문장을 실행했는지 검사하는 가장 간단한 방법은 JPA가 발표한 모든 SQL 문장의 로그 기록을 사용한 다음, 일부 데이터를 가져오거나 새로 고치거나, 사무 제출로 촉발되었는지 하나하나 조사하는 것이다.JPA가 logger를 통해 실행한 SQL 문장을 기록하기 위해서는
spring.jpa.show-sql
에 다음과 같은 속성을 추가해야 한다 logging.level:
org.hibernate.SQL: DEBUG
spring:
jpa:
properties:
hibernate:
format_sql: true # optional
use_sql_comments: true # optional
기록기의 로그 기록이 아닌 표준 출력 (남겨진 것) 속성을 활성화할 수 있습니다.따라서 다음과 같은 로그를 보고할 수 있습니다.
2020-10-31 19:37:40.730 DEBUG 15775 --- [ main] org.hibernate.SQL :
select
products0_.order_id as order_id1_9_0_,
products0_.product_id as product_2_9_0_,
product1_.id as id1_14_1_,
product1_.manufacturer_id as manufact3_14_1_,
product1_.name as name2_14_1_
from
order_product products0_
inner join
product product1_
on products0_.product_id=product1_.id
where
products0_.order_id=?
hibernate 로그의 일부에는 SQL 문을 JPA 솔리드 클래스와 연결하는 SQL 주석이 포함될 수 있습니다. 2020-11-25 23:51:58.390 DEBUG 28750 --- [ main] org.hibernate.SQL :
/* insert com.adgadev.blog.examples.bookshop.Book */ insert
into
book
(author_id, title, id)
values
(?, ?, ?)
일반적으로 이런 로그는 매우 유용할 수 있지만, 응용 프로그램 소스 코드에서 그것을 터치하는 위치를 어떻게 찾는지 관건적인 정보가 부족하다.이것은 JPA 실체 연결을 통해 실체 에이전트를 초기화한 결과입니까? 아니면 서비스가 JPQL 조회를 현시적으로 실행한 결과입니까?만약 응용 프로그램에 여러 개의 다른 서비스가 이러한 검색을 촉발할 수 있다면, 어느 것입니까?흐름은 무엇입니까?현대 코드 라이브러리의 영역과 논리는 매우 간단하거나, SQL 문장이 코드의 특정한 위치만을 가리킬 때, 추측을 시도할 수 있지만, 이것은 항상 쉽지 않다. 특히 JPA가 생성한 검색이 일부 실체를 불러오는 지연되는 상황에서는.이 경우 코드를 디버깅하고 SQL 문을 검사하는 행이 유일한 선택일 수 있습니다.디버그의 또 다른 문제는 IDE가 단점에서 변수의 값을 해석하기 때문에 실체의 불러오는 방식을 바꿀 수 있다는 것이다.한 서비스를 호출할 때 코드의 다른 위치에서 수십 개의 SQL을 촉발할 수 있다고 상상해 보세요. 이 SQL들은 모두 이런 서비스에서 호출됩니다.이 일을 조사하는 것은 매우 번거롭고, 게다가 정말 많은 시간이 필요하다.
제프리슨이 구원하러 왔어요.
도서관은 이런 문제들을 해결하는 데 목적을 두고 있다.일부 용례를 처리할 때 프로그램이 트리거하는 모든 JPA/SQL 활동을 포함하는 간결한 보고서를 만들 수 있습니다.이렇게 하면 호출된 SQL 문구를 JPA 작업과 쉽게 연결하고, 호출된 코드에서 정확한 줄을 찾을 수 있습니다.
2020-11-26 22:20:52.096 DEBUG 9990 --- [ main] c.a.j.core.report.ReportGenerator :
ROOT
com.adgadev.examples.bookshop.BookshopControllerTest.shouldGetBookDetails(BookshopControllerTest.java:46)
com.adgadev.examples.bookshop.BookshopController.getSampleBook(BookshopController.java:31)
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails [PROXY]
SESSION BOUNDARY
OPERATION [EXPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:32)
com.adgadev.examples.bookshop.BookRepository.findById [PROXY]
STATEMENT [READ]
select [...] from
book book0_
where
book0_.id=1
OPERATION [IMPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:34)
com.adgadev.examples.bookshop.Author.getName [PROXY]
com.adgadev.examples.bookshop.Author [FETCHING ENTITY]
STATEMENT [READ]
select [...] from
author author0_
where
author0_.id=1
OPERATION [IMPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:35)
com.adgadev.examples.bookshop.Author.countWrittenBooks(Author.java:41)
com.adgadev.examples.bookshop.Author.books [FETCHING COLLECTION]
STATEMENT [READ]
select [...] from
book books0_
where
books0_.author_id=1
또한 트리거하는 동작을 감지합니다.@ManyToOne
/@OneToOne
fetch
로 설정LAZY
@OneToMany
/@ManyToMany
을 초기화하고 fetch
으로 설정LAZY
EntityManager
트랜잭션 제출 시 트리거
INSERT
/SELECT
JPlusOne 모든 스프링 커버 2와 호환됩니다.x 기반 응용 프로그램은 JDK 9+에서 실행되며 Hibernate는 JPA 구현자입니다.JPlusOne
JPA 도메인 및 서비스 예
이러한 예를 더 잘 설명하기 위해서, 우리는 두 개의 JPA 실체로 구성된 간단한 영역을 가정한다.첫 번째는
Author
솔리드입니다. @Getter
@Entity
class Author {
@Id
private Long id;
private String name;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
private Set<Book> books = new HashSet<>();
int countWrittenBooks() {
return books.size();
}
}
두 번째는 Book
솔리드입니다. @Getter
@Entity
@EqualsAndHashCode(of = "id")
class Book {
@Id
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
}
또 하나의 간단한 서비스BookshopService
: @Service
@Transactional
@RequiredArgsConstructor
class BookshopService {
private final BookRepository bookRepository;
public BookDto getSampleBookDetails() {
Book book = bookRepository.findById(1L).get();
String authorName = book.getAuthor().getName();
int amountOfBooks = book.getAuthor().countWrittenBooks();
return new BookDto(authorName, book.getTitle(), amountOfBooks);
}
}
간단한 MVC 디렉터가 하나 더 있습니다. @RestController
@RequiredArgsConstructor
class BookshopController {
private final BookshopService bookshopService;
@GetMapping("/book/lazy")
BookDto getSampleBook() {
return bookshopService.getSampleBookDetails();
}
}
JPA 지연 로드 작업 및 관련 SQL 문 보고
JPA 로드 지연을 감지하기 위해 다음 종속성을 추가합니다.
<dependency>
<groupId>com.adgadev.jplusone</groupId>
<artifactId>jplusone-core</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
마지막 단계는 jplusone에 레코더를 설정하는 것입니다. application.yml
에 다음 줄을 추가합니다. logging.level:
com.adgadev.jplusone: DEBUG
Spring Boot'a 자동 구성은 나머지 구성을 자동으로 완료합니다.현재 JPA의 지속성을 직접 또는 간접적으로 이용하는 통합 테스트, 즉 BookshopController
단점에 요청하는 테스트를 실행합니다. @ActiveProfiles("integration-test")
@SpringBootTest(webEnvironment = MOCK)
@AutoConfigureMockMvc
class BookshopControllerTest {
@Autowired
private MockMvc mvc;
@Test
void shouldGetBookDetails() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/book/lazy")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
그러면 다음과 같이 JPA 작업/SQL 문에 대한 JPlusOne 보고서가 포함된 로그 항목이 추가됩니다. 2020-11-26 22:27:59.683 DEBUG 10730 --- [ main] c.a.j.core.report.ReportGenerator :
ROOT
com.adgadev.examples.bookshop.BookshopControllerTest.shouldGetBookDetails(BookshopControllerTest.java:46)
com.adgadev.examples.bookshop.BookshopController.getSampleBook(BookshopController.java:31)
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails [PROXY]
SESSION BOUNDARY
OPERATION [IMPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:34)
com.adgadev.examples.bookshop.Author.getName [PROXY]
com.adgadev.examples.bookshop.Author [FETCHING ENTITY]
STATEMENT [READ]
select [...] from
author author0_
where
author0_.id=1
OPERATION [IMPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:35)
com.adgadev.examples.bookshop.Author.countWrittenBooks(Author.java:41)
com.adgadev.examples.bookshop.Author.books [FETCHING COLLECTION]
STATEMENT [READ]
select [...] from
book books0_
where
books0_.author_id=1
이 예에서는 다음과 같은 두 가지 지연 로드 작업을 볼 수 있습니다.BookshopService.getSampleBookDetailsUsingLazyLoading
에서 JPA 실체getName
의 대리 대상에서 집행방법Author
.그 결과 프록시 객체의 초기화가 트리거되어 SQL 질의가 수행됩니다.Author.countWrittenBooks
에서 집합books
의 내용을 방문했는데 이 집합은 Book
실체와 관련된'많은'부분을 대표한다.그 결과 SQL 조회를 실행할 때 이 집합의 초기화Author.books
를 촉발했다.[…]
로 대체했다.기타 JPA 작업 및 관련 SQL 문 유형 보고
기본적으로 보고서에는 SQL SELECT 문을 지연시키는 로드 작업만 포함됩니다(
IMPLICIT
.일부 데이터 가져오기/새로 고침EXPLICIT
에 대한 명시적 호출과 관련된 JPA 작업은 포함되지 않습니다.마찬가지로, 사무를 제출할 때의 세션 리셋과 관련된 작업 (COMMIT
.application.yml
에 다음 줄을 추가하여 사용자 정의 필터 조건을 정의하면 기본 동작을 쉽게 변경할 수 있습니다. 지원하는 필터 모드와 기타 설정 옵션에 대한 자세한 정보는
를 참조하십시오. jplusone:
report:
operation-filtering-mode: EXPLICIT_OPERATIONS_ONLY
statement-filtering-mode: ALL_STATEMENTS
이전 예와 동일한 테스트를 실행하면 다음 보고서가 생성됩니다. 2020-11-26 22:30:13.497 DEBUG 10997 --- [ main] c.a.j.core.report.ReportGenerator :
ROOT
com.adgadev.examples.bookshop.BookshopControllerTest.shouldGetBookDetails(BookshopControllerTest.java:46)
com.adgadev.examples.bookshop.BookshopController.getSampleBook(BookshopController.java:31)
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails [PROXY]
SESSION BOUNDARY
OPERATION [EXPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:32)
com.adgadev.examples.bookshop.BookRepository.findById [PROXY]
STATEMENT [READ]
select [...] from
book book0_
where
book0_.id=1
보고서에 따르면 34행BookshopService.getSampleBookDetailsUsingLazyLoading
에서 Spring Data JPA repositoryfindById
를 호출하는 방법BookRepository
을 통해 현저한 가져오기 동작을 촉발하여 SQL 조회를 실행했다.JPlusOne documentation
JPA/SQL 최적화 서비스의 향후 최적화 보장
이제 테스트는 코드가 예상대로 작동하고 버그가 없는지 확인하기 위한 것이 아니다.코드의 다른 측면도 테스트할 수 있다.하나의 좋은 예는 인데 응용 프로그램의 단원 테스트 구조, 즉 응용 프로그램의 층 구조를 유지보수할 수 있도록 허용한다.시스템 구조에 대해 단원 테스트를 할 수 있는 이상 왜 JPA/SQL 성능 측면에서 최적화된 용례 논리가 미래의 코드 변경 후에 최적화 상태를 유지하는지 테스트하지 않습니까?일부 JPA 맵/연결을 추가하거나 수정하면 일부 흐름에 추가 지연 로드 작업을 쉽게 도입할 수 있지만, 이러한 변경 사항을 적용할 때 이 점을 발견하기 어려울 수도 있습니다.
이전 예시에서 통합 테스트를 확장합니다. 검증 검사는 두 개의 JPA 지연 불러오기 동작만 있습니다. 첫 번째는 불러오기
Author
에 사용되고, 두 번째는 불러오기 Author.books
집합에 사용됩니다.이러한 테스트를 작성하려면 다음 종속성을 추가해야 합니다. <dependency>
<groupId>com.adgadev.jplusone</groupId>
<artifactId>jplusone-assert</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
그리고 JPlusOneAssertionRule
대상을 정의하고 주입assertionContext
에 따라 검사해야 한다. @ActiveProfiles("integration-test")
@SpringBootTest(webEnvironment = MOCK)
@AutoConfigureMockMvc
class BookshopControllerTest {
@Autowired
private JPlusOneAssertionContext assertionContext;
@Autowired
private MockMvc mvc;
@Test
void shouldGetBookDetails() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/book/lazy")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
JPlusOneAssertionRule rule = JPlusOneAssertionRule
.within().lastSession()
.shouldBe().noImplicitOperations().exceptAnyOf(exclusions -> exclusions
.loadingEntity(Author.class)
.loadingCollection(Author.class, "books")
);
rule.check(assertionContext);
}
}
규정에 부합되는 이상 테스트 결과는 녹색이다.실행 중 세 번째 지연 불러오기 동작을 도입할 때 어떤 일이 일어나는지 보여 줍니다.가장 간단한 방법은 BookshopService.getSampleBookDetailsUsingLazyLoading
의 단일행을 다음과 같이 변경하는 것입니다. Book book = bookRepository.findById(1L).get();
다음을 수행합니다. Book book = bookRepository.getOne(1L);
그러면 JPA 엔티티가 아닌 JPA 에이전트로 돌아갑니다.에이전트는 위에서 호출된 첫 번째 작업에서 초기화되어 Book
실체의 로드 지연을 초래합니다.테스트AssertionError
를 다시 실행할 때 던지기: java.lang.AssertionError: Actual amount of IMPLICIT operations after applying exclusions is different than the expected amount
Expected: exactly
Actual :
Operations after applying requested exclusions:
ROOT
com.adgadev.examples.bookshop.BookshopControllerTest.shouldGetBookDetails(BookshopControllerTest.java:46)
com.adgadev.examples.bookshop.BookshopController.getSampleBook(BookshopController.java:31)
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails [PROXY]
SESSION BOUNDARY
OPERATION [IMPLICIT]
com.adgadev.examples.bookshop.BookshopService.getSampleBookDetails(BookshopService.java:34)
com.adgadev.examples.bookshop.Book.getAuthor [PROXY]
com.adgadev.examples.bookshop.Book [FETCHING ENTITY]
STATEMENT [READ]
select [...] from
book book0_
where
book0_.id=1
at com.adgadev.jplusone.asserts.impl.rule.AmountVerifier.checkAmount(AmountVerifier.java:44)
at com.adgadev.jplusone.asserts.impl.rule.Condition.checkOperation(Condition.java:84)
at com.adgadev.jplusone.asserts.impl.rule.Condition.check(Condition.java:54)
at com.adgadev.jplusone.asserts.impl.rule.Rule.lambda$check$0(Rule.java:48)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at com.adgadev.jplusone.asserts.impl.rule.Rule.check(Rule.java:48)
at com.adgadev.jplusone.asserts.impl.Assertion.check(Assertion.java:38)
at com.adgadev.jplusone.asserts.impl.ConditionDoneBuilderImpl.check(ConditionDoneBuilderImpl.java:38)
at com.adgadev.examples.bookshop.BookshopControllerTest.shouldGetBookDetails(BookshopControllerTest.java:57)
...
ArchUnit 요약
예시된 전체 소스 코드 를 사용할 수 있습니다.
here가 유용하고 더 개발할 가치가 있다고 생각되면 JPlusOne library에 프로젝트 라이브러리를 표시하십시오. 감사합니다!
Reference
이 문제에 관하여(JPA에서 게시한 SQL 문의 소스와 컨텍스트 찾기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/adgadev/finding-origin-and-context-of-jpa-issued-sql-statements-31bi텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)