This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 6f5535c57 fix(nameserver): make registry replacements atomic (#7231)
6f5535c57 is described below
commit 6f5535c576f6054d14fc6616a6b5216dee0fd552
Author: aias00 <[email protected]>
AuthorDate: Mon Oct 26 16:35:28 2026 +0800
fix(nameserver): make registry replacements atomic (#7231)
* fix(nameserver): make registry replacements atomic
---
.../nameserver/NameserverRegistryService.java | 2 +
.../NameserverRegistryTransactionTest.java | 130 +++++++++++++++++++++
2 files changed, 132 insertions(+)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
index c964291ae..01546ba86 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
@@ -24,6 +24,7 @@ import
org.apache.rocketmq.studio.persistence.entity.RmqNameserver;
import org.apache.rocketmq.studio.persistence.mapper.RmqNameserverMapper;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -75,6 +76,7 @@ public class NameserverRegistryService {
return toVO(stored);
}
+ @Transactional
public NameserverRegistryVO update(UpdateNameserverRegistryDTO command) {
RmqNameserver entity = nameserverMapper.selectById(command.getId());
if (entity == null) {
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryTransactionTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryTransactionTest.java
new file mode 100644
index 000000000..6e98ba90d
--- /dev/null
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryTransactionTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.cluster.nameserver;
+
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqNameserver;
+import org.apache.rocketmq.studio.persistence.mapper.RmqNameserverMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+
+@SpringBootTest(properties = {
+
"spring.datasource.url=jdbc:h2:mem:nameserver_registry_atomic;MODE=MySQL;DB_CLOSE_DELAY=-1;"
+ + "DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=VALUE",
+ "studio.auth.login-required=false"
+})
+@ActiveProfiles("dev")
+class NameserverRegistryTransactionTest {
+
+ @Autowired
+ private NameserverRegistryService service;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @MockitoSpyBean
+ private RmqNameserverMapper nameserverMapper;
+
+ @Test
+ void failedOptionalColumnCleanupShouldRollBackTheRegistryUpdateTest() {
+ RmqNameserver original = insertEntry();
+ String updatedName = "renamed-" + UUID.randomUUID();
+ AtomicBoolean cleanupReached = new AtomicBoolean();
+ try {
+ doAnswer(invocation -> {
+ assertThat(jdbcTemplate.queryForObject(
+ "SELECT name FROM rmq_nameserver WHERE id = ?",
String.class, original.getId()))
+ .isEqualTo(updatedName);
+ cleanupReached.set(true);
+ throw new IllegalStateException("optional column cleanup
failed");
+ }).when(nameserverMapper).update(isNull(),
any(UpdateWrapper.class));
+
+ assertThatThrownBy(() ->
service.update(updateCommand(original.getId(), updatedName)))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("optional column cleanup failed");
+
+ assertThat(cleanupReached).isTrue();
+ RmqNameserver persisted =
nameserverMapper.selectById(original.getId());
+ assertThat(persisted.getName()).isEqualTo(original.getName());
+
assertThat(persisted.getNamesrvAddr()).isEqualTo(original.getNamesrvAddr());
+
assertThat(persisted.getK8sNamespace()).isEqualTo(original.getK8sNamespace());
+ assertThat(persisted.getK8sId()).isEqualTo(original.getK8sId());
+
assertThat(persisted.getDescription()).isEqualTo(original.getDescription());
+ } finally {
+ deleteEntry(original.getId());
+ }
+ }
+
+ @Test
+ void
successfulUpdateShouldCommitTheReplacementAndClearOptionalColumnsTest() {
+ RmqNameserver original = insertEntry();
+ String updatedName = "renamed-" + UUID.randomUUID();
+ try {
+ NameserverRegistryVO result =
service.update(updateCommand(original.getId(), updatedName));
+
+ RmqNameserver persisted =
nameserverMapper.selectById(original.getId());
+ assertThat(persisted.getName()).isEqualTo(updatedName);
+
assertThat(persisted.getNamesrvAddr()).isEqualTo("new-nameserver:9876");
+ assertThat(persisted.getK8sNamespace()).isNull();
+ assertThat(persisted.getK8sId()).isNull();
+ assertThat(persisted.getDescription()).isNull();
+ assertThat(result.getName()).isEqualTo(persisted.getName());
+
assertThat(result.getNamesrvAddr()).isEqualTo(persisted.getNamesrvAddr());
+ assertThat(result.getK8sNamespace()).isNull();
+ assertThat(result.getK8sId()).isNull();
+ assertThat(result.getDescription()).isNull();
+ } finally {
+ deleteEntry(original.getId());
+ }
+ }
+
+ private RmqNameserver insertEntry() {
+ RmqNameserver entry = new RmqNameserver();
+ entry.setName("original-" + UUID.randomUUID());
+ entry.setNamesrvAddr("old-nameserver:9876");
+ entry.setK8sNamespace("old-namespace");
+ entry.setK8sId("old-k8s");
+ entry.setDescription("old description");
+ nameserverMapper.insert(entry);
+ return entry;
+ }
+
+ private UpdateNameserverRegistryDTO updateCommand(Long id, String name) {
+ return UpdateNameserverRegistryDTO.builder()
+ .id(id)
+ .name(name)
+ .namesrvAddr("new-nameserver:9876")
+ .build();
+ }
+
+ private void deleteEntry(Long id) {
+ jdbcTemplate.update("DELETE FROM rmq_nameserver WHERE id = ?", id);
+ }
+}