Skip to content

Commit 50879a4

Browse files
authored
bugfix: fix Byte[] type to ensure the correct primary key value. (#7761)
1 parent 6f1e37d commit 50879a4

File tree

4 files changed

+111
-1
lines changed

4 files changed

+111
-1
lines changed

changes/en-us/2.x.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Add changes here for all PR submitted to the 2.x branch.
4949
- [[#7643](https://github.com/apache/incubator-seata/pull/7643)] fix DM transaction rollback not using database auto-increment primary keys
5050
- [[#7747](https://github.com/apache/incubator-seata/pull/7747)] undo log table name dynamic derivation
5151
- [[#7749](https://github.com/apache/incubator-seata/pull/7749)] fix error parsing application/x-www-form-urlencoded requests in Http2HttpHandler
52+
- [[#7761](https://github.com/apache/incubator-seata/pull/7761)] special handling is applied to the Byte[] type to ensure the correct primary key value
5253

5354

5455
### optimize:

changes/zh-cn/2.x.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- [[#7643](https://github.com/apache/incubator-seata/pull/7643)] 修复 DM 事务回滚不使用数据库自动增量主键
5050
- [[#7747](https://github.com/apache/incubator-seata/pull/7747)] 支持undo_log序列名动态推导
5151
- [[#7749](https://github.com/apache/incubator-seata/pull/7749)] 修复 Http2HttpHandler 解析 application/x-www-form-urlencoded 请求失败的问题
52+
- [[#7761](https://github.com/apache/incubator-seata/pull/7761)] 对 Byte[] 类型进行了特殊处理,以确保主键值正确
5253

5354

5455
### optimize:

rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/BaseTransactionalExecutor.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.apache.seata.common.DefaultValues;
2020
import org.apache.seata.common.exception.ShouldNeverHappenException;
21+
import org.apache.seata.common.util.ArrayUtils;
2122
import org.apache.seata.common.util.CollectionUtils;
2223
import org.apache.seata.common.util.IOUtil;
2324
import org.apache.seata.common.util.StringUtils;
@@ -457,7 +458,12 @@ protected String buildLockKey(TableRecords rowsIncludingPK) {
457458
}
458459
Object pkVal = rowMap.get(pkName).getValue();
459460
validPk(String.valueOf(pkVal));
460-
sb.append(pkVal);
461+
// Handle byte[] primary keys properly to avoid using memory address as lock key
462+
if (pkVal instanceof byte[]) {
463+
sb.append(ArrayUtils.toString(pkVal));
464+
} else {
465+
sb.append(pkVal);
466+
}
461467
pkSplitIndex++;
462468
}
463469
rowSequence++;

rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/BaseTransactionalExecutorTest.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,106 @@ public void testBuildLockKeyWithMultiPk() {
171171
when(executor.getTableMeta()).thenReturn(tableMeta);
172172
assertThat(executor.buildLockKey(tableRecords)).isEqualTo(buildLockKeyExpect);
173173
}
174+
175+
@Test
176+
public void testBuildLockKeyWithBinaryPrimaryKey() {
177+
// Test binary (byte[]) primary key handling
178+
String tableName = "test_binary_table";
179+
byte[] binaryPkValue1 = new byte[] {1, 2, 3, 15, -1}; // -1 represents 0xFF in signed byte
180+
byte[] binaryPkValue2 = new byte[] {10, 20, 30};
181+
String pkColumnName = "binary_id";
182+
183+
// Expected: test_binary_table:[1, 2, 3, 15, -1],[10, 20, 30]
184+
// Using ArrayUtils.toString() format instead of memory address like [B@1b57bff9]
185+
// Note: byte is signed in Java, so 0xFF (255) is represented as -1
186+
String expectedLockKey = tableName + ":[1, 2, 3, 15, -1],[10, 20, 30]";
187+
188+
// Mock fields with byte[] values
189+
Field binaryField1 = mock(Field.class);
190+
when(binaryField1.getValue()).thenReturn(binaryPkValue1);
191+
Field binaryField2 = mock(Field.class);
192+
when(binaryField2.getValue()).thenReturn(binaryPkValue2);
193+
194+
List<Map<String, Field>> pkRows = new ArrayList<>();
195+
pkRows.add(Collections.singletonMap(pkColumnName, binaryField1));
196+
pkRows.add(Collections.singletonMap(pkColumnName, binaryField2));
197+
198+
// Mock tableMeta
199+
TableMeta tableMeta = mock(TableMeta.class);
200+
when(tableMeta.getTableName()).thenReturn(tableName);
201+
when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[] {pkColumnName}));
202+
203+
// Mock tableRecords
204+
TableRecords tableRecords = mock(TableRecords.class);
205+
when(tableRecords.getTableMeta()).thenReturn(tableMeta);
206+
when(tableRecords.size()).thenReturn(pkRows.size());
207+
when(tableRecords.pkRows()).thenReturn(pkRows);
208+
209+
// Mock executor
210+
BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class);
211+
when(executor.buildLockKey(tableRecords)).thenCallRealMethod();
212+
when(executor.getTableMeta()).thenReturn(tableMeta);
213+
214+
String actualLockKey = executor.buildLockKey(tableRecords);
215+
216+
// Verify that byte[] is properly converted to readable format
217+
assertThat(actualLockKey).isEqualTo(expectedLockKey);
218+
// Ensure it's not using memory address format
219+
assertThat(actualLockKey).doesNotContain("[B@]");
220+
assertThat(actualLockKey).contains("[1, 2, 3, 15, -1]");
221+
assertThat(actualLockKey).contains("[10, 20, 30]");
222+
}
223+
224+
@Test
225+
public void testBuildLockKeyWithMixedPrimaryKeys() {
226+
// Test mixed primary keys: one regular string and one binary
227+
String tableName = "test_mixed_table";
228+
String stringPkValue = "user123";
229+
byte[] binaryPkValue = new byte[] {16, 32, 48, 64};
230+
String stringPkColumnName = "user_id";
231+
String binaryPkColumnName = "session_id";
232+
233+
// Expected: test_mixed_table:user123_[16, 32, 48, 64]
234+
String expectedLockKey = tableName + ":user123_[16, 32, 48, 64]";
235+
236+
// Mock fields
237+
Field stringField = mock(Field.class);
238+
when(stringField.getValue()).thenReturn(stringPkValue);
239+
Field binaryField = mock(Field.class);
240+
when(binaryField.getValue()).thenReturn(binaryPkValue);
241+
242+
List<Map<String, Field>> pkRows = new ArrayList<>();
243+
Map<String, Field> row = new HashMap<String, Field>() {
244+
{
245+
put(stringPkColumnName, stringField);
246+
put(binaryPkColumnName, binaryField);
247+
}
248+
};
249+
pkRows.add(row);
250+
251+
// Mock tableMeta
252+
TableMeta tableMeta = mock(TableMeta.class);
253+
when(tableMeta.getTableName()).thenReturn(tableName);
254+
when(tableMeta.getPrimaryKeyOnlyName())
255+
.thenReturn(Arrays.asList(new String[] {stringPkColumnName, binaryPkColumnName}));
256+
257+
// Mock tableRecords
258+
TableRecords tableRecords = mock(TableRecords.class);
259+
when(tableRecords.getTableMeta()).thenReturn(tableMeta);
260+
when(tableRecords.size()).thenReturn(pkRows.size());
261+
when(tableRecords.pkRows()).thenReturn(pkRows);
262+
263+
// Mock executor
264+
BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class);
265+
when(executor.buildLockKey(tableRecords)).thenCallRealMethod();
266+
when(executor.getTableMeta()).thenReturn(tableMeta);
267+
268+
String actualLockKey = executor.buildLockKey(tableRecords);
269+
270+
// Verify mixed types are handled correctly
271+
assertThat(actualLockKey).isEqualTo(expectedLockKey);
272+
assertThat(actualLockKey).doesNotContain("[B@]");
273+
assertThat(actualLockKey).contains("user123");
274+
assertThat(actualLockKey).contains("[16, 32, 48, 64]");
275+
}
174276
}

0 commit comments

Comments
 (0)