通用代码生成器和常用工具类 - wtdig/study GitHub Wiki

一、通用代码生成器

1、预先创建的包

   java
       com
           wtdig
                batch  -- 批量操作
                bo     -- 通用bo代码
                mapper -- 通用mapper代码
                model  -- 通用model代码
                page   -- 通用分页类代码
                plugin -- 生成通用代码的工具
                utils  -- 常用工具类

   resources
            mapper               -- 通用mapper.xml文件
            sql                  -- 数据库案例
            bo-template.vm       --bo模板
            boimpl-template.vm   --boimpl模板
            generatorConfig.xml  --代码生成配置文件

2、pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wtdig</groupId>
    <artifactId>codeGenerator</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.1.1</version>
        </dependency>
    </dependencies>

</project>

3、generatorConfig.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC '-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN' 'http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd'>
<generatorConfiguration>
    <!-- mysql-connector的位置 -->
    <classPathEntry location="D:\maven_jar\repository\mysql\mysql-connector-java\5.1.40\mysql-connector-java-5.1.40.jar" />

    <!-- 配置一个代码生成操作,如果生成的目录或是数据库不一样,只需要参考增加一个context节点即可 -->
    <context id="generatorConfig" targetRuntime="MyBatis3">

        <property name="useActualColumnNames" value="false" />

        <!-- 配置插件 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin">
            <property name="useToStringFromRoot" value="true" />
        </plugin>

        <plugin type="com.wtdig.plugin.MysqlPaginationPlugin" />

        <commentGenerator>
            <property name="suppressAllComments" value="false" />
            <property name="suppressDate" value="true" />
        </commentGenerator>

        <!-- 配置数据库连接 -->
        <jdbcConnection connectionURL="jdbc:mysql://localhost:3306/code_generator"
            driverClass="com.mysql.jdbc.Driver" password="root" userId="root" />

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- 配置model生成位置 -->
        <javaModelGenerator targetPackage="com.wtdig.model" targetProject="src/main/java">
            <property name="rootClass" value="com.wtdig.model.BaseModel" />
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- 配置sqlmap生成位置 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!-- 配置mapper接口生成位置 -->
        <javaClientGenerator targetPackage="com.wtdig.mapper" targetProject="src/main/java"
            type="XMLMAPPER">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!--配置数据库的表-->
        <table schema="code_generator" tableName="code_generator_demo" enableCountByExample="true" enableUpdateByExample="false"
            enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="false">
            <property name="useActualColumnNames" value="false" />
            <generatedKey column="ID" sqlStatement="JDBC" identity="true" />
        </table>

        <table schema="code_generator" tableName="code_generator_test" enableCountByExample="true" enableUpdateByExample="false"
               enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="false">
            <property name="useActualColumnNames" value="false" />
            <generatedKey column="ID" sqlStatement="JDBC" identity="true" />
        </table>

    </context>
</generatorConfiguration>

4、sql文件下sql.sql文件

CREATE TABLE `code_generator_demo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `is_deleted` char(1) DEFAULT 'n' COMMENT '是否被删除',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `creator` varchar(32) DEFAULT NULL COMMENT '创建人',
  `gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
  `modifier` varchar(32) DEFAULT NULL COMMENT '修改人',
  `sex` varchar(128) NOT NULL COMMENT '性别',
  `name` varchar(192) NOT NULL COMMENT '名字',
  `remark` varchar(1024) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='代码生成演示'
;

CREATE TABLE `code_generator_test` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `is_deleted` char(1) DEFAULT 'n' COMMENT '是否被删除',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `creator` varchar(32) DEFAULT NULL COMMENT '创建人',
  `gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
  `modifier` varchar(32) DEFAULT NULL COMMENT '修改人',
  `city` varchar(128) NOT NULL COMMENT '城市',
  `address` varchar(192) NOT NULL COMMENT '地址',
  `remark` varchar(1024) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='代码生成测试'
;

5、plugin下的MysqlPaginationPlugin类代码

package com.wtdig.plugin;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.*;
import org.mybatis.generator.codegen.XmlConstants;
import org.mybatis.generator.config.PropertyRegistry;

import java.io.File;
import java.util.*;

/**
 * 数据库插件,model\mapper\mapperxml生成
 *
 * @author system
 */
public class MysqlPaginationPlugin extends PluginAdapter {

    private static String FULLY_QUALIFIED_PAGE = "com.wtdig.page.Page";

    private static String XMLFILE_POSTFIX = "Ext";

    private static String JAVAFILE_POTFIX = "Ext";

    private static String SQLMAP_COMMON_POTFIX = "and is_deleted = 'n'";

    private static String ANNOTATION_RESOURCE = "javax.annotation.Resource";

    private static String ANNOTATION_PARAM = "org.apache.ibatis.annotations.Param";

    private static Map<String, List<Element>> INSERT_MAP = new HashMap<String, List<Element>>();

    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        // 增加翻页
        addPage(topLevelClass, introspectedTable, "page");
        addCredCriteriaIsValidMethod(topLevelClass, introspectedTable);
        return super.modelExampleClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 在XXExample对象里添加Page对象属性
     *
     * @param topLevelClass
     * @param introspectedTable
     * @param name
     */
    private void addPage(TopLevelClass topLevelClass, IntrospectedTable introspectedTable, String name) {
        topLevelClass.addImportedType(new FullyQualifiedJavaType(FULLY_QUALIFIED_PAGE));
        CommentGenerator commentGenerator = context.getCommentGenerator();
        Field field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(new FullyQualifiedJavaType(FULLY_QUALIFIED_PAGE));
        field.setName(name);
        commentGenerator.addFieldComment(field, introspectedTable);
        topLevelClass.addField(field);
        char c = name.charAt(0);
        String camel = Character.toUpperCase(c) + name.substring(1);
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("set" + camel);
        method.addParameter(new Parameter(new FullyQualifiedJavaType(FULLY_QUALIFIED_PAGE), name));
        method.addBodyLine("this." + name + " = " + name + ";");
        commentGenerator.addGeneralMethodComment(method, introspectedTable);
        topLevelClass.addMethod(method);
        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType(FULLY_QUALIFIED_PAGE));
        method.setName("get" + camel);
        method.addBodyLine("return " + name + ";");
        commentGenerator.addGeneralMethodComment(method, introspectedTable);
        topLevelClass.addMethod(method);
    }

    private void addCredCriteriaIsValidMethod(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        CommentGenerator commentGenerator = context.getCommentGenerator();
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
        method.setName("isValid");
        List<String> bodyLines = new ArrayList<String>();
        bodyLines.add("if (oredCriteria.isEmpty()) {");
        bodyLines.add("return false;");
        bodyLines.add("}");
        bodyLines.add("for (Criteria criteria : oredCriteria) {");
        bodyLines.add("if (criteria.isValid()) {");
        bodyLines.add("return true;");
        bodyLines.add("}");
        bodyLines.add("}");
        bodyLines.add("return false;");
        method.addBodyLines(bodyLines);
        commentGenerator.addGeneralMethodComment(method, introspectedTable);
        topLevelClass.addMethod(method);
    }

    // 添删改Document的sql语句及属性
    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {

        XmlElement parentElement = document.getRootElement();

        updateDocumentNameSpace(introspectedTable, parentElement);

        removeDocumentUpdateByPrimaryKeySql(parentElement);

        generateInsertSql(parentElement, introspectedTable);

        generateMysqlPageSql(parentElement, introspectedTable);

        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }

    private void generateInsertSql(XmlElement parentElement, IntrospectedTable introspectedTable) {
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        List<Element> elements = INSERT_MAP.get(tableName);
        if (CollectionUtils.isEmpty(elements)) {
            return;
        }
        // mysql分页语句后半部分
        XmlElement paginationSuffixElement = new XmlElement("sql");
        paginationSuffixElement.addAttribute(new Attribute("id", "Insert_Column"));
        for (Element element : elements) {
            paginationSuffixElement.addElement(element);
        }
        parentElement.addElement(paginationSuffixElement);
        INSERT_MAP.remove(tableName);
    }

    private void generateMysqlPageSql(XmlElement parentElement, IntrospectedTable introspectedTable) {
        // mysql分页语句后半部分
        XmlElement paginationSuffixElement = new XmlElement("sql");
        context.getCommentGenerator().addComment(paginationSuffixElement);
        paginationSuffixElement.addAttribute(new Attribute("id", "MysqlPagination"));
        XmlElement pageEnd = new XmlElement("if");
        pageEnd.addAttribute(new Attribute("test", "page != null"));
        pageEnd.addElement(new TextElement("<![CDATA[ limit #{page.begin}, #{page.length} ]]>"));
        paginationSuffixElement.addElement(pageEnd);

        parentElement.addElement(paginationSuffixElement);
    }

    private void removeDocumentUpdateByPrimaryKeySql(XmlElement parentElement) {
        XmlElement updateElement = null;
        for (Element element : parentElement.getElements()) {
            XmlElement xmlElement = (XmlElement) element;
            if ("update".equals(xmlElement.getName())) {
                for (Attribute attribute : xmlElement.getAttributes()) {
                    if ("updateByPrimaryKey".equals(attribute.getValue())) {
                        updateElement = xmlElement;
                        break;
                    }
                }
            }
        }
        parentElement.getElements().remove(updateElement);
    }

    private void updateDocumentNameSpace(IntrospectedTable introspectedTable, XmlElement parentElement) {
        Attribute namespaceAttribute = null;
        for (Attribute attribute : parentElement.getAttributes()) {
            if ("namespace".equals(attribute.getName())) {
                namespaceAttribute = attribute;
            }
        }
        parentElement.getAttributes().remove(namespaceAttribute);
        parentElement.getAttributes().add(new Attribute("namespace", introspectedTable.getMyBatis3JavaMapperType()
                + JAVAFILE_POTFIX));
    }

    @Override
    public boolean sqlMapSelectByPrimaryKeyElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        TextElement text = new TextElement(SQLMAP_COMMON_POTFIX);
        element.addElement(text);
        return super.sqlMapSelectByPrimaryKeyElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeySelectiveElementGenerated(XmlElement element,
                                                                     IntrospectedTable introspectedTable) {
        List<Element> elements = element.getElements();
        XmlElement setItem = null;
        for (Element e : elements) {
            if (e instanceof XmlElement) {
                setItem = (XmlElement) e;
                Iterator<Element> it = setItem.getElements().iterator();
                while (it.hasNext()) {
                    XmlElement xmlElement = (XmlElement) it.next();
                    for (Attribute att : xmlElement.getAttributes()) {
                        String value = att.getValue();
                        if ("creator != null".equals(value) || "isDeleted != null".equals(value)
                                || "creator != null".equals(value) || "gmtCreate != null".equals(value)) {
                            it.remove();
                        }
                    }
                }
            }
        }
        TextElement text = new TextElement(SQLMAP_COMMON_POTFIX);
        element.addElement(text);
        return super.sqlMapUpdateByPrimaryKeySelectiveElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(XmlElement element,
                                                                        IntrospectedTable introspectedTable) {
        TextElement text = new TextElement(SQLMAP_COMMON_POTFIX);
        element.addElement(text);
        return super.sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapDeleteByPrimaryKeyElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.setName("update");
        replaceParameterType(element, introspectedTable);
        element.getElements().remove(element.getElements().size() - 1);
        element.getElements().remove(element.getElements().size() - 1);
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        element.getElements().add(new TextElement("update " + tableName
                + " set is_deleted = 'y',modifier=#{modifier,jdbcType=VARCHAR},gmt_modified=#{gmtModified,jdbcType=TIMESTAMP} where id = #{id,jdbcType=INTEGER}"));
        return super.sqlMapDeleteByPrimaryKeyElementGenerated(element, introspectedTable);
    }

    private void replaceParameterType(XmlElement element, IntrospectedTable introspectedTable) {
        int replaceInd = -1;
        for (int i = 0; i < element.getAttributes().size(); i++) {
            Attribute attr = element.getAttributes().get(i);
            if ("parameterType".equals(attr.getName())) {
                replaceInd = i;
                break;
            }
        }
        if (replaceInd >= 0) {
            element.getAttributes().remove(replaceInd);
            element.getAttributes().add(replaceInd,
                    new Attribute("parameterType", introspectedTable.getBaseRecordType()));
        }
    }

    private void replaceParameterType(XmlElement element, String name, String value) {
        int replaceInd = -1;
        for (int i = 0; i < element.getAttributes().size(); i++) {
            Attribute attr = element.getAttributes().get(i);
            if (attr.getName().equals(name)) {
                replaceInd = i;
                break;
            }
        }
        if (replaceInd >= 0) {
            element.getAttributes().remove(replaceInd);
            if (StringUtils.isNotBlank(value)) {
                element.getAttributes().add(replaceInd, new Attribute(name, value));
            }
        }
    }

    // insert
    @Override
    public boolean sqlMapInsertSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        INSERT_MAP.put(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime(),
                new ArrayList<Element>(element.getElements()));
        element.getElements().clear();
        context.getCommentGenerator().addComment(element);
        XmlElement insertElement = new XmlElement("include");
        insertElement.addAttribute(new Attribute("refid", "Insert_Column"));
        element.getElements().add(insertElement);

        return super.sqlMapInsertSelectiveElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapInsertElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        replaceParameterType(element, "id", "insertMapSelective");
        replaceParameterType(element, "parameterType", "java.util.Map");
        element.getElements().clear();
        context.getCommentGenerator().addComment(element);
        XmlElement insertElement = new XmlElement("include");
        insertElement.addAttribute(new Attribute("refid", "Insert_Column"));
        element.getElements().add(insertElement);
        return super.sqlMapInsertElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean clientDeleteByPrimaryKeyMethodGenerated(Method method, Interface interfaze,
                                                           IntrospectedTable introspectedTable) {
        Parameter parameter = new Parameter(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()),
                "record");
        method.getParameters().clear();
        method.addParameter(parameter);
        return super.clientDeleteByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientInsertMethodGenerated(Method method, Interface interfaze,
                                               IntrospectedTable introspectedTable) {
        method.setName("insertMapSelective");
        Parameter parameter = new Parameter(new FullyQualifiedJavaType("java.util.Map<String, Object>"), "record");
        method.getParameters().clear();
        method.addParameter(parameter);
        return super.clientInsertMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
                                   IntrospectedTable introspectedTable) {
        List<Method> methods = interfaze.getMethods();
        Iterator<Method> it = methods.iterator();
        while (it.hasNext()) {
            Method method = it.next();
            if ("insert".equals(method.getName()) || "updateByPrimaryKey".equals(method.getName())) {
                it.remove();
            }
        }

        return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
    }

    // selectByExample
    @Override
    public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element,
                                                                     IntrospectedTable introspectedTable) {

        addWhere(element);
        // $NON-NLS-1$
        XmlElement isNotNullElement = new XmlElement("include");
        isNotNullElement.addAttribute(new Attribute("refid", "MysqlPagination"));
        element.getElements().add(isNotNullElement);

        return super.sqlMapSelectByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapCountByExampleElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {

        addWhere(element);
        return super.sqlMapCountByExampleElementGenerated(element, introspectedTable);
    }

    private void addWhere(XmlElement element) {
        ListIterator<Element> it = element.getElements().listIterator();
        while (it.hasNext()) {
            Element em = it.next();
            if (em instanceof XmlElement) {
                XmlElement e = (XmlElement) em;
                if (e.getAttributes().get(0).getValue().equals("_parameter != null")) {
                    it.previous();
                    it.add(new TextElement(" where is_deleted = 'n' "));
                    return;
                }
            } else if (em instanceof TextElement) {
                TextElement e = (TextElement) em;
                if (e.getContent().contains("'false' as QUERYID")) {
                    it.remove();
                }
            }
        }
    }

    // 生成XXExt.xml
    @Override
    public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles(IntrospectedTable introspectedTable) {

        String[] splitFile = introspectedTable.getMyBatis3XmlMapperFileName().split("\\.");
        String fileNameExt = null;
        if (splitFile[0] != null) {
            fileNameExt = splitFile[0] + XMLFILE_POSTFIX + ".xml";
        }

        if (isExistExtFile(context.getSqlMapGeneratorConfiguration().getTargetProject(),
                introspectedTable.getMyBatis3XmlMapperPackage(), fileNameExt)) {
            return super.contextGenerateAdditionalXmlFiles(introspectedTable);
        }

        Document document = new Document(XmlConstants.MYBATIS3_MAPPER_PUBLIC_ID,
                XmlConstants.MYBATIS3_MAPPER_SYSTEM_ID);

        XmlElement root = new XmlElement("mapper");
        document.setRootElement(root);
        String namespace = introspectedTable.getMyBatis3SqlMapNamespace() + XMLFILE_POSTFIX;
        root.addAttribute(new Attribute("namespace", namespace));

        GeneratedXmlFile gxf = new GeneratedXmlFile(document, fileNameExt,
                introspectedTable.getMyBatis3XmlMapperPackage(),
                context.getSqlMapGeneratorConfiguration().getTargetProject(), false,
                context.getXmlFormatter());

        List<GeneratedXmlFile> answer = new ArrayList<GeneratedXmlFile>(1);
        answer.add(gxf);
        return answer;
    }

    // 生成XXExt.java
    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {

        FullyQualifiedJavaType type = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType()
                + JAVAFILE_POTFIX);
        Interface interfaze = new Interface(type);
        interfaze.setVisibility(JavaVisibility.PUBLIC);
        context.getCommentGenerator().addJavaFileComment(interfaze);

        FullyQualifiedJavaType baseInterfaze =
                new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
        interfaze.addSuperInterface(baseInterfaze);

        FullyQualifiedJavaType annotation = new FullyQualifiedJavaType(ANNOTATION_RESOURCE);
        interfaze.addAnnotation("@Resource");
        interfaze.addImportedType(annotation);

        CompilationUnit compilationUnits = interfaze;
        GeneratedJavaFile generatedJavaFile = new GeneratedJavaFile(compilationUnits,
                context.getJavaClientGeneratorConfiguration().getTargetProject(),
                context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING),
                context.getJavaFormatter());

        if (isExistExtFile(generatedJavaFile.getTargetProject(), generatedJavaFile.getTargetPackage(),
                generatedJavaFile.getFileName())) {
            return super.contextGenerateAdditionalJavaFiles(introspectedTable);
        }
        List<GeneratedJavaFile> generatedJavaFiles = new ArrayList<GeneratedJavaFile>(1);
        generatedJavaFile.getFileName();
        generatedJavaFiles.add(generatedJavaFile);
        return generatedJavaFiles;
    }

    private boolean isExistExtFile(String targetProject, String targetPackage, String fileName) {

        File project = new File(targetProject);
        if (!project.isDirectory()) {
            return true;
        }

        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(targetPackage, ".");
        while (st.hasMoreTokens()) {
            sb.append(st.nextToken());
            sb.append(File.separatorChar);
        }

        File directory = new File(project, sb.toString());
        if (!directory.isDirectory()) {
            boolean rc = directory.mkdirs();
            if (!rc) {
                return true;
            }
        }

        File testFile = new File(directory, fileName);
        if (testFile.exists()) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean sqlMapExampleWhereClauseElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        Iterator<Element> it = element.getElements().iterator();
        while (it.hasNext()) {
            Element em = (Element) it.next();
            if (em instanceof XmlElement) {
                XmlElement e = (XmlElement) em;
                if ("where".equals(e.getName())) {
                    e.setName("if");
                    e.addAttribute(new Attribute("test", "valid"));
                    XmlElement each = (XmlElement) e.getElements().get(0);
                    if (each.getName().equals("foreach")) {
                        each.addAttribute(new Attribute("open", "and ("));
                        each.addAttribute(new Attribute("close", ")"));
                        break;
                    }
                }
            }
        }
        return true;
    }

    @Override
    public boolean sqlMapDeleteByExampleElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientDeleteByExampleMethodGenerated(Method method, Interface interfaze,
                                                        IntrospectedTable introspectedTable) {
        return false;
    }

    /**
     * This plugin is always valid - no properties are required
     */
    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    /**
     * 多次执行,会覆盖
     *
     * @param args
     */
    public static void main(String[] args) {
        String config = "D:\\alibababank\\codeGenerator\\src\\main\\resources\\generatorConfig.xml";
        String[] arg = {"-configfile", config, "-verbose", "-overwrite"};
        ShellRunner.main(arg);
    }

}

6、page下的Page类代码

package com.wtdig.page;

public class Page {
    // 分页查询开始记录位置
    private int begin;
    // 分页查看下结束位置
    private int end;
    // 每页显示记录数
    private int length;
    // 查询结果总记录数
    private int count;
    // 当前页码
    private int current;
    // 总共页数
    private int total;

    public Page() {
    }

    /**
     * 构造函数
     *
     * @param begin
     * @param length
     */
    public Page(int begin, int length) {
        this.begin = begin;
        this.length = length;
        this.end = this.begin + this.length;
        this.current = (int) Math.floor((this.begin * 1.0d) / this.length) + 1;
    }

    /**
     * @param begin
     * @param length
     * @param count
     */
    public Page(int begin, int length, int count) {
        this(begin, length);
        this.count = count;
    }

    /**
     * @return the begin
     */
    public int getBegin() {
        return begin;
    }

    /**
     * @return the end
     */
    public int getEnd() {
        return end;
    }

    /**
     * @param end the end to set
     */
    public void setEnd(int end) {
        this.end = end;
    }

    /**
     * @param begin the begin to set
     */
    public void setBegin(int begin) {
        this.begin = begin;
        if (this.length != 0) {
            this.current = (int) Math.floor((this.begin * 1.0d) / this.length) + 1;
        }
    }

    /**
     * @return the length
     */
    public int getLength() {
        return length;
    }

    /**
     * @param length the length to set
     */
    public void setLength(int length) {
        this.length = length;
        if (this.begin != 0) {
            this.current = (int) Math.floor((this.begin * 1.0d) / this.length) + 1;
        }
    }

    /**
     * @return the count
     */
    public int getCount() {
        return count;
    }

    /**
     * @param count the count to set
     */
    public void setCount(int count) {
        this.count = count;
        this.total = (int) Math.floor((this.count * 1.0d) / this.length);
        if (this.count % this.length != 0) {
            this.total++;
        }
    }

    /**
     * @return the current
     */
    public int getCurrent() {
        return current;
    }

    /**
     * @param current the current to set
     */
    public void setCurrent(int current) {
        this.current = current;
    }

    /**
     * @return the total
     */
    public int getTotal() {
        if (total == 0) {
            return 1;
        }
        return total;
    }

    /**
     * @param total the total to set
     */
    public void setTotal(int total) {
        this.total = total;
    }

}

7、model下的BaseModel类代码

package com.wtdig.model;

import java.util.Date;

public class BaseModel {

    public static final String YES = "y", NO = "n";
    public static final String IS_DELETED = "isDeleted";
    public static final String CREATOR = "creator";
    public static final String MODIFIER = "modifier";
    public static final String GMT_MODIFIED = "gmtModified";
    public static final String GMT_CREATE = "gmtCreate";

    protected Long id;

    protected String isDeleted;

    protected String creator;

    protected String modifier;

    protected Date gmtModified;

    protected Date gmtCreate;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getIsDeleted() {
        return isDeleted;
    }

    public void setIsDeleted(String isDeleted) {
        this.isDeleted = isDeleted;
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        this.creator = creator;
    }

    public String getModifier() {
        return modifier;
    }

    public void setModifier(String modifier) {
        this.modifier = modifier;
    }

    public Date getGmtModified() {
        return gmtModified;
    }

    public void setGmtModified(Date gmtModified) {
        this.gmtModified = gmtModified;
    }

    public Date getGmtCreate() {
        return gmtCreate;
    }

    public void setGmtCreate(Date gmtCreate) {
        this.gmtCreate = gmtCreate;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(" [id=");
        builder.append(id);
        builder.append(", isDeleted=");
        builder.append(isDeleted);
        builder.append(", creator=");
        builder.append(creator);
        builder.append(", modifier=");
        builder.append(modifier);
        builder.append(", gmtModified=");
        builder.append(gmtModified);
        builder.append(", gmtCreate=");
        builder.append(gmtCreate);
        builder.append("]");
        return builder.toString();
    }

}

8、bo-template.vm文件

package com.wtdig.bo;

import com.wtdig.model.${name};
import com.wtdig.model.${name}Example;

public interface ${name}Bo extends BaseBo<${name}, ${name}Example> {

}

9、boimpl-template.vm文件

package com.wtdig.bo.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.wtdig.mapper.${name}MapperExt;
import com.wtdig.model.${name};
import com.wtdig.model.${name}Example;
import com.wtdig.bo.${name}Bo;

@Service("${caseName}Bo")
public class ${name}BoImpl implements ${name}Bo {

    @Autowired
    private ${name}MapperExt ${caseName}MapperExt;

    @Override
    public int insert(${name} record) {
        return ${caseName}MapperExt.insertSelective(record);
    }

    @Override
    public int updateById(${name} record) {
        validate(record);
        return ${caseName}MapperExt.updateByPrimaryKeySelective(record);
    }

    @Override
    public int deleteById(${name} record) {
        validate(record);
        return ${caseName}MapperExt.deleteByPrimaryKey(record);
    }

    @Override
    public ${name} queryById(Long id) {
        return ${caseName}MapperExt.selectByPrimaryKey(id);
    }

    @Override
    public List<${name}> queryByExample(${name}Example example) {
        return ${caseName}MapperExt.selectByExample(example);
    }

    @Override
    public long countByExample(${name}Example example) {
        return ${caseName}MapperExt.countByExample(example);
    }

    @Override
    public ${name} queryOneByExample(${name}Example example) {
        List<${name}> list = queryByExample(example);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    private void validate(${name} record) {
        Assert.notNull(record);
        Assert.notNull(record.getId(), "id is null");
    }

}

10、bo下的BaseBo类代码

package com.wtdig.bo;

import com.wtdig.model.BaseModel;

import java.util.List;


/**
 * 通用BO
 *
 * @param <T>
 * @param <E>
 * @author wenbo.mwb
 */
public interface BaseBo<T extends BaseModel, E> {

    /**
     * 插入数据
     *
     * @param record
     * @return
     */
    int insert(T record);

    /**
     * 更新数据
     *
     * @param record
     * @return
     */
    int updateById(T record);

    /**
     * 删除数据
     *
     * @param record
     * @return
     */
    int deleteById(T record);

    /**
     * 根据id查询数据
     *
     * @param id
     * @return
     */
    T queryById(Long id);

    /**
     * 根据条件查询多个结果集
     *
     * @param example
     * @return
     */
    List<T> queryByExample(E example);

    /**
     * 根据条件查询记录数
     *
     * @param example
     * @return
     */
    long countByExample(E example);

    /**
     * 根据条件查询一条结果集
     *
     * @param example
     * @return
     */
    T queryOneByExample(E example);

}

11、plugin下GengratorBoCode类代码

package com.wtdig.plugin;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.ui.velocity.VelocityEngineUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 通用的bo生成
 */
public class GengratorBoCode {

    public static void main(String[] args) throws Exception {
        /**
         * 是否覆盖(不进行覆盖操作)
         */
        boolean overwrited = false;
        /**
         * 项目的根路径(需要修改)
         */
        String basePath = "D:\\alibababank\\codeGenerator";

        String bo = "src/main/java/com/wtdig/bo";

        String model = "src/main/java/com/wtdig/model";

        String boImpl = "/src/main/java/com/wtdig/bo/impl";

        File file = new File(basePath, model);

        String[] fileNames = file.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return !"Page.java".equals(name) && !name.endsWith("Example.java") && !"BaseModel.java".equals(name);
            }
        });

        Properties properties = new Properties();
        properties.setProperty("resource.loader", "class");
        properties.setProperty("class.resource.loader.class",
                "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        VelocityEngine velocityEngine = new VelocityEngine(properties);
        velocityEngine.init();
        File servicePath = new File(basePath, bo);
        for (String fileName : fileNames) {
            fileName = fileName.substring(0, fileName.lastIndexOf("."));
            File serviceFile = new File(servicePath, fileName + "Bo.java");
            if (!overwrited && serviceFile.exists()) {
                continue;
            }
            System.out.println("gengrator " + serviceFile.getName());
            BufferedWriter writer = new BufferedWriter(new FileWriter(serviceFile));

            VelocityEngineUtils.mergeTemplate(velocityEngine, "bo-template.vm", "UTF-8",
                    Collections.singletonMap("name", (Object) fileName), writer);
            writer.flush();
            writer.close();
        }

        File serviceImplPath = new File(basePath, boImpl);
        for (String fileName : fileNames) {
            fileName = fileName.substring(0, fileName.lastIndexOf("."));
            File implFile = new File(serviceImplPath, fileName + "BoImpl.java");
            if (!overwrited && implFile.exists()) {
                continue;
            }
            System.out.println("gengrator " + implFile.getName());
            BufferedWriter writer = new BufferedWriter(new FileWriter(implFile));
            Map<String, Object> context = new HashMap<String, Object>();
            context.put("name", fileName);
            context.put("caseName", firstToLowerCase(fileName));
            VelocityEngineUtils.mergeTemplate(velocityEngine, "boimpl-template.vm", "UTF-8", context, writer);
            writer.flush();
            writer.close();
        }
        System.out.println("done gengrator");
    }

    /**
     * 首字母小写
     *
     * @param value
     * @return
     */
    private static String firstToLowerCase(String value) {
        String prefix = String.valueOf(value.charAt(0)).toLowerCase();
        String suffix = value.substring(1, value.length());
        StringBuilder builder = new StringBuilder();
        return builder.append(prefix).append(suffix).toString();
    }

}

二、mybatis的批量操作数据库

1、AbstractMapperTemplate类代码

package com.alibaba.sac.dal.util;

import java.util.Collection;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

/**
 * 数据库批量操作template
 */
public abstract class AbstractMapperTemplate {

    public void doBatch(SqlSessionFactory sqlSessionFactory, Class<?> mapperClass, String mapperId, Collection<?> objs) {
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            for (Object obj : objs) {
                singleOperation(session, mapperClass.getName() + "." + mapperId, obj);
            }
            session.commit();
            session.clearCache();
        } catch (Exception ex) {
            session.rollback();
            throw new RuntimeException("doBatch Exception.", ex);
        } finally {
            session.close();
        }
    }

    /**
     * 单个操作
     * 
     * @param session
     * @param statement
     * @param obj
     */
    protected abstract void singleOperation(SqlSession session, String statement, Object obj);

}

2、BasicValueHelper类代码

package com.wtdig.utils;

/**
 * 基础数据接口,可以使用Aop编程进行数据库通用字段值的设定
 */
public interface BasicValueHelper {

    /**
     * 设置插入时的基础值
     *
     * @param obj
     */
    void setInsertBasicValue(Object obj);

    /**
     * 设置修改时的基础值
     *
     * @param obj
     */
    void setUpdateBasicValue(Object obj);
}

3、MapperUtil类代码

package com.wtdig.utils;

import com.wtdig.model.BaseModel;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 数据库批量操作工具类
 *
 * 使用该工具类需要在spring-dao.xml配置:
 * <bean class="com.wtdig.utils.MapperUtil">
 *   <property name ="sqlSessionFactory" ref="sqlSessionFactory"/>
 * </bean>
 */

public class MapperUtil {

    @Autowired
    private static SqlSessionFactory sqlSessionFactory;

    public static final String COMMON_INSERT_MAPPER_ID = "insertSelective";

    public static final String COMMON_INSERT_MAP_MAPPER_ID = "insertMapSelective";

    public static final String COMMON_UPDATE_MAPPER_ID = "updateByPrimaryKeySelective";

    public static final String COMMON_DELETE_MAPPER_ID = "deleteByPrimaryKey";

    /**
     * 使用aop进行,数据库通用字段值的设置
     */
//    private static BasicValueHelper basicValueHelper;
//
//    @Autowired
//    public void setBasicValueHelper(BasicValueHelper basicValueHelper) {
//        MapperUtil.basicValueHelper = basicValueHelper;
//    }


    /**
     * 添加
     *
     * @param mapperClass
     * @param mapperId
     * @param objs
     */
    public static <T extends BaseModel> void insertBatch(Class<?> mapperClass, String mapperId, Collection<T> objs) {
        new AbstractMapperTemplate() {

            @Override
            protected void singleOperation(SqlSession session, String statement, Object obj) {
                //basicValueHelper.setInsertBasicValue(obj);
                session.insert(statement, obj);
            }
        }.doBatch(sqlSessionFactory, mapperClass, mapperId, objs);
    }

    public static <T extends BaseModel> void insertBatch(Class<?> mapperClass, Collection<T> objs) {
        insertBatch(mapperClass, COMMON_INSERT_MAPPER_ID, objs);
    }

    /**
     * 添加
     *
     * @param mapperClass
     * @param mapperId
     * @param objs
     */
    public static void insertBatch(Class<?> mapperClass, String mapperId, List<Map<String, Object>> objs) {
        new AbstractMapperTemplate() {

            @Override
            protected void singleOperation(SqlSession session, String statement, Object obj) {
               // basicValueHelper.setInsertBasicValue(obj);
                session.insert(statement, obj);
            }
        }.doBatch(sqlSessionFactory, mapperClass, mapperId, objs);
    }

    public static <T extends BaseModel> void insertBatch(Class<?> mapperClass, List<Map<String, Object>> objs) {
        insertBatch(mapperClass, COMMON_INSERT_MAP_MAPPER_ID, objs);
    }

    /**
     * 修改
     *
     * @param mapperClass
     * @param mapperId
     * @param objs
     */
    public static void updateBatch(Class<?> mapperClass, String mapperId, List<?> objs) {
        new AbstractMapperTemplate() {

            @Override
            protected void singleOperation(SqlSession session, String statement, Object obj) {
                //basicValueHelper.setUpdateBasicValue(obj);
                session.update(statement, obj);
            }
        }.doBatch(sqlSessionFactory, mapperClass, mapperId, objs);
    }

    public static void updateBatch(Class<?> mapperClass, List<?> objs) {
        updateBatch(mapperClass, COMMON_UPDATE_MAPPER_ID, objs);
    }

    /**
     * 删除
     *
     * @param mapperClass
     * @param mapperId
     * @param objs
     */
    public static void deleteBatch(Class<?> mapperClass, String mapperId, List<?> objs) {
        new AbstractMapperTemplate() {

            @Override
            protected void singleOperation(SqlSession session, String statement, Object obj) {
               // basicValueHelper.setUpdateBasicValue(obj);
                session.delete(statement, obj);
            }
        }.doBatch(sqlSessionFactory, mapperClass, mapperId, objs);
    }

    public static void deleteBatch(Class<?> mapperClass, List<?> objs) {
        deleteBatch(mapperClass, COMMON_DELETE_MAPPER_ID, objs);
    }

}

4、batch下的BatchDemo类代码

package com.wtdig.batch;

import com.wtdig.mapper.CodeGeneratorDemoMapperExt;
import com.wtdig.model.CodeGeneratorDemo;
import com.wtdig.utils.MapperUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * 数据库批量操作演示
 *
 * @author wb-wt261136
 * @version 2018/5/8. 11:10
 */
public class BatchDemo {

    /**
     * 默认的批量插入
     */
    public void doBatchDemo() {
        List<CodeGeneratorDemo> demoArrayList = new ArrayList<CodeGeneratorDemo>();
        MapperUtil.insertBatch(CodeGeneratorDemoMapperExt.class, demoArrayList);
    }

    /**
     * 按照mapperExt自定义的方式进行批量插入
     * "myInsert"需要在CodeGeneratorDemoMapperExt类中写入该方法public void myInsert(CodeGeneratorDemo record);
     */
    public void doBatchCoustomerDemo() {
        List<CodeGeneratorDemo> demoArrayList = new ArrayList<CodeGeneratorDemo>();
        MapperUtil.insertBatch(CodeGeneratorDemoMapperExt.class, "myInsert", demoArrayList);
    }

}

二、常用工具类型

1、utils下的驼峰转换工具类

package com.wtdig.utils;

import org.apache.commons.lang.StringUtils;

public class CamelCaseUtils {

    private static final char SEPARATOR = '_';

    private CamelCaseUtils() {

    }

    /**
     * 骆驼法则字符串转换为下划线标准格式
     *
     * @param s
     * @return
     */
    public static String toUnderlineName(String s) {
        if (StringUtils.isBlank(s)) {
            return s;
        }

        StringBuilder sb = new StringBuilder();
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            boolean nextUpperCase = true;

            if (i < (s.length() - 1)) {
                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
            }

            if ((i >= 0) && Character.isUpperCase(c)) {
                if (!upperCase || !nextUpperCase) {
                    if (i > 0) {
                        sb.append(SEPARATOR);
                    }
                }
                upperCase = true;
            } else {
                upperCase = false;
            }

            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 下划线命名字符串格式转换为骆驼法则格式
     *
     * @param s
     * @return
     */
    public static String toCamelCase(String s) {
        if (StringUtils.isBlank(s)) {
            return s;
        }
        s = s.toLowerCase();

        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }

        return sb.toString();
    }

    /**
     * 下划线命名字符串格式转换为骆驼法则格式(如果不含下划线就直接返回)
     *
     * @param s
     * @return
     */
    public static String toCamelCaseWithFormat(String s) {
        if (s.contains("_")) {
            return toCamelCase(s);
        } else {
            return s;
        }
    }

    /**
     * 下划线命名字符串格式转换为骆驼法则格式 首字母大写
     *
     * @param s
     * @return
     */
    public static String toCapitalizeCamelCase(String s) {
        if (StringUtils.isBlank(s)) {
            return s;
        }
        s = toCamelCase(s);
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

    public static void main(String[] args) {
        System.out.println(CamelCaseUtils.toUnderlineName("ISOCertifiedStaff"));
        System.out.println(CamelCaseUtils.toUnderlineName("CertifiedStaff"));
        System.out.println(CamelCaseUtils.toUnderlineName("UserID"));
        System.out.println(CamelCaseUtils.toCamelCase("iso_certified_staff"));
        System.out.println(CamelCaseUtils.toCamelCase("certified_staff"));
        System.out.println(CamelCaseUtils.toCamelCase("user_id"));
        System.out.println(StringUtils.capitalize("changePerceptionMapReducer.changePerceptionMapper"));
    }
}

2、utils下常见string操作工具类

package com.wtdig.utils;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;

import java.math.BigDecimal;
import java.nio.charset.Charset;

public class StringUtil extends StringUtils {

    private static final String SQL_LIKE = "%";

    /**
     * 将多个String连接起来,这里用StringBuffer效率更高
     *
     * @param strings 多个String
     * @return String
     */
    public static String concatenate(String... strings) {
        StringBuilder sb = new StringBuilder();
        for (String item : strings) {
            sb.append(item);
        }
        return sb.toString();
    }

    /**
     * 全角转成半角
     *
     * @param input
     * @return
     */
    public static String fullWidthTohalfWidth(String input) {
        if (isEmpty(regularTrim(input))) {
            return "";
        }
        char c[] = input.toCharArray();
        for (int i = 0; i < c.length; i++) {
            if (c[i] == '\u3000') {
                c[i] = ' ';
            } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') {
                c[i] = (char) (c[i] - 65248);
            }
        }
        return new String(c);
    }

    /**
     * 判断是否是数字
     *
     * @param str
     * @return
     */
    public static boolean isDigit(String str) {
        String regularExp = "\\d*";
        return str.matches(regularExp);
    }

    /**
     * 把一个字符串的$变量都用数组的值依次替换
     *
     * @param str
     * @param value
     * @return
     */
    public static String variableSubstitute(String str, String[] value) {
        String temp = str;
        if (StringUtil.isNotBlank(temp) && value != null) {
            for (int i = 0; i < value.length && value[i] != null; i++) {
                temp = replaceOnce(temp, "$", value[i]);
            }
        }
        return temp;
    }

    /**
     * 根据传入的源,查找值,数目n,获得第n个匹配的值的位置
     *
     * @param source
     * @param findStr
     * @param num
     * @return
     */
    public static int indexOfSpecial(String source, String findStr, int num) {
        int begin = -1;
        if (num < 1 || source == null || findStr == null || source.length() <= 0 || findStr.length() <= 0) {
            return -1;
        }
        for (int i = 0; i < num; i++) {
            begin = source.indexOf(findStr, begin + 1);
            if (begin == -1) {
                return -1;
            }
        }
        return begin;
    }

    /**
     * findStr在源字符串出现的次数
     *
     * @param source
     * @param findStr
     * @return
     */
    public static int getNumOfSpecial(String source, String findStr) {
        int ret = 0;
        int i = 1;
        while (indexOfSpecial(source, findStr, i) != -1) {
            i++;
            ret++;
        }
        return ret;
    }

    /**
     * 字符串是否为空
     *
     * @param str
     * @return
     */
    public static boolean isEmpty(String str) {
        if (str == null) {
            return true;
        }
        if (str.length() == 0) {
            return true;
        }
        return false;
    }

    /**
     * 对象是否为空
     *
     * @param obj
     * @return
     */
    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        } else if (obj instanceof String) {
            return isEmpty((String) obj);
        } else if (obj instanceof BigDecimal) {
            if (BigDecimal.valueOf(0).compareTo((BigDecimal) obj) == 0) {
                return true;
            }
        } else if (obj instanceof Integer) {
            if (Integer.valueOf(0).equals(obj)) {
                return true;
            }
        } else if (obj instanceof Double) {
            if (Double.valueOf(0).equals(obj)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 截取字符串开头指定长度(字节)的子字符串
     *
     * @param src
     * @param byteLen
     * @return
     */
    public static String limitByByte(String src, int byteLen) {
        if (byteLen <= 0) {
            throw new RuntimeException("invalid limit.");
        }
        String cs = Charset.defaultCharset().displayName();
        if ("UTF-8".equals(cs)) {
            if (byteLen % 3 == 1) {
                byteLen += 2;
            } else if (byteLen % 3 == 2) {
                byteLen += 1;
            }
        } else if ("GBK".equals(cs)) {
            if (byteLen % 2 != 0) {
                byteLen += 1;
            }
        }

        String ret = "";
        try {
            if (src != null) {
                byte[] tmpStr = new byte[byteLen];
                if (src.getBytes(cs).length <= byteLen) {
                    return src;
                }
                System.arraycopy(src.getBytes(cs), 0, tmpStr, 0, byteLen);
                ret = new String(tmpStr, cs).trim();
            }
        } catch (Exception e) {
            throw new RuntimeException("invalid charset.", e);
        }
        return ret;
    }

    /**
     * 截取字符串开头指定长度(字符)的子字符串
     *
     * @param src
     * @param len
     * @return
     */
    public static String limit(String src, int len) {
        if (len <= 0) {
            throw new RuntimeException("invalid limit.");
        }
        if (src == null) {
            throw new RuntimeException("invalid source string.");
        }
        if (len > src.length()) {
            return src;
        } else {
            return src.substring(0, len);
        }

    }

    /**
     * 根据传入的源,查找值#,数目n,获得第n个被查找值例如#包含的值
     *
     * @param source
     * @param special
     * @param num
     * @return
     */
    public static String subStringBySpecial(String source, String special, int num) {
        if (num < 1 || source == null || special == null || source.length() <= 0 || special.length() <= 0) {
            return null;
        }
        int beginNum = indexOfSpecial(source, special, num);
        int endNum = indexOfSpecial(source, special, num + 1);
        if (beginNum < 0 || endNum < 0) {
            return null;
        }
        String ret = source.substring(beginNum + 1, endNum);
        return ret;
    }

    public static String subStringBySpecialBe(String source, String special, int num) {
        if (num < 1 || source == null || special == null || source.length() <= 0 || special.length() <= 0) {
            return null;
        }
        boolean isBegin = false;
        boolean isEnd = false;
        if (indexOfSpecial(source, special, 1) == 0) {
            isBegin = true;
        }
        if (source.lastIndexOf(special) == (source.length() - 1)) {
            isEnd = true;
        }

        // 首位不为#
        if (num == 1 && !isBegin) {
            if (indexOfSpecial(source, special, 1) >= 0) {
                return source.substring(0, indexOfSpecial(source, special, 1));
            } else {
                return null;
            }
        }
        if (!isBegin) {
            num--;
        }
        int beginNum = indexOfSpecial(source, special, num);
        int endNum = indexOfSpecial(source, special, num + 1);
        if (beginNum < 0 || (endNum < 0 && isEnd)) {
            return null;
        }
        if (beginNum > 0 && endNum < 0 && !isEnd) {
            return source.substring(beginNum + 1);
        }

        return source.substring(beginNum + 1, endNum);
    }

    public static String regularTrim(String str) {
        if (str != null) {
            String regularExp = "^[  ]+|[  ]+$";
            String returnStr = str.replaceAll(regularExp, "");
            return returnStr;
        } else {
            return null;
        }
    }

    public static void testRegularTrim(String[] args) {
        String strQj = "   团体                  ";
        String strBj = " dd   ";
        regularTrim(strQj);
        regularTrim(strBj);
        regularTrim("");
        regularTrim(null);
    }

    public static String toString(Object obj) {
        return ReflectionToStringBuilder.toString(obj);
    }

    public static String getSQLLikeCriteria(String fieldValeLike) {
        String value = StringUtils.trimToNull(fieldValeLike);
        if (value == null) {
            throw new RuntimeException("The fieldValeLike is invalid");
        }
        return new StringBuilder(SQL_LIKE).append(value).append(SQL_LIKE).toString();
    }

    /**
     * 将xml中的特殊字符转义 &---->&amp; '---->&apos; <---->&lt; >---->&gt; "---->&quot;
     *
     * @param value
     * @return
     */
    public static String getStrReplaceSpecialChars(String value) {
        if (StringUtils.isBlank(value)) {
            return value;
        }
        value = value.replaceAll("&", "&amp;");
        value = value.replaceAll("'", "&apos;");
        value = value.replaceAll("<", "&lt;");
        value = value.replaceAll(">", ">&gt;");
        value = value.replaceAll("\"", "&quot;");
        return value;
    }
    
}

3、utils下date下日期工具类

package com.wtdig.utils.date;

import java.util.Calendar;

/**
 * 日期字段
 */
public class DateField {

    private DateField(Builder builder) {
        this.year = builder.year;
        this.month = builder.month;
        this.day = builder.day;
        this.hour = builder.hour;
        this.minute = builder.minute;
        this.second = builder.second;
    }

    private final int year;
    private final Month month;
    private final int day;
    private final int hour;
    private final int minute;
    private final int second;

    public int getYear() {
        return year;
    }

    public Month getMonth() {
        return month;
    }

    public int getDay() {
        return day;
    }

    public int getHour() {
        return hour;
    }

    public int getMinute() {
        return minute;
    }

    public int getSecond() {
        return second;
    }

    public static class Builder {

        public Builder(int year, Month month, int day) {
            super();
            this.year = year;
            this.month = month;
            this.day = day;
        }

        private final int year;
        private final Month month;
        private final int day;

        private int hour = 0;
        private int minute = 0;
        private int second = 0;

        public int getHour() {
            return hour;
        }

        public Builder setHour(int hour) {
            this.hour = hour;
            return this;
        }

        public Builder setMinute(int minute) {
            this.minute = minute;
            return this;
        }

        public Builder setSecond(int second) {
            this.second = second;
            return this;
        }

        public DateField build() {
            return new DateField(this);
        }

    }

    public static enum FieldNumber {
        /**
         * date enum
         */
        ERA(Calendar.ERA), YEAR(Calendar.YEAR), MONTH(Calendar.MONTH), WEEK_OF_YEAR(Calendar.WEEK_OF_YEAR),
        WEEK_OF_MONTH(Calendar.WEEK_OF_MONTH), DATE(Calendar.DATE), DAY_OF_MONTH(Calendar.DAY_OF_MONTH),
        DAY_OF_YEAR(Calendar.DAY_OF_YEAR), DAY_OF_WEEK(Calendar.DAY_OF_WEEK),
        DAY_OF_WEEK_IN_MONTH(Calendar.DAY_OF_WEEK_IN_MONTH), AM_PM(Calendar.AM_PM), HOUR(Calendar.HOUR),
        HOUR_OF_DAY(Calendar.HOUR_OF_DAY), MINUTE(Calendar.MINUTE), SECOND(Calendar.SECOND),
        MILLISECOND(Calendar.MILLISECOND), ZONE_OFFSET(Calendar.ZONE_OFFSET), DST_OFFSET(Calendar.DST_OFFSET),
        FIELD_COUNT(17);

        public int getValue() {
            return value;
        }

        private FieldNumber(int value) {
            this.value = value;
        }

        private int value;
    }

    public static enum DayOfWeek {
        /**
         * week enum
         */
        SUNDAY(Calendar.SUNDAY), MONDAY(Calendar.MONDAY), TUESDAY(Calendar.TUESDAY), WEDNESDAY(Calendar.WEDNESDAY),
        THURSDAY(Calendar.THURSDAY), FRIDAY(Calendar.FRIDAY), SATURDAY(Calendar.SATURDAY);

        public int getValue() {
            return value;
        }

        private DayOfWeek(int value) {
            this.value = value;
        }

        private int value;
    }

    public static enum Month {
        /**
         * month enum
         */
        JANUARY(Calendar.JANUARY), FEBRUARY(Calendar.FEBRUARY), MARCH(Calendar.MARCH), APRIL(Calendar.APRIL),
        MAY(Calendar.MAY), JUNE(Calendar.JUNE), JULY(Calendar.JULY), AUGUST(Calendar.AUGUST),
        SEPTEMBER(Calendar.SEPTEMBER), OCTOBER(Calendar.OCTOBER), NOVEMBER(Calendar.NOVEMBER),
        DECEMBER(Calendar.DECEMBER);

        public int getValue() {
            return value;
        }

        private Month(int value) {
            this.value = value;
        }

        private int value;
    }
}

package com.wtdig.utils.date;

public enum DateFormatPatterns {

    /** 日期部分格式(默认):yyyyMMdd */
    DATE_PATTERN_DEFAULT("yyyyMMdd"),
    /** 日期部分格式:yyyy-MM-dd */
    DATE_PATTERN_YYYYMMDD("yyyy-MM-dd"),
    /** 日期部分格式:yyyy/MM/dd */
    DATE_PATTERN_YYYYMMDD_SPRIT("yyyy/MM/dd"),
    /** 日期部分格式:yyyy年MM月dd日 */
    DATE_PATTERN_YYYYMMDD_CN("yyyy年MM月dd日"),
    /** 日期部分格式:yyMMdd */
    DATE_PATTERN_YYMMDD("yyMMdd"),
    /** 日期格式: yyyyMM */
    DATE_PATTERN_YYYYMM("yyyyMM"),
    /** 日期+时间完整格式(默认):yyyyMMddHHmmss */
    DATETIME_PATTERN_DEFAULT("yyyyMMddHHmmss"),
    /** 日期+时间完整格式:yyyy-MM-dd HH:mm:ss */
    DATETIME_PATTERN_YYYYMMDDHHMMSS("yyyy-MM-dd HH:mm:ss"),
    /** 日期+时间完整格式:yyyy-MM-ddTHH:mm:ss比如2015-07-10T10:28:28 */
    DATETIME_PATTERN_YYYYMMDDTHHMMSS("yyyy-MM-dd'T'HH:mm:ss"),
    /** 日期+时间完整格式:yyyy年MM月dd日 HH时mm分ss秒 */
    DATETIME_PATTERN_YYYYMMDDHHMMSS_CN("yyyy年MM月dd日 HH时mm分ss秒"),
    /** 特殊日期+时间格式1: yyyy-MM-ddHHmmss */
    DATETIME_PATTERN_SPECIAL1("yyyy-MM-ddHHmmss"),
    /** 特殊日期+时间格式1: yyyy-MM-ddHHmmss 2014-03-05-15.09.21.776650 */
    DATETIME_PATTERN_SPECIAL2("yyyy-MM-dd-HH.mm.ss"),
    /** 时间部分格式(默认):HHmmss */
    TIME_PATTERN_DEFAULT("HHmmss"),
    /** 时间部分格式:HH:mm:ss */
    TIME_PATTERN_HHMMSS("HH:mm:ss"),
    /** 时间部分格式(精确到毫秒):HHmmssSS */
    TIME_PATTERN_HHMMSSSS("HHmmssSS"),
    /** 完整日期+时间格式含毫秒 */
    DATETIME_PATTERN_YYYYMMDDHHMMSSSSS("yyyy-MM-dd HH:mm:ss:SSS");

    private String pattern;

    DateFormatPatterns(String pattern){
        this.pattern = pattern;
    }

    public String getPattern() {
        return pattern;
    }

}



package com.wtdig.utils.date;

import org.apache.commons.lang.time.DateUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 一个扩展了Date类型的功能强大的日期时间类,提供了大量日常所需的便捷日期计算方法<br>
 * 在任何使用Date的地方都可以使用该类<br>
 * 依赖的三方库为{@link org.apache.commons.lang.time.DateUtils apache DateUtils}
 */
public class SuperDate {

    /**
     * 通过Calendar构造当前时间
     *
     * @return
     */
    public static Date now() {
        Calendar cal = Calendar.getInstance();
        return cal.getTime();
    }

    /**
     * 通过Calendar构造当前日期(时分秒都是0)
     *
     * @return
     */
    public static Date nowDateOnly() {
        Calendar cal = Calendar.getInstance();
        return trunc(cal.getTime(), DateField.FieldNumber.DATE);
    }

    /**
     * 转换成sql Date
     *
     * @return
     */
    public static java.sql.Date toSqlDate(Date date) {
        return new java.sql.Date(date.getTime());
    }

    /**
     * 转换成sql Timestamp
     *
     * @return
     */
    public static java.sql.Timestamp toSqlTimestamp(Date date) {
        return new java.sql.Timestamp(date.getTime());
    }

    /**
     * 输出格式化的日期字符串,输出格式为:yyyyMMdd
     *
     * @return 格式后的日期
     */
    public static String formatAsDateString(Date date) {
        return formatDate(date);
    }

    /**
     * 输出格式化的日期时间字符串,输出格式为:yyyyMMddHHmmss
     *
     * @return 格式后的日期时间
     */
    public static String formatAsDateTimeString(Date date) {
        return formatDateTime(date);
    }

    /**
     * 输出格式化的时间字符串,输出格式为:HHmmss
     *
     * @return 格式化后的时间
     */
    public static String formatAsTimeString(Date date) {
        return formatTime(date);
    }

    /**
     * 使用自定义的格式输出相应字符串
     *
     * @param pattern
     * @return
     */
    public static String formatAsString(Date date, DateFormatPatterns pattern) {
        return format(date, pattern);
    }

    /**
     * 天数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addDays(Date date, int amount) {
        return DateUtils.addDays(date, amount);
    }

    /**
     * 年数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addYears(Date date, int amount) {
        return DateUtils.addYears(date, amount);
    }

    /**
     * 月数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addMonths(Date date, int amount) {
        return DateUtils.addMonths(date, amount);
    }

    /**
     * 周数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addWeeks(Date date, int amount) {
        return DateUtils.addWeeks(date, amount);
    }

    /**
     * 时数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addHours(Date date, int amount) {
        return DateUtils.addHours(date, amount);
    }

    /**
     * 分数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addMinutes(Date date, int amount) {
        return DateUtils.addMinutes(date, amount);
    }

    /**
     * 秒数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addSeconds(Date date, int amount) {
        return DateUtils.addSeconds(date, amount);
    }

    /**
     * 毫秒数加减操作
     *
     * @param amount
     * @return
     */
    public static Date addMilliseconds(Date date, int amount) {
        return DateUtils.addMilliseconds(date, amount);
    }

    /**
     * 两个日期之间的天数,返回参数 endDate - beginDate 之间的天数
     *
     * @param beginDate
     * @return 参数 endDate - beginDate 之间的天数
     */
    public static int daysBetween(Date beginDate, Date endDate) {
        long beginTime = trunc(beginDate, DateField.FieldNumber.DATE).getTime();
        long endTime = trunc(endDate, DateField.FieldNumber.DATE).getTime();
        long betweenDays = (endTime - beginTime) / (1000 * 3600 * 24);
        return (int) betweenDays;
    }

    /**
     * 截断
     *
     * @param field
     * @return
     */
    public static Date trunc(Date date, DateField.FieldNumber field) {
        return DateUtils.truncate(date, field.getValue());
    }

    /**
     * 截取日期到天
     *
     * @param date
     * @return
     */
    public static Date truncToDate(Date date) {
        return trunc(date, DateField.FieldNumber.DATE);
    }

    public static Date round(Date date, DateField.FieldNumber field) {
        return DateUtils.round(date, field.getValue());
    }

    /**
     * 月第一天
     *
     * @return
     */
    public static Date firstDayOfMonth(Date date) {
        return trunc(date, DateField.FieldNumber.MONTH);
    }

    /**
     * 月最后一天
     *
     * @return
     */
    public static Date lastDayOfMonth(Date date) {
        return addDays(firstDayOfMonth(addMonths(date, 1)), -1);
    }

    /**
     * 年第一天
     *
     * @return
     */
    public static Date firstDayOfYear(Date date) {
        return trunc(date, DateField.FieldNumber.YEAR);
    }

    /**
     * 年最后一天
     *
     * @return
     */
    public static Date lastDayOfYear(Date date) {
        return addDays(firstDayOfYear(addYears(date, 1)), -1);
    }

    public static boolean isNextMonthOf(Date beginDate, Date endDate) {
        return addMonths(trunc(beginDate, DateField.FieldNumber.MONTH), -1).equals(trunc(endDate, DateField.FieldNumber.MONTH));
    }

    public static boolean isLastMonthOf(Date beginDate, Date endDate) {
        return addMonths(trunc(beginDate, DateField.FieldNumber.MONTH), 1).equals(trunc(endDate, DateField.FieldNumber.MONTH));
    }

    /**
     * 输出当前时间格式化的字符串,输出格式为:yyyyMMddHHmmss
     *
     * @return
     */
    public static String nowDateTimeAsString() {
        return formatAsDateTimeString(now());
    }

    /**
     * 输出当前时间(仅日期部分)格式化的字符串,输出格式为:yyyyMMdd
     *
     * @return
     */
    public static String nowDateAsString() {
        return formatAsDateString(now());
    }

    /**
     * 输出仅日期部分格式化的字符串,输出格式为:yyyyMMdd
     *
     * @return
     */
    public static String formatDate(Date date) {
        return format(date, DateFormatPatterns.DATE_PATTERN_DEFAULT);
    }

    /**
     * 输出完整的日期的格式化的字符串,输出格式为:yyyyMMddHHmmss
     *
     * @return
     */
    public static String formatDateTime(Date date) {
        return format(date, DateFormatPatterns.DATETIME_PATTERN_DEFAULT);
    }

    public static String formatTime(Date date) {
        return format(date, DateFormatPatterns.TIME_PATTERN_DEFAULT);
    }

    public static String format(Date date, DateFormatPatterns pattern) {
        if (date == null || pattern == null) {
            return null;
        }
        SimpleDateFormat dateFromat = new SimpleDateFormat(pattern.getPattern());
        return dateFromat.format(date);
    }

    /**
     * 字符串转日期,输入字符串格式必须为:yyyyMMdd
     *
     * @param dateValue
     * @return
     */
    public static Date parseDate(String dateValue) {
        if (dateValue.length() != DateFormatPatterns.DATE_PATTERN_DEFAULT.getPattern().length()) {
            throw new IllegalArgumentException(String.format("param format is illegal, format must be %s.",
                    DateFormatPatterns.DATE_PATTERN_DEFAULT.getPattern()));
        }
        return parse(dateValue, DateFormatPatterns.DATE_PATTERN_DEFAULT);
    }

    /**
     * 字符串转日期,输入字符串格式必须为:yyyyMMddHHmmss
     *
     * @param dateValue
     * @return
     */
    public static Date parseDateTime(String dateValue) {
        if (dateValue.length() != DateFormatPatterns.DATETIME_PATTERN_DEFAULT.getPattern().length()) {
            throw new IllegalArgumentException(String.format("param format is illegal, format must be %s.",
                    DateFormatPatterns.DATETIME_PATTERN_DEFAULT.getPattern()));
        }
        return parse(dateValue, DateFormatPatterns.DATETIME_PATTERN_DEFAULT);
    }

    public static Date parse(String dateValue, DateFormatPatterns pattern) {
        if (dateValue == null || pattern == null) {
            return null;
        }
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern.getPattern());
            return dateFormat.parse(dateValue);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static long getTime(DateField dateField) {
        Calendar c = Calendar.getInstance();
        c.set(dateField.getYear(), dateField.getMonth().getValue(), dateField.getDay(), dateField.getHour(),
                dateField.getMinute(), dateField.getSecond());
        return c.getTime().getTime();
    }

    /**
     * 是否是月第一天
     *
     * @param date
     * @return
     */
    public static boolean isFirstDayOfMonth(Date date) {
        Calendar testDateCal = Calendar.getInstance();
        testDateCal.setTime(date);
        int dayOfMonth = testDateCal.get(Calendar.DAY_OF_MONTH);
        return dayOfMonth == 1;
    }

    /**
     * 是否是月最后一天
     *
     * @param date
     * @return
     */
    public static boolean isLastDayOfMonth(Date date) {
        Date nextDay = addDays(date, 1);
        return isFirstDayOfMonth(nextDay);
    }

    public static int dayOfMonth(Date date) {
        Calendar testDateCal = Calendar.getInstance();
        testDateCal.setTime(date);
        return testDateCal.get(Calendar.DAY_OF_MONTH);
    }

    public static int dayOfWeek(Date date) {
        Calendar testDateCal = Calendar.getInstance();
        testDateCal.setTime(date);
        return testDateCal.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 判断日期是否是工作日
     *
     * @param date
     * @return
     * @throws Exception
     */
    public static boolean isWorkingDay(Date date) {
        // 先查这天是周几
        int day = dayOfWeek(date);
        if (day >= Calendar.MONDAY && day <= Calendar.FRIDAY) {
            return true;
        }
        return false;
    }

    /**
     * 数据日期+时间精确到3位毫秒
     *
     * @param date
     * @return
     */
    public static String formatDateAccurate(Date date) {
        return format(date, DateFormatPatterns.DATETIME_PATTERN_YYYYMMDDHHMMSSSSS);
    }

    /**
     * 获取两个日期之间的日期
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 日期集合
     */
    public static List<Date> getBetweenDates(Date start, Date end) {
        List<Date> result = new ArrayList<Date>();
        Calendar tempStart = Calendar.getInstance();
        tempStart.setTime(start);
        tempStart.add(Calendar.DAY_OF_YEAR, 1);

        Calendar tempEnd = Calendar.getInstance();
        tempEnd.setTime(end);
        while (tempStart.before(tempEnd)) {
            result.add(tempStart.getTime());
            tempStart.add(Calendar.DAY_OF_YEAR, 1);
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(getBetweenDates(parseDate("20170512"), parseDate("20170513")));
        System.out.println(getBetweenDates(parseDate("20170512"), parseDate("20170531")));
        System.out.println(getBetweenDates(parseDate("20170512"), parseDate("20170602")));
    }
}

备注:

简易版代码生成器参考: 通用代码生成器

⚠️ **GitHub.com Fallback** ⚠️