Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/en-us/2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#7643](https://github.com/apache/incubator-seata/pull/7643)] fix DM transaction rollback not using database auto-increment primary keys
- [[#7747](https://github.com/apache/incubator-seata/pull/7747)] undo log table name dynamic derivation
- [[#7749](https://github.com/apache/incubator-seata/pull/7749)] fix error parsing application/x-www-form-urlencoded requests in Http2HttpHandler
- [[#7761](https://github.com/apache/incubator-seata/pull/7761)] special handling is applied to the Byte[] type to ensure the correct primary key value


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


### optimize:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.apache.seata.common.DefaultValues;
import org.apache.seata.common.exception.ShouldNeverHappenException;
import org.apache.seata.common.util.ArrayUtils;
import org.apache.seata.common.util.CollectionUtils;
import org.apache.seata.common.util.IOUtil;
import org.apache.seata.common.util.StringUtils;
Expand Down Expand Up @@ -457,7 +458,12 @@ protected String buildLockKey(TableRecords rowsIncludingPK) {
}
Object pkVal = rowMap.get(pkName).getValue();
validPk(String.valueOf(pkVal));
sb.append(pkVal);
// Handle byte[] primary keys properly to avoid using memory address as lock key
if (pkVal instanceof byte[]) {
sb.append(ArrayUtils.toString(pkVal));
} else {
sb.append(pkVal);
}
pkSplitIndex++;
}
rowSequence++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,106 @@ public void testBuildLockKeyWithMultiPk() {
when(executor.getTableMeta()).thenReturn(tableMeta);
assertThat(executor.buildLockKey(tableRecords)).isEqualTo(buildLockKeyExpect);
}

@Test
public void testBuildLockKeyWithBinaryPrimaryKey() {
// Test binary (byte[]) primary key handling
String tableName = "test_binary_table";
byte[] binaryPkValue1 = new byte[] {1, 2, 3, 15, -1}; // -1 represents 0xFF in signed byte
byte[] binaryPkValue2 = new byte[] {10, 20, 30};
String pkColumnName = "binary_id";

// Expected: test_binary_table:[1, 2, 3, 15, -1],[10, 20, 30]
// Using ArrayUtils.toString() format instead of memory address like [B@1b57bff9]
// Note: byte is signed in Java, so 0xFF (255) is represented as -1
String expectedLockKey = tableName + ":[1, 2, 3, 15, -1],[10, 20, 30]";

// Mock fields with byte[] values
Field binaryField1 = mock(Field.class);
when(binaryField1.getValue()).thenReturn(binaryPkValue1);
Field binaryField2 = mock(Field.class);
when(binaryField2.getValue()).thenReturn(binaryPkValue2);

List<Map<String, Field>> pkRows = new ArrayList<>();
pkRows.add(Collections.singletonMap(pkColumnName, binaryField1));
pkRows.add(Collections.singletonMap(pkColumnName, binaryField2));

// Mock tableMeta
TableMeta tableMeta = mock(TableMeta.class);
when(tableMeta.getTableName()).thenReturn(tableName);
when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[] {pkColumnName}));

// Mock tableRecords
TableRecords tableRecords = mock(TableRecords.class);
when(tableRecords.getTableMeta()).thenReturn(tableMeta);
when(tableRecords.size()).thenReturn(pkRows.size());
when(tableRecords.pkRows()).thenReturn(pkRows);

// Mock executor
BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class);
when(executor.buildLockKey(tableRecords)).thenCallRealMethod();
when(executor.getTableMeta()).thenReturn(tableMeta);

String actualLockKey = executor.buildLockKey(tableRecords);

// Verify that byte[] is properly converted to readable format
assertThat(actualLockKey).isEqualTo(expectedLockKey);
// Ensure it's not using memory address format
assertThat(actualLockKey).doesNotContain("[B@]");
assertThat(actualLockKey).contains("[1, 2, 3, 15, -1]");
assertThat(actualLockKey).contains("[10, 20, 30]");
}

@Test
public void testBuildLockKeyWithMixedPrimaryKeys() {
// Test mixed primary keys: one regular string and one binary
String tableName = "test_mixed_table";
String stringPkValue = "user123";
byte[] binaryPkValue = new byte[] {16, 32, 48, 64};
String stringPkColumnName = "user_id";
String binaryPkColumnName = "session_id";

// Expected: test_mixed_table:user123_[16, 32, 48, 64]
String expectedLockKey = tableName + ":user123_[16, 32, 48, 64]";

// Mock fields
Field stringField = mock(Field.class);
when(stringField.getValue()).thenReturn(stringPkValue);
Field binaryField = mock(Field.class);
when(binaryField.getValue()).thenReturn(binaryPkValue);

List<Map<String, Field>> pkRows = new ArrayList<>();
Map<String, Field> row = new HashMap<String, Field>() {
{
put(stringPkColumnName, stringField);
put(binaryPkColumnName, binaryField);
}
};
pkRows.add(row);

// Mock tableMeta
TableMeta tableMeta = mock(TableMeta.class);
when(tableMeta.getTableName()).thenReturn(tableName);
when(tableMeta.getPrimaryKeyOnlyName())
.thenReturn(Arrays.asList(new String[] {stringPkColumnName, binaryPkColumnName}));

// Mock tableRecords
TableRecords tableRecords = mock(TableRecords.class);
when(tableRecords.getTableMeta()).thenReturn(tableMeta);
when(tableRecords.size()).thenReturn(pkRows.size());
when(tableRecords.pkRows()).thenReturn(pkRows);

// Mock executor
BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class);
when(executor.buildLockKey(tableRecords)).thenCallRealMethod();
when(executor.getTableMeta()).thenReturn(tableMeta);

String actualLockKey = executor.buildLockKey(tableRecords);

// Verify mixed types are handled correctly
assertThat(actualLockKey).isEqualTo(expectedLockKey);
assertThat(actualLockKey).doesNotContain("[B@]");
assertThat(actualLockKey).contains("user123");
assertThat(actualLockKey).contains("[16, 32, 48, 64]");
}
}
Loading