54
54
import org .springframework .stereotype .Component ;
55
55
import org .springframework .test .annotation .DirtiesContext ;
56
56
import org .springframework .test .context .junit .jupiter .SpringJUnitConfig ;
57
+ import org .springframework .transaction .annotation .Transactional ;
57
58
58
59
/**
59
60
* {@link org.springframework.kafka.annotation.RetryableTopic} with transaction.
63
64
@ SpringJUnitConfig
64
65
@ DirtiesContext
65
66
@ EmbeddedKafka (topics = { RetryTopicTransactionIntegrationTests .RETRY_TRANSACTION_FIRST_TOPIC ,
66
- RetryTopicTransactionIntegrationTests .RETRY_KAFKA_LISTENER_NESTED_TX_FIRST_TOPIC }, partitions = 1 ,
67
+ RetryTopicTransactionIntegrationTests .RETRY_KAFKA_LISTENER_NESTED_TX_FIRST_TOPIC ,
68
+ RetryTopicTransactionIntegrationTests .RETRY_TRANSACTION_SECOND_TOPIC ,
69
+ RetryTopicTransactionIntegrationTests .RETRY_KAFKA_LISTENER_NESTED_TX_SECOND_TOPIC }, partitions = 1 ,
67
70
brokerProperties = { "transaction.state.log.replication.factor=1" , "transaction.state.log.min.isr=1" })
68
71
public class RetryTopicTransactionIntegrationTests {
69
72
70
73
public final static String RETRY_TRANSACTION_FIRST_TOPIC = "retryTopic1" ;
71
74
72
75
public final static String RETRY_KAFKA_LISTENER_NESTED_TX_FIRST_TOPIC = "retryNestedTxTopic1" ;
73
76
77
+ public final static String RETRY_TRANSACTION_SECOND_TOPIC = "retryTopic2" ;
78
+
79
+ public final static String RETRY_KAFKA_LISTENER_NESTED_TX_SECOND_TOPIC = "retryNestedTxTopic2" ;
80
+
74
81
@ Autowired
75
82
private KafkaTemplate <String , String > kafkaTemplate ;
76
83
77
84
@ Autowired
78
85
private CountDownLatchContainer latchContainer ;
79
86
80
87
@ Test
81
- @ DisplayName ("retry topic not support kafka listener nested kafka transactions" )
88
+ @ DisplayName ("kafka listener nested kafka transactions does not support retryable topic " )
82
89
void shouldRetryableTopicWithKafkaListenerNestedKafkaTransactions () {
83
90
kafkaTemplate .executeInTransaction (t ->
84
91
kafkaTemplate .send (RETRY_TRANSACTION_FIRST_TOPIC , "Testing topic 1" )
85
92
);
86
- assertThat (awaitLatch (latchContainer .countDownLatchFirstRetryable )).isTrue ();
93
+ assertThat (awaitLatch (latchContainer .countDownLatchOneRetryable )).isTrue ();
87
94
assertThat (awaitLatch (latchContainer .countDownLatchDltOne )).isTrue ();
88
95
ConsumerRecord <String , String > consumerRecord =
89
96
kafkaTemplate .receive (RETRY_KAFKA_LISTENER_NESTED_TX_FIRST_TOPIC , 0 , 4 );
@@ -92,9 +99,24 @@ void shouldRetryableTopicWithKafkaListenerNestedKafkaTransactions() {
92
99
assertThat (consumerRecord .offset ()).isEqualTo (4 );
93
100
}
94
101
102
+ @ Test
103
+ @ DisplayName ("kafka listener nested kafka transactions with @Transactional does not support retryable topic" )
104
+ void shouldRetryableTopicWithKafkaListenerNestedKafkaTransactionsAndTransactional () {
105
+ kafkaTemplate .executeInTransaction (t ->
106
+ kafkaTemplate .send (RETRY_TRANSACTION_SECOND_TOPIC , "Testing topic 2" )
107
+ );
108
+ assertThat (awaitLatch (latchContainer .countDownLatchTwoRetryable )).isTrue ();
109
+ assertThat (awaitLatch (latchContainer .countDownLatchDltTwo )).isTrue ();
110
+ ConsumerRecord <String , String > consumerRecord =
111
+ kafkaTemplate .receive (RETRY_KAFKA_LISTENER_NESTED_TX_SECOND_TOPIC , 0 , 4 );
112
+ assertThat (consumerRecord ).isNotNull ();
113
+ assertThat (consumerRecord .value ()).isEqualTo ("message-3" );
114
+ assertThat (consumerRecord .offset ()).isEqualTo (4 );
115
+ }
116
+
95
117
private boolean awaitLatch (CountDownLatch latch ) {
96
118
try {
97
- return latch .await (150 , TimeUnit .SECONDS );
119
+ return latch .await (10 , TimeUnit .SECONDS );
98
120
}
99
121
catch (Exception e ) {
100
122
fail (e .getMessage ());
@@ -103,7 +125,7 @@ private boolean awaitLatch(CountDownLatch latch) {
103
125
}
104
126
105
127
@ Component
106
- static class FirstRetryableKafkaListener {
128
+ static class KafkaListenerWithRetryableAndNoTransactionalAnnotation {
107
129
108
130
@ Autowired
109
131
CountDownLatchContainer countDownLatchContainer ;
@@ -116,7 +138,7 @@ static class FirstRetryableKafkaListener {
116
138
@ RetryableTopic (topicSuffixingStrategy = TopicSuffixingStrategy .SUFFIX_WITH_INDEX_VALUE )
117
139
@ KafkaListener (topics = RetryTopicTransactionIntegrationTests .RETRY_TRANSACTION_FIRST_TOPIC )
118
140
void listen (String in , @ Header (KafkaHeaders .RECEIVED_TOPIC ) String topic ) {
119
- countDownLatchContainer .countDownLatchFirstRetryable .countDown ();
141
+ countDownLatchContainer .countDownLatchOneRetryable .countDown ();
120
142
kafkaTemplate .send (RETRY_KAFKA_LISTENER_NESTED_TX_FIRST_TOPIC , "m-" + ++SEND_MESSAGE_COUNT );
121
143
throw new RuntimeException ("from FirstRetryableKafkaListener" );
122
144
}
@@ -125,14 +147,45 @@ void listen(String in, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
125
147
void dlt (String in , @ Header (KafkaHeaders .RECEIVED_TOPIC ) String topic ) {
126
148
countDownLatchContainer .countDownLatchDltOne .countDown ();
127
149
}
150
+
151
+ }
152
+
153
+ @ Component
154
+ static class KafkaListenerWithRetryableAndTransactionalAnnotation {
155
+
156
+ @ Autowired
157
+ CountDownLatchContainer countDownLatchContainer ;
158
+
159
+ @ Autowired
160
+ KafkaTemplate <String , String > kafkaTemplate ;
161
+
162
+ static int SEND_MESSAGE_COUNT = 0 ;
163
+
164
+ @ RetryableTopic (topicSuffixingStrategy = TopicSuffixingStrategy .SUFFIX_WITH_INDEX_VALUE )
165
+ @ KafkaListener (topics = RetryTopicTransactionIntegrationTests .RETRY_TRANSACTION_SECOND_TOPIC )
166
+ @ Transactional
167
+ void listen (String in , @ Header (KafkaHeaders .RECEIVED_TOPIC ) String topic ) {
168
+ countDownLatchContainer .countDownLatchTwoRetryable .countDown ();
169
+ kafkaTemplate .send (RETRY_KAFKA_LISTENER_NESTED_TX_SECOND_TOPIC , "message-" + ++SEND_MESSAGE_COUNT );
170
+ throw new RuntimeException ("from SecondRetryableKafkaListener" );
171
+ }
172
+
173
+ @ DltHandler
174
+ void dlt (String in , @ Header (KafkaHeaders .RECEIVED_TOPIC ) String topic ) {
175
+ countDownLatchContainer .countDownLatchDltTwo .countDown ();
176
+ }
177
+
128
178
}
129
179
130
180
@ Component
131
181
static class CountDownLatchContainer {
132
182
133
- CountDownLatch countDownLatchFirstRetryable = new CountDownLatch (3 );
183
+ CountDownLatch countDownLatchOneRetryable = new CountDownLatch (3 );
134
184
CountDownLatch countDownLatchDltOne = new CountDownLatch (1 );
135
185
186
+ CountDownLatch countDownLatchTwoRetryable = new CountDownLatch (3 );
187
+ CountDownLatch countDownLatchDltTwo = new CountDownLatch (1 );
188
+
136
189
}
137
190
138
191
@ EnableKafka
@@ -148,8 +201,13 @@ CountDownLatchContainer latchContainer() {
148
201
}
149
202
150
203
@ Bean
151
- FirstRetryableKafkaListener firstRetryableKafkaListener () {
152
- return new FirstRetryableKafkaListener ();
204
+ KafkaListenerWithRetryableAndNoTransactionalAnnotation kafkaListenerWithRetryableAndNoTx () {
205
+ return new KafkaListenerWithRetryableAndNoTransactionalAnnotation ();
206
+ }
207
+
208
+ @ Bean
209
+ KafkaListenerWithRetryableAndTransactionalAnnotation kafkaListenerWithRetryableAndTx () {
210
+ return new KafkaListenerWithRetryableAndTransactionalAnnotation ();
153
211
}
154
212
155
213
@ Bean
0 commit comments