> {
+/**
+ * JSON type handler for context providers.
+ *
+ * Do not extend MyBatis-Plus {@code AbstractJsonTypeHandler}: its constructor and JSON handler
+ * contract changed in MyBatis-Plus 3.5.6. {@link BaseTypeHandler} is part of MyBatis itself and
+ * keeps this handler compatible with both 3.5.5 and newer MyBatis-Plus releases.
+ */
+public class ContextProviderListTypeHandler extends BaseTypeHandler> {
private static final TypeReference> CONTEXT_PROVIDER_LIST_TYPE = new TypeReference<>() {
};
@Override
- protected List parse(String json) {
+ public void setNonNullParameter(PreparedStatement ps, int i, List parameter,
+ JdbcType jdbcType) throws SQLException {
+ ps.setString(i, JsonUtils.toJsonString(parameter));
+ }
+
+ @Override
+ public List getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ return parseNullable(rs.getString(columnName));
+ }
+
+ @Override
+ public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ return parseNullable(rs.getString(columnIndex));
+ }
+
+ @Override
+ public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ return parseNullable(cs.getString(columnIndex));
+ }
+
+ private List parseNullable(String json) {
if (StringUtils.isBlank(json)) {
- return Collections.emptyList();
+ return null;
}
List providers = JsonUtils.parseObject(json, CONTEXT_PROVIDER_LIST_TYPE);
return providers == null ? Collections.emptyList() : providers;
}
- @Override
- protected String toJson(List obj) {
- return JsonUtils.toJsonString(obj == null ? Collections.emptyList() : obj);
- }
}
diff --git a/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpService.java b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpService.java
new file mode 100644
index 00000000..65e5c8b9
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpService.java
@@ -0,0 +1,7 @@
+package xiaozhi.common.mybatisplus;
+
+/**
+ * Stable project-level alias for the MyBatis-Plus service contract used by 3.5.5 and 3.5.6.
+ */
+public interface MpService extends com.baomidou.mybatisplus.extension.service.IService {
+}
diff --git a/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpServiceImpl.java b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpServiceImpl.java
new file mode 100644
index 00000000..421588fb
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MpServiceImpl.java
@@ -0,0 +1,10 @@
+package xiaozhi.common.mybatisplus;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * Stable project-level base class for the MyBatis-Plus 3.5.5/3.5.6 service implementation.
+ */
+public class MpServiceImpl, T>
+ extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl implements MpService {
+}
diff --git a/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java
new file mode 100644
index 00000000..2350f24e
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-legacy/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java
@@ -0,0 +1,24 @@
+package xiaozhi.common.mybatisplus;
+
+import java.util.Collection;
+import java.util.function.BiConsumer;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
+
+/**
+ * Type-safe access to the MyBatis-Plus 3.5.5/3.5.6 batch callback API.
+ */
+public final class MybatisPlusBatchHelper {
+ private MybatisPlusBatchHelper() {
+ }
+
+ @SuppressWarnings("deprecation")
+ public static boolean executeBatch(SqlSessionFactory sqlSessionFactory, Log log, Collection entities,
+ int batchSize, BiConsumer operation) {
+ return SqlHelper.executeBatch(sqlSessionFactory, log, entities, batchSize, operation);
+ }
+}
diff --git a/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpService.java b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpService.java
new file mode 100644
index 00000000..23bfeca6
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpService.java
@@ -0,0 +1,7 @@
+package xiaozhi.common.mybatisplus;
+
+/**
+ * Stable project-level alias for the MyBatis-Plus 3.5.17 Spring service contract.
+ */
+public interface MpService extends com.baomidou.mybatisplus.spring.service.IService {
+}
diff --git a/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpServiceImpl.java b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpServiceImpl.java
new file mode 100644
index 00000000..fd1fb2f6
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MpServiceImpl.java
@@ -0,0 +1,10 @@
+package xiaozhi.common.mybatisplus;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * Stable project-level base class for the MyBatis-Plus 3.5.17 Spring service implementation.
+ */
+public class MpServiceImpl, T>
+ extends com.baomidou.mybatisplus.spring.service.impl.ServiceImpl implements MpService {
+}
diff --git a/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java
new file mode 100644
index 00000000..690fee97
--- /dev/null
+++ b/main/manager-api/src/mybatis-plus-modern/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelper.java
@@ -0,0 +1,27 @@
+package xiaozhi.common.mybatisplus;
+
+import java.util.Collection;
+import java.util.function.BiConsumer;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
+
+/**
+ * Type-safe access to the MyBatis-Plus 3.5.17 batch callback API.
+ */
+public final class MybatisPlusBatchHelper {
+ private MybatisPlusBatchHelper() {
+ }
+
+ @SuppressWarnings("deprecation")
+ public static boolean executeBatch(SqlSessionFactory sqlSessionFactory, Log log, Collection entities,
+ int batchSize, BiConsumer operation) {
+ return SqlHelper.executeBatch(sqlSessionFactory, log, entities, batchSize, (sqlSession, entity) -> {
+ operation.accept(sqlSession, entity);
+ return 0;
+ });
+ }
+}
diff --git a/main/manager-api/src/test/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelperTest.java b/main/manager-api/src/test/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelperTest.java
new file mode 100644
index 00000000..692d56c7
--- /dev/null
+++ b/main/manager-api/src/test/java/xiaozhi/common/mybatisplus/MybatisPlusBatchHelperTest.java
@@ -0,0 +1,47 @@
+package xiaozhi.common.mybatisplus;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.junit.jupiter.api.Test;
+
+class MybatisPlusBatchHelperTest {
+
+ @Test
+ void executesBatchThroughSelectedVersionAdapter() {
+ SqlSessionFactory sqlSessionFactory = mock(SqlSessionFactory.class);
+ SqlSession sqlSession = mock(SqlSession.class);
+ Log log = mock(Log.class);
+ when(sqlSessionFactory.openSession(ExecutorType.BATCH)).thenReturn(sqlSession);
+
+ BatchResult batchResult = new BatchResult(mock(MappedStatement.class), "INSERT");
+ batchResult.setUpdateCounts(new int[] { 1, 1 });
+ when(sqlSession.flushStatements()).thenReturn(List.of(batchResult));
+
+ List processed = new ArrayList<>();
+ boolean result = MybatisPlusBatchHelper.executeBatch(sqlSessionFactory, log, List.of("first", "second"), 2,
+ (session, entity) -> {
+ assertSame(sqlSession, session);
+ processed.add(entity);
+ });
+
+ assertTrue(result);
+ assertEquals(List.of("first", "second"), processed);
+ verify(sqlSession).flushStatements();
+ verify(sqlSession).commit(true);
+ verify(sqlSession).close();
+ }
+}
diff --git a/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java b/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java
index 78fd05b9..c822358c 100644
--- a/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java
+++ b/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java
@@ -1096,13 +1096,13 @@ class AgentSnapshotServiceImplTest {
when(snapshotService.getCurrentVersionNo(agentId)).thenReturn(0);
when(contextProviderService.getByAgentId(agentId)).thenReturn(null);
when(correctWordFileService.getAgentCorrectWordFileIds(agentId)).thenReturn(List.of());
- when(agentDao.updateById(any())).thenReturn(1);
+ when(agentDao.updateById(any(AgentEntity.class))).thenReturn(1);
service.updateAgentById(agentId, update);
InOrder inOrder = inOrder(agentDao, snapshotService);
inOrder.verify(snapshotService).createSnapshot(agentId, "initial");
- inOrder.verify(agentDao).updateById(argThat(agent -> "new-name".equals(agent.getAgentName())));
+ inOrder.verify(agentDao).updateById(argThat((AgentEntity agent) -> "new-name".equals(agent.getAgentName())));
inOrder.verify(snapshotService).createSnapshot(agentId, "config");
}
@@ -1130,13 +1130,13 @@ class AgentSnapshotServiceImplTest {
when(snapshotService.getCurrentVersionNo(agentId)).thenReturn(4);
when(contextProviderService.getByAgentId(agentId)).thenReturn(null);
when(correctWordFileService.getAgentCorrectWordFileIds(agentId)).thenReturn(List.of());
- when(agentDao.updateById(any())).thenReturn(1);
+ when(agentDao.updateById(any(AgentEntity.class))).thenReturn(1);
service.updateAgentById(agentId, update);
InOrder inOrder = inOrder(agentDao, snapshotService);
inOrder.verify(snapshotService).createSnapshot(agentId, "current");
- inOrder.verify(agentDao).updateById(argThat(agent -> "new-name".equals(agent.getAgentName())));
+ inOrder.verify(agentDao).updateById(argThat((AgentEntity agent) -> "new-name".equals(agent.getAgentName())));
inOrder.verify(snapshotService).createSnapshot(agentId, "config");
}
@@ -1160,7 +1160,7 @@ class AgentSnapshotServiceImplTest {
when(templateService.getDefaultTemplate()).thenReturn(template);
when(timbreService.getDefaultLanguageById("TTS_EdgeTTS0001")).thenReturn("普通话");
- when(agentDao.insert(any())).thenReturn(1);
+ when(agentDao.insert(any(AgentEntity.class))).thenReturn(1);
AgentCreateDTO dto = new AgentCreateDTO();
dto.setAgentName("test123");
@@ -1168,7 +1168,7 @@ class AgentSnapshotServiceImplTest {
String agentId = service.createAgent(dto);
InOrder inOrder = inOrder(agentDao, pluginMappingService, snapshotService);
- inOrder.verify(agentDao).insert(argThat(agent -> "test123".equals(agent.getAgentName())
+ inOrder.verify(agentDao).insert(argThat((AgentEntity agent) -> "test123".equals(agent.getAgentName())
&& "普通话".equals(agent.getTtsLanguage())
&& "".equals(agent.getSummaryMemory())
&& Integer.valueOf(0).equals(agent.getChatHistoryConf())));
@@ -1834,8 +1834,8 @@ class AgentSnapshotServiceImplTest {
assertTrue(error.getCause() instanceof RenException);
assertEquals("快照引用的标签已被删除,无法恢复,请先重新创建或选择标签", error.getCause().getMessage());
- verify(tagDao, never()).updateById(any());
- verify(tagDao, never()).insert(any());
+ verify(tagDao, never()).updateById(any(AgentTagEntity.class));
+ verify(tagDao, never()).insert(any(AgentTagEntity.class));
}
private AgentInfoVO snapshotAgentInfo(String agentId, Long userId, String agentName, String summaryMemory) {
diff --git a/main/manager-api/src/test/java/xiaozhi/modules/agent/typehandler/ContextProviderListTypeHandlerTest.java b/main/manager-api/src/test/java/xiaozhi/modules/agent/typehandler/ContextProviderListTypeHandlerTest.java
index e1d0e6c4..95c9b154 100644
--- a/main/manager-api/src/test/java/xiaozhi/modules/agent/typehandler/ContextProviderListTypeHandlerTest.java
+++ b/main/manager-api/src/test/java/xiaozhi/modules/agent/typehandler/ContextProviderListTypeHandlerTest.java
@@ -2,36 +2,30 @@ package xiaozhi.modules.agent.typehandler;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.util.List;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.dto.ContextProviderDTO;
class ContextProviderListTypeHandlerTest {
private final ContextProviderListTypeHandler handler = new ContextProviderListTypeHandler();
- @Test
- void parseKeepsContextProviderDtoElementType() {
- List providers = handler
- .parse("[{\"url\":\"https://example.com/context\",\"headers\":{\"Authorization\":\"Bearer token\"}}]");
-
- assertEquals(1, providers.size());
- assertInstanceOf(ContextProviderDTO.class, providers.get(0));
- assertEquals("https://example.com/context", providers.get(0).getUrl());
- assertEquals("Bearer token", providers.get(0).getHeaders().get("Authorization"));
- }
-
- @Test
- void parseBlankJsonAsEmptyList() {
- assertTrue(handler.parse(" ").isEmpty());
- }
-
@Test
void myBatisCanInstantiateHandlerForListField() {
TypeHandler> typeHandler = new TypeHandlerRegistry().getInstance(List.class,
@@ -39,4 +33,41 @@ class ContextProviderListTypeHandlerTest {
assertInstanceOf(ContextProviderListTypeHandler.class, typeHandler);
}
+
+ @Test
+ void resultSetDeserializationKeepsContextProviderDtoElementType() throws Exception {
+ ResultSet resultSet = mock(ResultSet.class);
+ when(resultSet.getString("context_providers"))
+ .thenReturn("[{\"url\":\"https://example.com/context\",\"headers\":{\"Authorization\":\"Bearer token\"}}]");
+
+ List providers = handler.getNullableResult(resultSet, "context_providers");
+
+ assertEquals(1, providers.size());
+ assertInstanceOf(ContextProviderDTO.class, providers.get(0));
+ assertEquals("https://example.com/context", providers.get(0).getUrl());
+ assertEquals("Bearer token", providers.get(0).getHeaders().get("Authorization"));
+ }
+
+ @Test
+ void sqlNullRemainsNull() throws Exception {
+ ResultSet resultSet = mock(ResultSet.class);
+
+ assertNull(handler.getNullableResult(resultSet, 1));
+ }
+
+ @Test
+ void serializesProviderListAsJson() throws Exception {
+ ContextProviderDTO provider = new ContextProviderDTO();
+ provider.setUrl("https://example.com/context");
+ PreparedStatement statement = mock(PreparedStatement.class);
+
+ handler.setNonNullParameter(statement, 1, List.of(provider), null);
+
+ ArgumentCaptor json = ArgumentCaptor.forClass(String.class);
+ verify(statement).setString(eq(1), json.capture());
+ List serialized = JsonUtils.parseObject(json.getValue(), new TypeReference<>() {
+ });
+ assertEquals(1, serialized.size());
+ assertEquals("https://example.com/context", serialized.get(0).getUrl());
+ }
}