|
1 | 1 | package vip.mate; |
2 | 2 |
|
| 3 | +import com.baomidou.mybatisplus.annotation.DbType; |
3 | 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
4 | 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; |
| 6 | +import jakarta.annotation.PostConstruct; |
| 7 | +import lombok.extern.slf4j.Slf4j; |
5 | 8 | import org.mybatis.spring.annotation.MapperScan; |
| 9 | +import org.springframework.beans.factory.annotation.Autowired; |
6 | 10 | import org.springframework.boot.SpringApplication; |
7 | 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 12 | +import org.springframework.boot.context.event.ApplicationReadyEvent; |
8 | 13 | import org.springframework.context.annotation.Bean; |
| 14 | +import org.springframework.context.event.EventListener; |
9 | 15 | import org.springframework.scheduling.annotation.EnableScheduling; |
10 | 16 |
|
| 17 | +import javax.sql.DataSource; |
| 18 | +import java.sql.Connection; |
| 19 | + |
11 | 20 | /** |
12 | 21 | * MateClaw - Personal AI Assistant |
13 | 22 | * Powered by Spring AI Alibaba |
14 | 23 | * |
15 | 24 | * @author MateClaw Team |
16 | 25 | */ |
| 26 | +@Slf4j |
17 | 27 | @SpringBootApplication(exclude = { |
18 | 28 | // Disable Spring AI MCP Client auto-configuration (lifecycle owned by McpClientManager). |
19 | 29 | org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration.class, |
|
33 | 43 | @MapperScan("vip.mate.**.repository") |
34 | 44 | public class MateClawApplication { |
35 | 45 |
|
| 46 | + @Autowired |
| 47 | + private DataSource dataSource; |
| 48 | + |
| 49 | + /** Cached DbType for the PaginationInnerInterceptor. */ |
| 50 | + private volatile DbType resolvedDbType; |
| 51 | + |
36 | 52 | public static void main(String[] args) { |
37 | 53 | SpringApplication.run(MateClawApplication.class, args); |
38 | 54 | } |
39 | 55 |
|
| 56 | + /** |
| 57 | + * Detect the actual database type from the live DataSource so the |
| 58 | + * {@link PaginationInnerInterceptor} always uses the correct dialect, |
| 59 | + * even when the JDBC URL is wrapped by a proxy (HikariCP, P6Spy, etc.). |
| 60 | + * |
| 61 | + * <p>DbType is cached after the first successful detection; a failure |
| 62 | + * falls back to the value set in {@code mybatis-plus.global-config.db-config.db-type}, |
| 63 | + * or eventually to {@link DbType#MYSQL} — but by then the connection |
| 64 | + * pool would already have failed. |
| 65 | + */ |
| 66 | + @PostConstruct |
| 67 | + void detectDbType() { |
| 68 | + try (Connection conn = dataSource.getConnection()) { |
| 69 | + String productName = conn.getMetaData().getDatabaseProductName().toLowerCase(); |
| 70 | + if (productName.contains("kingbase")) { |
| 71 | + resolvedDbType = DbType.KINGBASE_ES; |
| 72 | + } else if (productName.contains("postgresql")) { |
| 73 | + resolvedDbType = DbType.POSTGRE_SQL; |
| 74 | + } else if (productName.contains("mysql") || productName.contains("mariadb")) { |
| 75 | + resolvedDbType = DbType.MYSQL; |
| 76 | + } else if (productName.contains("h2")) { |
| 77 | + resolvedDbType = DbType.H2; |
| 78 | + } else { |
| 79 | + // Let the PaginationInnerInterceptor auto-detect at query time |
| 80 | + resolvedDbType = null; |
| 81 | + } |
| 82 | + if (resolvedDbType != null) { |
| 83 | + log.info("Detected database type: {} (product={})", resolvedDbType, productName); |
| 84 | + } |
| 85 | + } catch (Exception e) { |
| 86 | + log.warn("Could not detect database type — PaginationInnerInterceptor will auto-detect on first query: {}", |
| 87 | + e.getMessage()); |
| 88 | + } |
| 89 | + } |
| 90 | + |
40 | 91 | /** |
41 | 92 | * MyBatis Plus pagination plugin. |
42 | 93 | * |
43 | | - * <p>DbType is auto-detected from the JDBC connection at runtime rather |
44 | | - * than hardcoded. Hardcoding H2 here meant the MySQL deployment used |
45 | | - * the H2 dialect for the count query, which silently returned 0 — |
46 | | - * frontends saw records but total=0 and couldn't paginate (RFC-042 P0). |
| 94 | + * <p>When {@code resolvedDbType} is available the interceptor uses it directly; |
| 95 | + * otherwise it falls back to JDBC-URL auto-detection, which works for |
| 96 | + * {@code jdbc:kingbase8://} but not for proxied DataSources (RFC-042 P0). |
47 | 97 | */ |
48 | 98 | @Bean |
49 | 99 | public MybatisPlusInterceptor mybatisPlusInterceptor() { |
50 | 100 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); |
51 | | - interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); |
| 101 | + PaginationInnerInterceptor pagination = resolvedDbType != null |
| 102 | + ? new PaginationInnerInterceptor(resolvedDbType) |
| 103 | + : new PaginationInnerInterceptor(); |
| 104 | + interceptor.addInnerInterceptor(pagination); |
52 | 105 | return interceptor; |
53 | 106 | } |
| 107 | + |
| 108 | + /** |
| 109 | + * Print a clear "READY" banner after all post-startup initialization, |
| 110 | + * so operators can tell at a glance when the application is ready to serve. |
| 111 | + */ |
| 112 | + @EventListener(ApplicationReadyEvent.class) |
| 113 | + public void onReady() { |
| 114 | + log.info(""); |
| 115 | + log.info("╔══════════════════════════════════════════════════════════════════════╗"); |
| 116 | + log.info("║ MateClaw is READY ✓ ║"); |
| 117 | + log.info("║ Web UI → http://localhost:18088 ║"); |
| 118 | + log.info("║ Swagger → http://localhost:18088/swagger-ui.html ║"); |
| 119 | + log.info("╚══════════════════════════════════════════════════════════════════════╝"); |
| 120 | + log.info(""); |
| 121 | + } |
54 | 122 | } |
0 commit comments