DynamicSqlMapperGenerator.java

/*
 *    Copyright 2006-2026 the original author or authors.
 *
 *    Licensed 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
 *
 *       https://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.mybatis.generator.runtime.dynamicsql.java;

import static org.mybatis.generator.internal.util.messages.Messages.getString;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.codegen.AbstractJavaGenerator;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import org.mybatis.generator.internal.util.StringUtility;
import org.mybatis.generator.runtime.JavaFieldAndImports;
import org.mybatis.generator.runtime.common.RootClassAndInterfaceUtility;
import org.mybatis.generator.runtime.dynamicsql.java.elements.BasicInsertMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.BasicMultipleInsertMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.BasicSelectManyMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.BasicSelectOneMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.DeleteByPrimaryKeyMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.FragmentGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralCountMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralDeleteMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralSelectDistinctMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralSelectMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralSelectOneMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.GeneralUpdateMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.InsertMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.InsertMultipleMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.InsertSelectiveMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.SelectByPrimaryKeyMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.SelectListGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.UpdateAllColumnsMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.UpdateByPrimaryKeyMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.UpdateByPrimaryKeySelectiveMethodGenerator;
import org.mybatis.generator.runtime.dynamicsql.java.elements.UpdateSelectiveColumnsMethodGenerator;

public class DynamicSqlMapperGenerator extends AbstractJavaGenerator {
    // record type for insert, select, update
    protected final FullyQualifiedJavaType recordType;
    // id to use for the common result map
    protected final String resultMapId;
    // name of the field containing the table in the support class
    protected final String tableFieldName;
    protected final FragmentGenerator fragmentGenerator;
    protected final boolean hasGeneratedKeys;

    public DynamicSqlMapperGenerator(Builder builder) {
        super(builder);

        recordType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
        resultMapId = recordType.getShortNameWithoutTypeArguments() + "Result"; //$NON-NLS-1$
        boolean useSnakeCase = introspectedTable
                .findTableOrClientGeneratorPropertyAsBoolean(PropertyRegistry.ANY_USE_SNAKE_CASE_IDENTIFIERS);

        tableFieldName = calculateTableFieldName(useSnakeCase);
        fragmentGenerator = new FragmentGenerator.Builder()
                .withIntrospectedTable(introspectedTable)
                .withResultMapId(resultMapId)
                .withTableFieldName(tableFieldName)
                .useSnakeCase(useSnakeCase)
                .withRecordType(recordType)
                .withCommentGenerator(commentGenerator)
                .build();

        hasGeneratedKeys = introspectedTable.getGeneratedKey().isPresent();
    }

    private String calculateTableFieldName(boolean useSnakeCase) {
        String name =
                JavaBeansUtil.getValidPropertyName(introspectedTable.getMyBatisDynamicSQLTableObjectName());
        if (useSnakeCase) {
            name = StringUtility.convertCamelCaseToSnakeCase(name);
        }

        return name;
    }

    @Override
    public List<CompilationUnit> getCompilationUnits() {
        progressCallback.startTask(getString("Progress.17", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));

        Interface interfaze = createBasicInterface();

        TopLevelClass supportClass = getSupportClass();
        String staticImportString =
                supportClass.getType().getFullyQualifiedNameWithoutTypeParameters() + ".*"; //$NON-NLS-1$
        interfaze.addStaticImport(staticImportString);

        if (hasGeneratedKeys) {
            addBasicInsertMethod(interfaze);
            addBasicInsertMultipleMethod(interfaze);
        }

        boolean reuseResultMap = addBasicSelectManyMethod(interfaze);
        addBasicSelectOneMethod(interfaze, reuseResultMap);

        addGeneralCountMethod(interfaze);
        addGeneralDeleteMethod(interfaze);
        addDeleteByPrimaryKeyMethod(interfaze);
        addInsertOneMethod(interfaze);
        addInsertMultipleMethod(interfaze);
        addInsertSelectiveMethod(interfaze);
        addSelectListField(interfaze);
        addGeneralSelectOneMethod(interfaze);
        addGeneralSelectMethod(interfaze);
        addSelectDistinctMethod(interfaze);
        addSelectByPrimaryKeyMethod(interfaze);
        addGeneralUpdateMethod(interfaze);
        addUpdateAllMethod(interfaze);
        addUpdateSelectiveMethod(interfaze);
        addUpdateByPrimaryKeyMethod(interfaze);
        addUpdateByPrimaryKeySelectiveMethod(interfaze);

        List<CompilationUnit> answer = new ArrayList<>();
        if (pluginAggregator.clientGenerated(interfaze, introspectedTable)) {
            answer.add(interfaze);
        }

        if (pluginAggregator.dynamicSqlSupportGenerated(supportClass, introspectedTable)) {
            answer.add(supportClass);
        }

        return answer;
    }

    protected Interface createBasicInterface() {
        FullyQualifiedJavaType type = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
        Interface interfaze = new Interface(type);
        interfaze.setVisibility(JavaVisibility.PUBLIC);
        commentGenerator.addJavaFileComment(interfaze);
        interfaze.addImportedType(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Mapper")); //$NON-NLS-1$
        interfaze.addAnnotation("@Mapper"); //$NON-NLS-1$

        RootClassAndInterfaceUtility.addRootInterfaceIfNecessary(interfaze, introspectedTable);
        return interfaze;
    }

    protected TopLevelClass getSupportClass() {
        return initializeSubBuilder(new DynamicSqlSupportClassGenerator.Builder())
                .build()
                .generate();
    }

    protected void addInsertOneMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new InsertMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .withRecordType(recordType)
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);

        if (generated && !hasGeneratedKeys) {
            // add common interface
            addCommonInsertInterface(interfaze);
        }
    }

    protected void addCommonInsertInterface(Interface interfaze) {
        FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(
                "org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper<" //$NON-NLS-1$
                        + recordType.getFullyQualifiedName() + ">"); //$NON-NLS-1$
        interfaze.addSuperInterface(superInterface);
        interfaze.addImportedTypes(superInterface.getImportList().stream()
                .map(FullyQualifiedJavaType::new)
                .collect(Collectors.toSet()));
    }

    protected void addBasicInsertMultipleMethod(Interface interfaze) {
        initializeSubBuilder(new BasicMultipleInsertMethodGenerator.Builder())
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addInsertMultipleMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new InsertMultipleMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .withRecordType(recordType)
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);

        if (generated && !hasGeneratedKeys) {
            // add common interface
            addCommonInsertInterface(interfaze);
        }
    }

    protected void addGeneralCountMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new GeneralCountMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .build()
                .execute(interfaze);

        if (generated) {
            // add common interface
            FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(
                    "org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper"); //$NON-NLS-1$
            interfaze.addSuperInterface(superInterface);
            interfaze.addImportedType(superInterface);
        }
    }

    protected void addGeneralDeleteMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new GeneralDeleteMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .build()
                .execute(interfaze);

        if (generated) {
            // add common interface
            FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(
                    "org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper"); //$NON-NLS-1$
            interfaze.addSuperInterface(superInterface);
            interfaze.addImportedType(superInterface);
        }
    }

    protected void addSelectListField(Interface interfaze) {
        var generator = initializeSubBuilder(new SelectListGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .build();

        JavaFieldAndImports javaFieldAndImports = generator.generateFieldAndImports();

        if (generator.callPlugins(javaFieldAndImports.getField(), interfaze)) {
            interfaze.addField(javaFieldAndImports.getField());
            interfaze.addImportedTypes(javaFieldAndImports.getImports());
        }
    }

    protected void addGeneralSelectMethod(Interface interfaze) {
        initializeSubBuilder(new GeneralSelectMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);
    }

    protected void addSelectDistinctMethod(Interface interfaze) {
        initializeSubBuilder(new GeneralSelectDistinctMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);
    }

    protected void addGeneralSelectOneMethod(Interface interfaze) {
        initializeSubBuilder(new GeneralSelectOneMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addGeneralUpdateMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new GeneralUpdateMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .build()
                .execute(interfaze);

        if (generated) {
            // add common interface
            FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(
                    "org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper"); //$NON-NLS-1$
            interfaze.addSuperInterface(superInterface);
            interfaze.addImportedType(superInterface);
        }
    }

    protected void addUpdateAllMethod(Interface interfaze) {
        initializeSubBuilder(new UpdateAllColumnsMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addUpdateSelectiveMethod(Interface interfaze) {
        initializeSubBuilder(new UpdateSelectiveColumnsMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addBasicSelectOneMethod(Interface interfaze, boolean reuseResultMap) {
        initializeSubBuilder(new BasicSelectOneMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .withResultMapId(resultMapId)
                .withReuseResultMap(reuseResultMap)
                .build()
                .execute(interfaze);
    }

    protected void addDeleteByPrimaryKeyMethod(Interface interfaze) {
        initializeSubBuilder(new DeleteByPrimaryKeyMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);
    }

    protected void addInsertSelectiveMethod(Interface interfaze) {
        var generated = initializeSubBuilder(new InsertSelectiveMethodGenerator.Builder())
                .withTableFieldName(tableFieldName)
                .withRecordType(recordType)
                .withFragmentGenerator(fragmentGenerator)
                .build()
                .execute(interfaze);

        if (generated && !hasGeneratedKeys) {
            // add common interface
            addCommonInsertInterface(interfaze);
        }
    }

    protected void addSelectByPrimaryKeyMethod(Interface interfaze) {
        initializeSubBuilder(new SelectByPrimaryKeyMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addUpdateByPrimaryKeyMethod(Interface interfaze) {
        initializeSubBuilder(new UpdateByPrimaryKeyMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addUpdateByPrimaryKeySelectiveMethod(Interface interfaze) {
        initializeSubBuilder(new UpdateByPrimaryKeySelectiveMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected void addBasicInsertMethod(Interface interfaze) {
        initializeSubBuilder(new BasicInsertMethodGenerator.Builder())
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    protected boolean addBasicSelectManyMethod(Interface interfaze) {
        return initializeSubBuilder(new BasicSelectManyMethodGenerator.Builder())
                .withFragmentGenerator(fragmentGenerator)
                .withRecordType(recordType)
                .build()
                .execute(interfaze);
    }

    public static class Builder extends AbstractJavaGeneratorBuilder<Builder> {
        @Override
        protected Builder getThis() {
            return this;
        }

        @Override
        public DynamicSqlMapperGenerator build() {
            return new DynamicSqlMapperGenerator(this);
        }
    }
}