Spring:用于动态查询的通用 RowMapper
Spring: Generic RowMapper for dynamic queries
我正在使用 SpringBatch 从 Oracle 读取并写入 ElasticSearch。
我的代码适用于静态查询。
示例:select emp_id, emp_name from employee_table
我有一个 RowMapper class 将 resultSet 中的值映射到 Employee POJO。
我的要求是
查询将由用户输入。所以查询可能如下
select emp_id, emp_name from employee_table
select cust_id, cust_name, cust_age from customer_table
select door_no, street_name, loc_name, city from address_table
Similar queries
我的问题是
- 有没有办法根据用户的查询动态创建POJO?
- 如果查询像我的情况一样不断变化,RowMapper 概念是否有效?
- 是否有类似通用行映射器的东西?
示例代码将不胜感激。
回答您的问题:
- 有没有一种方法可以根据用户的查询动态创建 POJO - 即使有,我也不确定它会有多大帮助。对于您的用例,我建议只使用
Map
.
- 如果查询不断变化,
RowMapper
概念是否有效 - 如果您使用 Map
,您可以使用列名作为键,使用列值作为值。您应该能够创建一个可以执行此操作的 RowMapper
实现。
- 是否有类似通用的东西
RowMapper
- 有,但它是为 POJO 设计的,因此您需要为此创建自己的东西。
如果您有需要映射到的对象...
考虑使用 RowMapper
的自定义实现为您的 SQL 添加别名以匹配您的对象字段名称,它实际上扩展了 BeanWrapperFieldSetMapper
因此,如果您的 POJO 如下所示:
public class Employee {
private String employeeId;
private String employeeName;
...
// getters and setters
}
那么你的 SQL 可以是这样的:
SELECT emp_id employeeId, emp_name employeeName from employee_table
然后你的包装 RowMapper
看起来像这样:
import org.springframework.jdbc.core.RowMapper
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper
public class BeanWrapperRowMapper<T> extends BeanWrapperFieldSetMapper<T> implements RowMapper<T> {
@Override
public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final FieldSet fs = getFieldSet(rs);
try {
return super.mapFieldSet(fs);
} catch (final BindException e) {
throw new IllegalArgumentException("Could not bind bean to FieldSet", e);
}
}
private FieldSet getFieldSet(final ResultSet rs) throws SQLException {
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
final List<String> tokens = new ArrayList<>();
final List<String> names = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
tokens.add(rs.getString(i));
names.add(metaData.getColumnName(i));
}
return new DefaultFieldSet(tokens.toArray(new String[0]), names.toArray(new String[0]));
}
}
或者...
如果您没有要映射到的任何 POJO,请使用开箱即用的 ColumnMapRowMapper
取回列名称的映射 (Map<String,Object>
)(我们称它们为 COL_A, COL_B, COL_C) 到值。然后,如果您的编写器类似于 JdbcBatchItemWriter
,您可以将命名参数设置为:
INSERT TO ${schema}.TARGET_TABLE (COL_1, COL_2, COL_3) values (:COL_A, :COL_B, :COL_C)
然后您的 ItemSqlParameterSourceProvider
实现可能如下所示:
public class MapItemSqlParameterSourceProvider implements
ItemSqlParameterSourceProvider<Map<String, Object>> {
public SqlParameterSource createSqlParameterSource(Map<String, Object> item) {
return new MapSqlParameterSource(item);
}
}
我使用 Spring 的 ColumnMapRowMapper 找到了解决问题的方法。请从 xml 配置文件中找到一个片段。我没有生成任何 POJO class。我用 Map 管理并将其插入到 ES 中。地图的键名称应与索引中存在的字段名称相匹配。
<step id="slave" xmlns="http://www.springframework.org/schema/batch">
<tasklet>
<chunk reader="pagingItemReader" writer="elasticSearcItemWriter"
processor="itemProcessor" commit-interval="10" />
</tasklet>
</step>
<bean id="pagingItemReader"
class="org.springframework.batch.item.database.JdbcPagingItemReader"
scope="step">
<property name="dataSource" ref="dataSource" />
<property name="queryProvider">
<bean
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="selectClause" value="*******" />
<property name="fromClause" value="*******" />
<property name="whereClause" value="*******" />
<property name="sortKey" value="*******" />
</bean>
</property>
<!-- Inject via the ExecutionContext in rangePartitioner -->
<property name="parameterValues">
<map>
<entry key="fromId" value="#{stepExecutionContext[fromId]}" />
<entry key="toId" value="#{stepExecutionContext[toId]}" />
</map>
</property>
<property name="pageSize" value="10" />
<property name="rowMapper">
<bean class="org.springframework.jdbc.core.ColumnMapRowMapper" />
</property>
</bean>
在我的 elasticSearcItemWriter 里面 class.....
public class ElasticSearchItemWriter<T> extends AbstractItemStreamItemWriter<T>
implements ResourceAwareItemWriterItemStream<T>, InitializingBean {
....
....
....
@Override
public void write(List<? extends T> items) throws Exception {
client = jestClient.getJestClient();
if (items.size() > 0) {
for (Object item : items) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) item;
// Asynch index
Index index = new Index.Builder(map).index(Start.prop.getProperty(Constants.ES_INDEX_NAME))
.type(Start.prop.getProperty(Constants.ES_INDEX_TYPE)).build();
client.executeAsync(index, new JestResultHandler<JestResult>() {
public void failed(Exception ex) {
}
public void completed(JestResult result) {
}
});
}
}
}
.....
....
}
你可以像下面这样简单地做,
SettingsDto settings = SettingsDao.getById(1, new BeanPropertyRowMapper<>(SettingsDto.class));
以通用方式,您可以传递 DTO class,但请注意,您必须使用与 SQL 列相同的名称,或者必须在 SQL 查询中使用别名到 DTO。
@Data
public class SettingsDto {
private int id;
private int retryCount;
private int batchSize;
private int retryPeriod;
private int statusInitialDelay;
}
我的dao方法如下
SettingsDto getById(int id, final RowMapper<OMoneySettingsDto> mapper);
其实现如下,
@Override
public SettingsDto getById(final int id, final RowMapper<OMoneySettingsDto> mapper) {
return new JdbcTemplate(YourDataSource).queryForObject(QUERY_SETTINGS_BY_ID,new Object[]{id}, mapper);
}
SQL 在这里,如下所示,您必须在 DTO
中使用相同的名称
private static final String OMONEY_SETTINGS_BY_ID = "SELECT AS id,retry_count AS retryCount FROM setttings WHERE id = ?";
我正在使用 SpringBatch 从 Oracle 读取并写入 ElasticSearch。
我的代码适用于静态查询。
示例:select emp_id, emp_name from employee_table
我有一个 RowMapper class 将 resultSet 中的值映射到 Employee POJO。
我的要求是
查询将由用户输入。所以查询可能如下
select emp_id, emp_name from employee_table
select cust_id, cust_name, cust_age from customer_table
select door_no, street_name, loc_name, city from address_table
Similar queries
我的问题是
- 有没有办法根据用户的查询动态创建POJO?
- 如果查询像我的情况一样不断变化,RowMapper 概念是否有效?
- 是否有类似通用行映射器的东西?
示例代码将不胜感激。
回答您的问题:
- 有没有一种方法可以根据用户的查询动态创建 POJO - 即使有,我也不确定它会有多大帮助。对于您的用例,我建议只使用
Map
. - 如果查询不断变化,
RowMapper
概念是否有效 - 如果您使用Map
,您可以使用列名作为键,使用列值作为值。您应该能够创建一个可以执行此操作的RowMapper
实现。 - 是否有类似通用的东西
RowMapper
- 有,但它是为 POJO 设计的,因此您需要为此创建自己的东西。
如果您有需要映射到的对象...
考虑使用 RowMapper
的自定义实现为您的 SQL 添加别名以匹配您的对象字段名称,它实际上扩展了 BeanWrapperFieldSetMapper
因此,如果您的 POJO 如下所示:
public class Employee {
private String employeeId;
private String employeeName;
...
// getters and setters
}
那么你的 SQL 可以是这样的:
SELECT emp_id employeeId, emp_name employeeName from employee_table
然后你的包装 RowMapper
看起来像这样:
import org.springframework.jdbc.core.RowMapper
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper
public class BeanWrapperRowMapper<T> extends BeanWrapperFieldSetMapper<T> implements RowMapper<T> {
@Override
public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final FieldSet fs = getFieldSet(rs);
try {
return super.mapFieldSet(fs);
} catch (final BindException e) {
throw new IllegalArgumentException("Could not bind bean to FieldSet", e);
}
}
private FieldSet getFieldSet(final ResultSet rs) throws SQLException {
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
final List<String> tokens = new ArrayList<>();
final List<String> names = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
tokens.add(rs.getString(i));
names.add(metaData.getColumnName(i));
}
return new DefaultFieldSet(tokens.toArray(new String[0]), names.toArray(new String[0]));
}
}
或者...
如果您没有要映射到的任何 POJO,请使用开箱即用的 ColumnMapRowMapper
取回列名称的映射 (Map<String,Object>
)(我们称它们为 COL_A, COL_B, COL_C) 到值。然后,如果您的编写器类似于 JdbcBatchItemWriter
,您可以将命名参数设置为:
INSERT TO ${schema}.TARGET_TABLE (COL_1, COL_2, COL_3) values (:COL_A, :COL_B, :COL_C)
然后您的 ItemSqlParameterSourceProvider
实现可能如下所示:
public class MapItemSqlParameterSourceProvider implements
ItemSqlParameterSourceProvider<Map<String, Object>> {
public SqlParameterSource createSqlParameterSource(Map<String, Object> item) {
return new MapSqlParameterSource(item);
}
}
我使用 Spring 的 ColumnMapRowMapper 找到了解决问题的方法。请从 xml 配置文件中找到一个片段。我没有生成任何 POJO class。我用 Map 管理并将其插入到 ES 中。地图的键名称应与索引中存在的字段名称相匹配。
<step id="slave" xmlns="http://www.springframework.org/schema/batch">
<tasklet>
<chunk reader="pagingItemReader" writer="elasticSearcItemWriter"
processor="itemProcessor" commit-interval="10" />
</tasklet>
</step>
<bean id="pagingItemReader"
class="org.springframework.batch.item.database.JdbcPagingItemReader"
scope="step">
<property name="dataSource" ref="dataSource" />
<property name="queryProvider">
<bean
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="selectClause" value="*******" />
<property name="fromClause" value="*******" />
<property name="whereClause" value="*******" />
<property name="sortKey" value="*******" />
</bean>
</property>
<!-- Inject via the ExecutionContext in rangePartitioner -->
<property name="parameterValues">
<map>
<entry key="fromId" value="#{stepExecutionContext[fromId]}" />
<entry key="toId" value="#{stepExecutionContext[toId]}" />
</map>
</property>
<property name="pageSize" value="10" />
<property name="rowMapper">
<bean class="org.springframework.jdbc.core.ColumnMapRowMapper" />
</property>
</bean>
在我的 elasticSearcItemWriter 里面 class.....
public class ElasticSearchItemWriter<T> extends AbstractItemStreamItemWriter<T>
implements ResourceAwareItemWriterItemStream<T>, InitializingBean {
....
....
....
@Override
public void write(List<? extends T> items) throws Exception {
client = jestClient.getJestClient();
if (items.size() > 0) {
for (Object item : items) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) item;
// Asynch index
Index index = new Index.Builder(map).index(Start.prop.getProperty(Constants.ES_INDEX_NAME))
.type(Start.prop.getProperty(Constants.ES_INDEX_TYPE)).build();
client.executeAsync(index, new JestResultHandler<JestResult>() {
public void failed(Exception ex) {
}
public void completed(JestResult result) {
}
});
}
}
}
.....
....
}
你可以像下面这样简单地做,
SettingsDto settings = SettingsDao.getById(1, new BeanPropertyRowMapper<>(SettingsDto.class));
以通用方式,您可以传递 DTO class,但请注意,您必须使用与 SQL 列相同的名称,或者必须在 SQL 查询中使用别名到 DTO。
@Data
public class SettingsDto {
private int id;
private int retryCount;
private int batchSize;
private int retryPeriod;
private int statusInitialDelay;
}
我的dao方法如下
SettingsDto getById(int id, final RowMapper<OMoneySettingsDto> mapper);
其实现如下,
@Override
public SettingsDto getById(final int id, final RowMapper<OMoneySettingsDto> mapper) {
return new JdbcTemplate(YourDataSource).queryForObject(QUERY_SETTINGS_BY_ID,new Object[]{id}, mapper);
}
SQL 在这里,如下所示,您必须在 DTO
中使用相同的名称private static final String OMONEY_SETTINGS_BY_ID = "SELECT AS id,retry_count AS retryCount FROM setttings WHERE id = ?";