问题是关于RowMapper在主/细节场景中的最佳实践用法,我们希望使用Spring jdbc急切地获取细节。
假设我们有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中-
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) );
}
}
我显然还没有编译这个...希望你明白这个想法。还有另一个选择,使用hibernate或任何JPA实现,他们开箱即用地做这种事情,会为你节省很多时间。
更正:应该像@gkamal在回答中所用的那样,真正使用结果文本提取器,但总体逻辑仍然成立。
基于ResultSetExtractor的公认解决方案可以变得更加模块化和可重用:在我的应用程序中,我创建了一个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 <b>must be sorted</b> 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;
}
}
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.Assert;
/**
* A ResultSetExtractor that uses a CollectingRowMapper.
* This class has been derived from the source code of Spring's RowMapperResultSetExtractor.
*
* @author Pino Navato
**/
public class CollectingRowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
private final CollectingRowMapper<T> rowMapper;
private final int rowsExpected;
/**
* Create a new CollectingRowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
**/
public CollectingRowMapperResultSetExtractor(CollectingRowMapper<T> rowMapper) {
this(rowMapper, 0);
}
/**
* Create a new CollectingRowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
* @param rowsExpected the number of expected rows (just used for optimized collection handling)
**/
public CollectingRowMapperResultSetExtractor(CollectingRowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is required");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
}
@Override
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (rowsExpected > 0 ? new ArrayList<>(rowsExpected) : new ArrayList<>());
int rowNum = 0;
if (rs.next()) {
do {
results.add(rowMapper.mapRow(rs, rowNum++));
} while (rowMapper.hasNext());
}
return results;
}
}
以上所有代码都可以作为库重用。您只需子类化AbstractCollectionRowMapper并实现这两个抽象方法。
给出如下查询:
SELECT * FROM INVOICE inv
JOIN INVOICELINES lines
on inv.INVID = lines.INVOICE_ID
order by inv.INVID
可以为两个连接的表只编写一个映射器:
public class InvoiceRowMapper extends AbstractCollectingRowMapper<Invoice> {
@Override
protected Invoice mapRow(ResultSet rs, Invoice partialResult, int rowNum) throws SQLException {
if (partialResult == null) {
partialResult = new Invoice();
partialResult.setInvId(rs.getBigDecimal("INVID"));
partialResult.setInvDate(rs.getDate("INVDATE"));
partialResult.setLines(new ArrayList<>());
}
InvoiceLine line = new InvoiceLine();
line.setOrder(rs.getInt("ORDER"));
line.setPrice(rs.getBigDecimal("PRICE"));
line.setQuantity(rs.getBigDecimal("QUANTITY"));
partialResult.getLines().add(line);
return partialResult;
}
/** Returns true if the current record has the same invoice ID of the previous ones. **/
@Override
protected boolean isRelated(ResultSet rs, Invoice partialResult) throws SQLException {
return partialResult.getInvId().equals(rs.getBigDecimal("INVID"));
}
}
最后一点注意:我在JdbcCursorItemReader的自定义子类中主要使用Spring Batch的CollectingRowMapper和AbstractCollectingRowMapper:我在另一个答案中描述了这个解决方案。使用Spring Batch,您可以在获得下一行之前处理每组相关行,因此可以避免加载可能非常庞大的整个查询结果。
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();
}
});
我有非常复杂的冬眠结构,有许多@manytomy、@ManyToOne、@OneToOne等关联。这些嵌套的集合/对象也有自己的嵌套对象/集合。整个对象被延迟加载。 我试图实现的是,对于一个特定的操作,能够急切地获取整个对象。 我做了一些研究,但我所能找到的只是需要指定每个对象/集合并手动获取的解决方案。 有没有一种方法可以获取整个对象结构,而无需指定需要获取的每个对象?
我有以下课程: 现在我基本上想加载所有的,但在适用时渴望加载条,所以我使用以下查询: 虽然这是可行的,但它似乎为Bar实体生成了一个SELECT N 1问题: 有没有可能告诉hibernate急切地为子集合中的每个元素加载关联,而不使用N 1 SELECTs? 我尝试了以下查询的思路,这显然不起作用,因为它是一个集合: 我还尝试在(b.bar)bar中使用
我的Spring数据存储库被配置为默认@RepositoryRestResources,没有任何自定义。 JPA实体: ..... 我希望House obkect以JSON格式返回,作为Flat对象的嵌入部分,但只获取House的URL /仓库/公寓/442991: 同时,User-Role OneTo多项关系被取好,角色名为: 请求:/repository/users/5 除了关系类型,我不知道
问题内容: 我在多对一元素中声明的域对象上有一个属性。此属性的基本语法如下所示: 现在,我们的想法是让Hibernate不急于获取此属性。它可能为null,因此设置了未找到的忽略。 但是,Hibernate在加载包含此关联的类时,会在加载父类时自行加载实际的类(甚至不是代理)实例。由于某些属性的大小超过1MB,因此它们会占用大量堆空间。 但是,如果将not-found设置为exception(或默
我通过提示检查了它在EclipseLink中的工作情况: 这个链接http://blog.ringerc.id.au/2012/06/jpa2-is-very-inflexible-with-eagerlazy.html暗示通过Hibernate是不可能的,建议手动获取。但是,我无法理解如何通过HQL或标准来实现它,特别是如何获得不在实体上但仅存在于数据库上的child.parent_id列。即避
我有3个模型 1-书: 2-用户 3-书签用户 bookuser迁移: 我试图得到所有的书籍与喜欢的只是当前的用户: 这是我得到的: 我如何访问数据透视表...或者如何得到那样的??我正在尝试,但id不起作用