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 }, {@code }, etc., it will be + * replaced with the actual record type from the introspected table. For example: + *

+ * + * @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 getCompilationUnits() { } if (stringHasValue(rootInterface)) { - FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface); + // Process rootInterface to replace generic placeholders with actual record type + String recordType = introspectedTable.getBaseRecordType(); + String processedRootInterface = processRootInterfaceWithGenerics(rootInterface, recordType); + FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface); interfaze.addSuperInterface(fqjt); interfaze.addImportedType(fqjt); } diff --git a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/SimpleJavaClientGenerator.java b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/SimpleJavaClientGenerator.java index 71032b49f..219981f10 100644 --- a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/SimpleJavaClientGenerator.java +++ b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/SimpleJavaClientGenerator.java @@ -66,7 +66,10 @@ public List getCompilationUnits() { } if (stringHasValue(rootInterface)) { - FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface); + // Process rootInterface to replace generic placeholders with actual record type + String recordType = introspectedTable.getBaseRecordType(); + String processedRootInterface = processRootInterfaceWithGenerics(rootInterface, recordType); + FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface); interfaze.addSuperInterface(fqjt); interfaze.addImportedType(fqjt); } diff --git a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/runtime/dynamic/sql/DynamicSqlMapperGenerator.java b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/runtime/dynamic/sql/DynamicSqlMapperGenerator.java index 5a9add4ee..02cba65d4 100644 --- a/core/mybatis-generator-core/src/main/java/org/mybatis/generator/runtime/dynamic/sql/DynamicSqlMapperGenerator.java +++ b/core/mybatis-generator-core/src/main/java/org/mybatis/generator/runtime/dynamic/sql/DynamicSqlMapperGenerator.java @@ -155,7 +155,10 @@ protected Interface createBasicInterface() { } if (stringHasValue(rootInterface)) { - FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface); + // Process rootInterface to replace generic placeholders with actual record type + String processedRootInterface = processRootInterfaceWithGenerics(rootInterface, + recordType.getFullyQualifiedName()); + FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface); interfaze.addSuperInterface(fqjt); interfaze.addImportedType(fqjt); } diff --git a/core/mybatis-generator-core/src/site/xhtml/configreference/javaClientGenerator.xhtml b/core/mybatis-generator-core/src/site/xhtml/configreference/javaClientGenerator.xhtml index 29fae800a..cb300b1bd 100644 --- a/core/mybatis-generator-core/src/site/xhtml/configreference/javaClientGenerator.xhtml +++ b/core/mybatis-generator-core/src/site/xhtml/configreference/javaClientGenerator.xhtml @@ -169,7 +169,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 each table. For example: +

+ Placeholders are typically single uppercase letters (T, E, K, V, etc.) optionally + followed by " extends ..." or " super ...". If you specify a fully qualified type + (like 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: +

+ Placeholders are typically single uppercase letters (T, E, K, V, etc.) optionally + followed by " extends ..." or " super ...". If you specify a fully qualified type + (like BaseMapper<com.example.User>), it will be used as-is without + replacement.

runtimeCatalog