JPA: 대규모 결과 세트에 대해 반복하기 위한 적절한 패턴은 무엇입니까?
수백만 개의 열이 있는 테이블이 있다고 칩시다.JPA를 사용하면 수백만 개의 개체를 가진 모든 메모리 내 목록을 가지고 있지 않도록 해당 테이블에 대한 쿼리를 반복하는 적절한 방법은 무엇입니까?
예를 들어, 테이블이 클 경우 다음 항목이 폭파될 것으로 예상됩니다.
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
및 갱신)setFirstResult()
/setMaxResult()
가) 최선의 솔루션인가?" ( ) " " " "
편집: 제가 대상으로 하는 주요 사용 사례는 일종의 배치 작업입니다.달리는 데 시간이 오래 걸려도 괜찮아요.웹 클라이언트는 관여하지 않습니다.한 번에 1개씩(또는 몇 개의 작은 N개씩) 각 행에 대해 '무엇인가를' 하면 됩니다.난 그저 모든 걸 동시에 기억하게 하는 걸 피하려는 것뿐이야
「Java Persistence with Hibernate」의 537페이지에서는, 다음의 방법을 사용하고 있습니다.ScrollableResults
애석하게도 이건 동면기만을 위한 거야
그래서 이 기능을 사용하면setFirstResult
/setMaxResults
수동 반복이 정말 필요합니다.JPA를 사용하다
private List<Model> getAllModelsIterable(int offset, int max)
{
return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}
그리고 이렇게 사용합니다.
private void iterateAll()
{
int offset = 0;
List<Model> models;
while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
{
entityManager.getTransaction().begin();
for (Model model : models)
{
log.info("do something with model: " + model.getId());
}
entityManager.flush();
entityManager.clear();
em.getTransaction().commit();
offset += models.size();
}
}
여기에 제시된 답변을 시험해 보았습니다만, JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2는 동작하지 않습니다.JBoss 4.x에서 JBoss 5.1로 이행한 지 얼마 되지 않았기 때문에 현재까지는 그대로 사용하고 있습니다.따라서 최신 Hibernate는 3.3.2입니다.
몇 가지 파라미터를 추가하면 동작합니다.이러한 코드는 OOE 없이 실행됩니다.
StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();
Query query = session
.createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
query.setFetchSize(Integer.valueOf(1000));
query.setReadOnly(true);
query.setLockMode("a", LockMode.NONE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
while (results.next()) {
Address addr = (Address) results.get(0);
// Do stuff
}
results.close();
session.close();
중요한 행은 create 사이의 쿼리 파라미터입니다.조회하여 스크롤합니다.이 명령어가 없으면 "scroll" 호출은 모든 것을 메모리에 로드하려고 하며 종료되지 않거나 Out Of Memory Error로 실행되지 않습니다.
스트레이트 JPA에서는 실제로 이 작업을 수행할 수 없지만, 휴지 상태 세션과 스크롤 가능한 결과 세트를 지원합니다.
우리는 그것의 도움으로 일상적으로 수십억 개의 열을 처리합니다.
다음은 매뉴얼 링크입니다.http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
말하면 JPA를 하고 JDBC를 계속 하는 것이 확실히 JDBC를 사용합니다).JdbcTemplate
」를 참조해 주세요.는된 모든 것이 하고 있기 에 1개의 되어 있지 JPA(「ORM」/「」)의 ).clear()
(JPA) ( ( ( ( ( ( 。
ORM일 뿐)의가 너무 플레인 (반사는 빙산의 될 수 낮은 합니다.ResultSet
다음과 같은 경량 지원 사용 시에도JdbcTemplate
훨씬 더 빠를 거야
JPA는 단순히 많은 수의 엔티티에 대해 작업을 수행하도록 설계되지 않았습니다. 놀 요.flush()
/clear()
OutOfMemoryError
이치노력하다막대한 자원 소비의 대가를 치르는 것은 거의 없습니다.
이 메서드를 사용하여 Eclipse Link I'를 사용하여 Itable로 결과를 얻는 경우
private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
//eclipseLink
if(query instanceof JpaQuery) {
JpaQuery<T> jQuery = (JpaQuery<T>) query;
jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
.setHint(QueryHints.SCROLLABLE_CURSOR, true);
final Cursor cursor = jQuery.getResultCursor();
return new Iterable<T>()
{
@SuppressWarnings("unchecked")
@Override
public Iterator<T> iterator()
{
return cursor;
}
};
}
return query.getResultList();
}
클로즈 메서드
static void closeCursor(Iterable<?> list)
{
if (list.iterator() instanceof Cursor)
{
((Cursor) list.iterator()).close();
}
}
당신이 어떤 수술을 해야 하는지에 따라 달라요.왜 백만 개가 넘는 열을 반복하는 거야?배치 모드로 업데이트하고 있습니까?모든 레코드를 클라이언트에 표시하시겠습니까?검색된 엔티티에 대한 통계를 계산하고 있습니까?
클라이언트에 100만 개의 레코드를 표시하는 경우는, 유저 인터페이스를 재검토해 주세요.이 하여 이, 절, 절, 절을 하는 것입니다.setFirstResult()
★★★★★★★★★★★★★★★★★」setMaxResult()
.
레코드의 하게 해, 「」를 사용해 .Query.executeUpdate()
【워크 매니저】메시지 드리븐 빈.
취득한 엔티티에 대한 통계를 계산하는 경우 JPA 규격에 정의된 그룹화 함수를 활용할 수 있습니다.
그 외의 경우는, 좀 더 구체적으로 설명해 주세요.
이것을 「적절」하게 실시할 필요는 없습니다.이것은 JPA나 JDO, 또는 다른 ORM 의 목적이 아닙니다.스트레이트 JDBC 는, 한 번에 소수의 행을 되돌려, 사용중의 행에 플래시 하도록 설정할 수 있기 때문에, 서버측 커서가 존재하는 가장 좋은 방법입니다.
ORM 도구는 벌크 처리용으로 설계되지 않았습니다.오브젝트를 조작하여 데이터가 저장되어 있는RDBMS를 가능한 한 투과적으로 만들 수 있도록 설계되어 있습니다.대부분은 투과적인 부분에서 적어도 어느 정도 장애가 발생합니다.이 규모에서는 ORM을 사용하여 수십만 행(오브젝트)을 처리할 방법이 없습니다.오브젝트 인스턴스화의 오버헤드는 단순하고 단순하기 때문에 ORM을 사용하여 처리할 수 있는 시간은 더욱 적습니다.
적절한 도구를 사용합니다.Straight JDBC와 Stored Procedures는 특히 ORM 프레임워크에 비해 뛰어난 기능을 갖추고 있습니다.
모든 것을 단순하게 끌어당기는 것조차List<Integer>
어떻게 하든지 별로 효율적이지 않을 것입니다.이에요.SELECT id FROM table
로합니다.SERVER SIDE
dependent )및 ('')에 대한 커서FORWARD_ONLY READ-ONLY
그걸 반복하는 거죠
몇 개의 웹 서버를 호출하여 수백만 개의 ID를 실제로 처리하려면 몇 가지 동시 처리를 수행해야 합니다.JDBC 커서를 사용하여 여러 개의 스레드를 Concurrent Linked Queue에 한 번에 배치하고 작은 스레드 풀(# CPU/Cores + 1)을 풀링하여 처리하는 것이 이미 메모리가 부족하다는 것을 알 수 있는 유일한 방법입니다.
이 답변도 참조해 주세요.
다른 "꼼수"를 사용할 수 있습니다.관심 있는 엔티티의 식별자 모음만 로드합니다.예를 들어, 식별자가 long=8바이트 유형이라고 가정하면, 10^6의 식별자 목록은 약 8Mb를 만듭니다. 만약 이것이 배치 프로세스(한 번에 한 인스턴스씩)라면, 견딜 수 있습니다.그럼 그냥 반복하고 그 일을 하세요.
또 하나의 코멘트는, 어쨌든 청크로 실시할 필요가 있습니다.특히 레코드를 수정하는 경우는, 데이터베이스의 롤백 세그먼트가 커집니다.
firstResult/maxRows 전략을 설정하는 경우, 상위와는 거리가 먼 결과에 대해서는 매우 느립니다.
또한 데이터베이스가 읽기 커밋된 격리 상태로 작동하므로 팬텀 읽기를 방지하고 식별자를 로드한 다음 엔티티를 하나씩 로드합니다(또는 10x10).
이 답변에서는 스토어드 프로시저의 사용이 그다지 두드러지지 않는 것을 알고 놀랐습니다.과거에는 이와 같은 작업을 수행해야 할 때 데이터를 작은 덩어리로 처리하고 잠시 동안 절전 상태를 유지한 후 계속하는 저장 프로시저를 만들었습니다.sleep 상태가 되는 이유는 데이터베이스에 과부하가 걸리지 않기 위해서입니다.데이터베이스는 웹 사이트에 접속하는 등 보다 실시간타입의 쿼리에 사용되고 있을 가능성이 있습니다.데이터베이스를 사용하는 다른 사용자가 없는 경우 sleep을 생략할 수 있습니다.각 레코드를 한 번만 처리해야 하는 경우 재시작 시 복원력을 유지하기 위해 처리한 레코드를 저장하는 추가 테이블(또는 필드)을 만들어야 합니다.
여기서의 퍼포먼스 삭감은 현저하고, JPA/Hibernate/AppServer 랜드에서 할 수 있는 것보다 훨씬 빠를 수 있습니다.또한 데이터베이스 서버에는 대규모 결과 세트를 효율적으로 처리하기 위한 독자적인 서버 사이드 커서 타입의 메커니즘이 탑재되어 있을 가능성이 높습니다.따라서 데이터를 데이터베이스 서버에서 애플리케이션 서버로 전송하지 않아도 되고, 여기서 데이터를 처리한 후 다시 전송할 필요가 없어집니다.
스토어드 프로시저를 사용하면 이 문제를 완전히 배제할 수 있는 중대한 단점이 몇 가지 있지만, 개인 툴박스에 이러한 기술을 가지고 있고 이러한 상황에서 사용할 수 있다면 이러한 것들을 상당히 빠르게 제거할 수 있습니다.
@Tomasz Nurkiewicz.'할 수 있다'에할 수 있습니다.DataSource
, 수 있습니다.
@Resource(name = "myDataSource",
lookup = "java:comp/DefaultDataSource")
private DataSource myDataSource;
코드에는
try (Connection connection = myDataSource.getConnection()) {
// raw jdbc operations
}
이것에 의해, Import/export등의 특정의 대규모 배치 조작에 대해서 JPA 를 바이패스 할 수 있습니다만, 필요에 따라서 엔티티 매니저에 액세스 할 수 있습니다.
다음은 Kotlin의 간단한 JPA 예제입니다.커서를 사용하지 않고 100개의 항목의 청크를 한 번에 읽는 임의의 큰 결과 세트에 대해 페이지 수를 매기는 방법을 보여줍니다(각 커서는 데이터베이스의 리소스를 소비합니다).키 세트의 페이지 번호를 사용합니다.
키 세트의 페이지화의 개념에 대해서는, https://use-the-index-luke.com/no-offset 를 참조해 주세요.또, 페이지화의 다양한 방법과 그 결점을 비교하려면 , https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/ 를 참조해 주세요.
/*
create table my_table(
id int primary key, -- index will be created
my_column varchar
)
*/
fun keysetPaginationExample() {
var lastId = Integer.MIN_VALUE
do {
val someItems =
myRepository.findTop100ByMyTableIdAfterOrderByMyTableId(lastId)
if (someItems.isEmpty()) break
lastId = someItems.last().myTableId
for (item in someItems) {
process(item)
}
} while (true)
}
오프셋을 사용한 크기 요소마다 JPA 및 NativeQuery 가져오기 예제
public List<X> getXByFetching(int fetchSize) {
int totalX = getTotalRows(Entity);
List<X> result = new ArrayList<>();
for (int offset = 0; offset < totalX; offset = offset + fetchSize) {
EntityManager entityManager = getEntityManager();
String sql = getSqlSelect(Entity) + " OFFSET " + offset + " ROWS";
Query query = entityManager.createNativeQuery(sql, X.class);
query.setMaxResults(fetchSize);
result.addAll(query.getResultList());
entityManager.flush();
entityManager.clear();
return result;
}
마지막으로 원하는 답변은 JPA 2.2로, 휴지 상태(최소한 v5.4.30에서)는 이전 답변에서 설명한 Scrollable 구현을 사용합니다.
이제 코드는 다음과 같습니다.
entityManager().createQuery("from Model m", Model.class)
.getResultStream();
.forEach(model -> System.out.println(model.getId());
Pagination
과과 concept concept concept 。
저도 궁금했어요.중요한 것 같습니다.
- 데이터 집합의 크기(행)
- 사용하고 있는 JPA 실장
- 각 행에 대해 어떤 처리를 하고 있는지 확인합니다.
양쪽 접근법(모두 검색과 검색 엔트리)을 쉽게 교환할 수 있도록 하기 위해 Iterator를 작성했습니다.
둘 다 드셔보세요.
Long count = entityManager().createQuery("select count(o) from Model o", Long.class).getSingleResult();
ChunkIterator<Model> it1 = new ChunkIterator<Model>(count, 2) {
@Override
public Iterator<Model> getChunk(long index, long chunkSize) {
//Do your setFirst and setMax here and return an iterator.
}
};
Iterator<Model> it2 = List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList().iterator();
public static abstract class ChunkIterator<T>
extends AbstractIterator<T> implements Iterable<T>{
private Iterator<T> chunk;
private Long count;
private long index = 0;
private long chunkSize = 100;
public ChunkIterator(Long count, long chunkSize) {
super();
this.count = count;
this.chunkSize = chunkSize;
}
public abstract Iterator<T> getChunk(long index, long chunkSize);
@Override
public Iterator<T> iterator() {
return this;
}
@Override
protected T computeNext() {
if (count == 0) return endOfData();
if (chunk != null && chunk.hasNext() == false && index >= count)
return endOfData();
if (chunk == null || chunk.hasNext() == false) {
chunk = getChunk(index, chunkSize);
index += chunkSize;
}
if (chunk == null || chunk.hasNext() == false)
return endOfData();
return chunk.next();
}
}
청크 반복기를 사용하지 않게 되었습니다(그렇게 테스트되지 않았을 수도 있습니다).참고로 구글 컬렉션을 사용하려면 구글 컬렉션이 필요합니다.
휴지 상태에서는 원하는 것을 실현하기 위한 4가지 방법이 있습니다.각각 설계상의 트레이드오프, 제한사항 및 결과가 있습니다.각각의 상황을 살펴보고 어떤 것이 당신의 상황에 적합한지 결정할 것을 제안합니다.
- stateless 세션을 스크롤()과 함께 사용합니다.
- 반복할 때마다 session.clear()를 사용합니다.다른 엔티티를 연결해야 할 경우 다른 세션에 로드합니다.사실상 첫 번째 세션은 상태 비저장 세션을 에뮬레이트하지만 개체가 분리될 때까지 상태 저장 세션의 모든 기능을 유지합니다.
- interate() 또는 list()를 사용하되 첫 번째 쿼리에서 ID만 얻은 다음 각 반복에서 session.load를 수행하고 반복이 끝날 때 세션을 닫습니다.
- EntityManager.detach() 또는 Session.evict()와 함께 Query.iterate()를 사용합니다.
언급URL : https://stackoverflow.com/questions/5067619/jpa-what-is-the-proper-pattern-for-iterating-over-large-result-sets
'itsource' 카테고리의 다른 글
MySQL 일부 외부 키 제거 (0) | 2022.09.14 |
---|---|
PHP를 사용하여 IP 주소 국가 가져오기 (0) | 2022.09.13 |
문자열이 JS의 regex와 일치하는지 확인합니다. (0) | 2022.09.13 |
query Selector 및 query SelectorJavaScript의 모든 vs getElementsByClassName 및 getElementById (0) | 2022.09.13 |
MySQL CREATE/DROP USER가 0을 반환함 (0) | 2022.09.13 |