如何使用Spring Batch无条件地写入每个读取行的多个表

ryoqjall  于 2023-05-16  发布在  Spring
关注(0)|答案(4)|浏览(165)

我在这个论坛上发现了很多使用多个作者的例子。大多数(如果不是全部)答案都集中在CompositeItemWriter和ClassifierItemWriter上。

  • 业务需求 *:从输入文件中读取一行。这一行将包含多个字段(超过50个),这些字段需要写入各自的数据库表(理论上代表不同的类)。
----- claimwriter(write to claim table)
                              /
                             /
claimlineitemprocessor  -----
                             \
                              \
                               ----- pharmacywriter(write to pharmacy table)

我使用了一个字段集Map器来创建表示索赔行的对象(ClaimLine)。大多数字段都是到文件中数据的简单Map,但少数字段需要更改其格式或相关的字段Map逻辑。
基本项编写器代码如下所示:

@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public ItemWriter<ClaimLine> writer() {
    CompositeItemWriter<ClaimLine> cWriter = new CompositeItemWriter<ClaimLine>();

    JdbcBatchItemWriter claimWriter = new JdbcBatchItemWriter();
    claimWriter.setItemSqlParameterSourceProvider(new ClaimItemSqlParameterSourceProvider());
    claimWriter.setSql( // would like to insert into pharmacy table);
    claimWriter.setDataSource(dataSource);
    claimWriter.afterPropertiesSet();

    JdbcBatchItemWriter pharmacyWriter = new JdbcBatchItemWriter();
    pharmacyWriter.setItemSqlParameterSourceProvider(new PharmacyItemSqlParameterSourceProvider());
    pharmacyWriter.setSql( // would like to insert into pharmacy table);
    pharmacyWriter.setDataSource(dataSource);
    pharmacyWriter.afterPropertiesSet();

    List<ItemWriter<? super ClaimLine>> mWriter = new ArrayList<ItemWriter<? super ClaimLine>>();
    mWriter.add(claimWriter); 
    mWriter.add(pharmacyWriter);
    cWriter.setDelegates(mWriter);

    // other code

    return cWriter;
};

在创建自定义源提供程序时,它们中的每一个似乎都期望,因为这是已经Map到输入行的类,并且包含我想要发送到相应表的值。
这基本上就是我现在认为我不能使用CompositeItemWriter的地方,因为我试图将一个对象转换为两个不同的对象。而ClassifierCompositeItemWriter的工作方式就像路由器一样,沿着特定于某个条件的路径发送,这不是我想做的。
作为参考,我尝试用Spring Integration做类似的事情,也遇到了类似的障碍。
任何帮助都很感激。

a2mppw5e

a2mppw5e1#

我相信@Hansjoerg和@Luca的评论对这个问题提供了有价值的回答,并且在研究之前和研究期间进行了研究。
我能够通过继续使用ItemSqlParameterSourceProvider解决此问题,代码如下。当我最初探索如何使用这个类及其方法时,我的想法是我仍然只在ClaimLine类上操作。
真正发生的是方法从编写器接收类,并且您正在设置使用setSQL(Stringsql)设置的SQL语句的值。通过使用ItemSqlParameterSourceProvider,您将在SQL中为put语句使用命名参数。下面的代码仅显示索赔的代码。药房也是类似的。

public class ClaimItemSqlParameterSourceProvider implements ItemSqlParameterSourceProvider<ClaimLine> {

    @SuppressWarnings({ "serial"})
    @Override
    public SqlParameterSource createSqlParameterSource(final ClaimLine item) {
        return new MapSqlParameterSource(new HashMap<String, Object>() {
            {
                put("rxclaimid", item.getRxClaimID());
                ...
                // many more
            }
        });
    }
}

自定义项编写器可能也解决了这个问题,但似乎需要更多的代码来支持它。最后,对于这种情况,使用ItemPreparedStatementSetter或ItemSqlParameterSourceProvider都可以。我们选择后者的主要原因是因为参数被明确地命名,而不是通过索引值(1、2、3等)访问参数值并使用“?“中。

zysjyyx4

zysjyyx42#

你可以使用chain来写多个表,

<int:chain input-channel="processTransactionChannel"
		output-channel="processedItems">
		<int:header-enricher>
			<int:header name="savePayload" expression="payload" />
		</int:header-enricher>

		<int-jpa:updating-outbound-gateway
			auto-startup="true"
			native-query="insert into  TableOne values( :transactionStatus ,bank_Reference_Number = :bankReferenceNumber )"
			entity-manager="entityManager" 
			use-payload-as-parameter-source="false">
			<int-jpa:transactional />
			<int-jpa:parameter name="transactionStatus"
				expression="payload['transactionStatus']" />
			<int-jpa:parameter name="bankReferenceNumber"
				expression="payload['bankReferenceNumber']" />

		</int-jpa:updating-outbound-gateway>

		<int:transformer expression="headers.savePayload" />

		
		<int-jpa:updating-outbound-gateway
			native-query="insert 
 						into PARTNER_RESPONSE_DETAILS(PARTNER_ID,BANK_REFERENCE_NUMBER,REQUEST_STRING,RESPONSE_STRING)  
  						values (:partnerId,:bankRefNumber,:requestString,:responseString)"
			entity-manager="entityManager">
			<int-jpa:transactional />
			<int-jpa:parameter name="partnerId" expression="payload['partnerId']" />
			<int-jpa:parameter name="bankRefNumber" expression="payload['bankRefNumber']" />
			<int-jpa:parameter name="requestString" expression="payload['requestString']" />
			<int-jpa:parameter name="responseString"
				expression="payload['responseString']" />
			<int-jpa:parameter name="transactionStatus"
				expression="payload['transactionStatus']" />
			<int-jpa:parameter name="bankReferenceNumber"
				expression="payload['bankReferenceNumber']" />

		</int-jpa:updating-outbound-gateway>

		<int:transformer expression="headers.savePayload" />


	</int:chain>

这段代码更新2表,它是为我工作。

gopyfrb3

gopyfrb33#

也许我的答案是你的例外。我也看到了同样的问题,我用classifierCompositeWriter解决了它。我在我的项目中定义了我的分类器,正如你所看到的,你可以决定你应该在你的逻辑中使用哪个作家。例如,在我的逻辑中

if(baseEntity instanceof Product){ 
    return productItemWriter;
}else {
    return otherItemWriter;
}

真幸运。

m4pnthwp

m4pnthwp4#

不清楚你在这里想说什么:
在创建自定义源提供程序时,它们中的每一个似乎都期望,因为这是已经Map到输入行的类,并且包含我想要发送到相应表的值。
但是我从这个问题中得到的是,你想从一个有N列的文件中读取一行,然后把它分解成两个或多个表。在此之前,您需要更改某些列的格式并派生其他一些列。
这应该可以通过CompositeItemWriter实现。
1.从文件中读取行
1.在Map器中,创建类对象并填充字段。在这里,您还可以更改行中任何字段的格式,然后将其分配给对象属性。同样,你可以根据读取行中的其他属性派生另一个属性,并将其赋值给对象中的属性!因此,您感兴趣的每个字段都有一个属性。如果需要引用原始数据,可以为原始字段保留一个属性,为重新格式化的字段保留一个属性。
1.这个模型示例将被传递给处理器,您可以在那里处理数据。事实上,这是另一个可以进行转换的地方,即更改格式并生成派生列!
1.为要写入的每个表创建一个writer。为每个编写器分配一个数据源。然后将此编写器列表分配给CompositeItemWriter。请注意,只有当两个编写器连接到相同的数据源并且将相同的数据源示例分配给步骤的事务管理器时,此设置才有效。否则回滚会导致数据完整性问题。
1.为每个相应的编写器创建ItemSqlParameterSourceProvider。具有50个或任意数量属性的相同数据对象被传递给每个提供者。但每个提供程序返回与要写入的表对应的不同对象。
1.完成!这应该从文件中读取行,处理每个行项目以按照您的意愿进行转换,然后写入同一事务中的多个表。提交和回滚是一致的。
读取每行的模型:

class LineModel
{
   public String tab1Prop1;
   public boolean tab1Prop2;
   public int tab1PropN;
   public String tab1ReformattedProp1;
   public String tab2DerivedFieldFromProp2AndN;
   public String tab2Prop3;
}

在线Map器中,您可以填充上面的模型,将线字段Map到 prop ,还可以重新格式化和派生其他字段。或者在处理器中进行重新格式化和派生,因为这是应该用于转换的。
或者,您可以将上面的模型与两个模型组合,每个模型对应一个表:

class LineModel
{
   public Tab1 tab1Cols;
   public Tab2 tab2Cols;
}

class Tab1
{
   public String tab1Prop1;
   public boolean tab1Prop2;
   public int tab1PropN;
   public String tab1ReformattedProp1;
}

class Tab2
{
   public String tab2DerivedFieldFromProp2AndN;
   public String tab2Prop3;
}

现在,在每个编写器的ItemSqlParameterSourceProvider中,根据您正在处理的编写器,返回lineModelObj.tab1Cols或lineModelObj.tab2Cols。此时,您将知道哪个模型的哪个属性将被写入哪个表,因此您可以相应地在编写器中创建SQL。
就是这样!它应该做你的工作!

相关问题