[SPRING] eager fetches를위한 Spring JDBC RowMapper 사용법
SPRINGeager fetches를위한 Spring JDBC RowMapper 사용법
문제는 Spring jdbc를 사용하여 세부 사항을 열심히 가져오고 자하는 마스터 / 세부 시나리오의 RowMapper에 대한 모범 사례 사용에 관한 것입니다.
Invoice 클래스와 InvoiceLine 클래스가 있다고 가정합니다.
public class Invoice{
private BigDecimal invId;
private Date invDate;
private List<InvoiceLine> lines;
}
public class InvoiceLine{
private int order;
private BigDecimal price;
private BigDecimal quantity;
}
행 매퍼로 Spring Jdbc를 사용할 때 우리는 보통
public class InvoiceMapper implements RowMapper<Invoice>{
public Invoice mapRow(ResultSet rs, int rowNum) throws SQLException {
Invoice invoice = new Invoice();
invoice.setInvId(rs.getBigDecimal("INVID"));
invoice.setInvDate(rs.getDate("INVDATE"));
return invoice;
}
}
이제 문제는이 송장 인스턴스와 관련된 InvoiceLine을 열심히 가져오고 싶습니다. 만약 내가 rowmapper 클래스에서 데이터베이스를 쿼리해도 괜찮을까요? 아니면 다른 사람이 다른 방법을 선호합니까? 나는 아래의 패턴을 사용하지만 그 패턴에 만족하지는 않습니다.
public class InvoiceMapper implements RowMapper<Invoice>{
private JdbcTemplate jdbcTemplate;
private static final String SQLINVLINE=
"SELECT * FROM INVOICELINES WHERE INVID = ?";
public Invoice mapRow(ResultSet rs, int rowNum) throws SQLException {
Invoice invoice = new Invoice();
invoice.setInvId(rs.getBigDecimal("INVID"));
invoice.setInvDate(rs.getDate("INVDATE"));
invoice.setLines(jdbcTemplate.query(SQLINVLINE,
new Object[]{invoice.getInvId},new InvLineMapper());
return invoice;
}
}
나는이 접근법에 잘못된 점이 있지만 더 좋은 길을 찾을 수 없다는 것을 알고 있습니다. 왜 누군가가 나에게 왜 나쁜 디자인인지 그리고 올바른 사용법이 무엇인지를 보여줄 수 있다면 나는 기쁜 것 이상입니다.
해결법
-
==============================
1.이렇게하기 위해서는 ResultSetExtractor가 더 좋습니다. 두 테이블을 조인 한 쿼리를 실행 한 다음 결과 집합을 반복합니다. 인보이스 ID로 주문하고 ID가 변경 될 때 확인하거나 아래 예와 같이지도를 사용하여 동일한 인보이스에 속하는 여러 행을 집계하는 논리가 필요합니다.
이렇게하기 위해서는 ResultSetExtractor가 더 좋습니다. 두 테이블을 조인 한 쿼리를 실행 한 다음 결과 집합을 반복합니다. 인보이스 ID로 주문하고 ID가 변경 될 때 확인하거나 아래 예와 같이지도를 사용하여 동일한 인보이스에 속하는 여러 행을 집계하는 논리가 필요합니다.
jdbcTemplate.query("SELECT * FROM INVOICE inv JOIN INVOICE_LINE line " + + " on inv.id = line.invoice_id", new ResultSetExtractor<List<Invoice>>() { public List<Invoice> extractData(ResultSet rs) { Map<Integer,Invoice> invoices = new HashMap<Integer,Invoice>(); while(rs.hasNext()) { rs.next(); Integer invoiceId = rs.getInt("inv.id"); Invoice invoice = invoces.get(invoiceId); if (invoice == null) { invoice = invoiceRowMapper.mapRow(rs); invoices.put(invoiceId,invoice); } InvoiceItem item = invLineMapper.mapRow(rs); invoice.addItem(item); } return invoices.values(); } });
-
==============================
2.여기서 1 + n 문제를 재현 한 것입니다.
여기서 1 + n 문제를 재현 한 것입니다.
이를 해결하려면 외부 쿼리를 조인으로 변경 한 다음 공예품에 루프를 보내어 플랫 조인 결과 세트를 인보이스로 구문 분석해야합니다. 1 -> * InvLine
List<Invoice> results = new ArrayList<>(); jdbcTemplate.query("SELECT * FROM INVOICE inv JOIN INVOICE_LINE line on inv.id = line.invoice_id", null, new RowCallbackHandler() { private Invoice current = null; private InvoiceMapper invoiceMapper ; private InvLineMapper lineMapper ; public void processRow(ResultSet rs) { if ( current == null || rs.getInt("inv.id") != current.getId() ){ current = invoiceMapper.mapRow(rs, 0); // assumes rownum not important results.add(current); } current.addInvoiceLine( lineMapper.mapRow(rs, 0) ); } }
나는 분명히 이것을 컴파일하지 않았다 ... 당신이 아이디어를 얻었기를 바랍니다. 다른 옵션이 있는데, 최대 절전 모드 나 JPA 구현을 사용하면, 이런 종류의 작업을 수행하고 시간을 절약 할 수 있습니다.
수정 : ResultSetExtractor를 사용하여 @gkamal이 그의 대답에 사용 했어야하지만, 모든 논리가 여전히 유효합니다.
-
==============================
3.ResultSetExtractor는 전체 ResultSet을 처리하고 하나의 (거대한 거대한) 객체를 반환하기 때문에 사용하지 않는 것이 좋습니다.
ResultSetExtractor는 전체 ResultSet을 처리하고 하나의 (거대한 거대한) 객체를 반환하기 때문에 사용하지 않는 것이 좋습니다.
내 솔루션을 사용하면 관련 행 그룹당 하나의 객체를 얻을 수 있으며 다음 객체를 얻기 전에이 객체를 처리 할 수 있습니다. 응용 프로그램에서 CollectingRowMapper 인터페이스와 추상 구현을 만들었습니다. 아래 코드를 참조하십시오. Javadoc 주석이 들어 있습니다.
import org.springframework.jdbc.core.RowMapper; /** * A RowMapper that collects data from more than one row to generate one result object. * This means that, unlike normal RowMapper, a CollectingRowMapper will call * <code>next()</code> on the given ResultSet until it finds a row that is not related * to previous ones. Rows must be sorted so that related rows are adjacent. * Tipically the T object will contain some single-value property (an id common * to all collected rows) and a Collection property. * <p/> * NOTE. Implementations will be stateful (to save the result of the last call * to <code>ResultSet.next()</code>), so <b>they cannot have singleton scope</b>. * * @see AbstractCollectingRowMapper * * @author Pino Navato **/ public interface CollectingRowMapper<T> extends RowMapper<T> { /** * Returns the same result of the last call to <code>ResultSet.next()</code> made by <code>RowMapper.mapRow(ResultSet, int)</code>. * If <code>next()</code> has not been called yet, the result is meaningless. **/ public boolean hasNext(); }
import java.sql.ResultSet; import java.sql.SQLException; /** * Basic implementation of {@link CollectingRowMapper}. * * @author Pino Navato **/ public abstract class AbstractCollectingRowMapper<T> implements CollectingRowMapper<T> { private boolean lastNextResult; @Override public T mapRow(ResultSet rs, int rowNum) throws SQLException { T result = mapRow(rs, null, rowNum); while (nextRow(rs) && isRelated(rs, result)) { result = mapRow(rs, result, ++rowNum); } return result; } /** * Collects the current row into the given partial result. * On the first call partialResult will be null, so this method must create * an instance of T and map the row on it, on subsequent calls this method updates * the previous partial result with data from the new row. * * @return The newly created (on the first call) or modified (on subsequent calls) partialResult. **/ protected abstract T mapRow(ResultSet rs, T partialResult, int rowNum) throws SQLException; /** * Analyzes the current row to decide if it is related to previous ones. * Tipically it will compare some id on the current row with the one stored in the partialResult. **/ protected abstract boolean isRelated(ResultSet rs, T partialResult) throws SQLException; @Override public boolean hasNext() { return lastNextResult; } protected boolean nextRow(ResultSet rs) throws SQLException { lastNextResult = rs.next(); return lastNextResult; } }
JdbcCursorItemReader의 커스텀 서브 클래스에서 Spring Batch와 함께이 코드를 사용한다. 나는이 해답을 또 다른 대답으로 설명했다.
from https://stackoverflow.com/questions/11455220/spring-jdbc-rowmapper-usage-for-eager-fetches by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring을 통해 서버 모드에서 H2 데이터베이스 시작하기 (0) | 2019.02.03 |
---|---|
[SPRING] Spring을 이용한 런타임 의존성 주입 (0) | 2019.02.03 |
[SPRING] Spring Controller에서 Web App 루트 얻기 (0) | 2019.02.03 |
[SPRING] 다른 인증 프로 바이더 (Web App 용 API 및 LDAP의 기본 인증)로 여러 WebSecurityConfigurerAdapter 사용 (0) | 2019.02.03 |
[SPRING] Spring Config에 의한 Spring 데이터 저장소 스캐닝? (0) | 2019.02.03 |