diff --git a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/AbstractJavaClientGenerator.java b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/AbstractJavaClientGenerator.java index 3ec3bfb9f..d6ec490b5 100644 --- a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/AbstractJavaClientGenerator.java +++ b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/AbstractJavaClientGenerator.java @@ -15,6 +15,8 @@ */ package org.mybatis.generator.codegen; +import static org.mybatis.generator.internal.util.StringUtility.stringHasValue; + /** * This class exists to that Java client generators can specify whether * an XML generator is required to match the methods in the @@ -49,4 +51,92 @@ public boolean requiresXMLGenerator() { * XML is required by this generator */ public abstract AbstractXmlGenerator getMatchedXMLGenerator(); + + /** + * Processes the rootInterface string to replace generic type placeholders with the actual record type. + *
+ * If the rootInterface contains a generic placeholder like {@code Important: MBG does not verify that the interface exists, or is a
valid Java interface. If specified, the value of this property should be a fully qualified
- interface name (like com.mycompany.MyRootInterface).
+ *
+ *
+ * @param rootInterface
+ * the rootInterface string from configuration, may contain generic placeholders
+ * @param recordType
+ * the actual record type (fully qualified) to use as replacement
+ * @return the processed rootInterface string with placeholders replaced, or the original string if no
+ * replacement is needed
+ */
+ protected String processRootInterfaceWithGenerics(String rootInterface, String recordType) {
+ if (!stringHasValue(rootInterface) || !stringHasValue(recordType)) {
+ return rootInterface;
+ }
+
+ // Check if rootInterface contains generic brackets
+ int openBracketIndex = rootInterface.indexOf('<');
+ if (openBracketIndex == -1) {
+ // No generic brackets, return as-is for backward compatibility
+ return rootInterface;
+ }
+
+ int closeBracketIndex = rootInterface.lastIndexOf('>');
+ if (closeBracketIndex == -1 || closeBracketIndex <= openBracketIndex) {
+ // Malformed generic brackets, return as-is
+ return rootInterface;
+ }
+
+ String baseInterface = rootInterface.substring(0, openBracketIndex);
+ String genericContent = rootInterface.substring(openBracketIndex + 1, closeBracketIndex).trim();
+
+ // Check if the generic content is a placeholder (single letter or single letter with extends clause)
+ // Common Java generic type parameter names: T, E, K, V, N, U, R, S
+ if (isGenericPlaceholder(genericContent)) {
+ // Replace placeholder with actual record type
+ return baseInterface + "<" + recordType + ">";
+ }
+
+ // Already has a concrete type (fully qualified or not), return as-is
+ return rootInterface;
+ }
+
+ /**
+ * Checks if a generic content string represents a placeholder that should be replaced.
+ * Placeholders are typically single-letter type parameters like T, E, K, V, etc.,
+ * optionally with an "extends" clause like "T extends Entity".
+ *
+ * @param genericContent
+ * the content between angle brackets
+ * @return true if this looks like a placeholder that should be replaced
+ */
+ private boolean isGenericPlaceholder(String genericContent) {
+ if (genericContent.isEmpty()) {
+ return false;
+ }
+
+ String trimmed = genericContent.trim();
+
+ // Simple case: just a single uppercase letter (T, E, K, V, etc.)
+ if (trimmed.length() == 1 && Character.isUpperCase(trimmed.charAt(0))) {
+ return true;
+ }
+
+ // Case with bounds: "T extends SomeClass" or "T super SomeClass"
+ // Pattern: single uppercase letter followed by " extends " or " super "
+ int extendsIndex = trimmed.indexOf(" extends ");
+ int superIndex = trimmed.indexOf(" super ");
+
+ if (extendsIndex > 0 || superIndex > 0) {
+ int boundIndex = extendsIndex > 0 ? extendsIndex : superIndex;
+ // First part should be a single uppercase letter
+ String typeParam = trimmed.substring(0, boundIndex).trim();
+ if (typeParam.length() == 1 && Character.isUpperCase(typeParam.charAt(0))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/JavaMapperGenerator.java b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/JavaMapperGenerator.java
index 6a4f34528..d7fc673ec 100644
--- a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/JavaMapperGenerator.java
+++ b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/JavaMapperGenerator.java
@@ -75,7 +75,10 @@ public List
Generic Type Support: If the root interface is a generic interface, you can + use a placeholder for the type parameter. The placeholder will be automatically + replaced with the actual record type for each table. For example: +
com.mycompany.BaseMapper<T> will become
+ com.mycompany.BaseMapper<com.example.User> for a User tablecom.mycompany.BaseMapper<T extends Entity> will become
+ com.mycompany.BaseMapper<com.example.User>BaseMapper<com.example.User>), it will be used as-is without
+ replacement.
diff --git a/core/mybatis-generator-core/src/site/xhtml/configreference/table.xhtml b/core/mybatis-generator-core/src/site/xhtml/configreference/table.xhtml
index e18af87f9..d28f7d3fa 100644
--- a/core/mybatis-generator-core/src/site/xhtml/configreference/table.xhtml
+++ b/core/mybatis-generator-core/src/site/xhtml/configreference/table.xhtml
@@ -416,7 +416,20 @@ specified with the <property> child element:
Important: MBG does not verify that the interface exists, or is a valid Java interface.
If specified, the value of this property should be a fully qualified - interface name (like com.mycompany.MyRootInterface).
+ interface name (like com.mycompany.MyRootInterface). +Generic Type Support: If the root interface is a generic interface, you can + use a placeholder for the type parameter. The placeholder will be automatically + replaced with the actual record type for this table. For example: +
com.mycompany.BaseMapper<T> will become
+ com.mycompany.BaseMapper<com.example.User>com.mycompany.BaseMapper<T extends Entity> will become
+ com.mycompany.BaseMapper<com.example.User>BaseMapper<com.example.User>), it will be used as-is without
+ replacement.