SpringBoot e-commerce project mall (40K + STAR) address: github.com/macrozheng/…

Abstract

I believe that the ORM framework used by many friends in the project is MyBatis. If you use MyBatis alone to operate the database, you need to write a lot of SQL queries for single tables by hand. At this time we often choose an enhancement tool to implement these single table CRUD operations, here recommend a good tool MyBatis-Plus!

MyBatis – Plus introduction

MyBatis-Plus (MP for short) is a MyBatis enhancement tool, on the basis of MyBatis only do enhancement do not change, to simplify the development and improve efficiency. MyBatis-Plus provides code generator, you can generate controller, service, mapper, model, mapper. XML code, at the same time provides rich CRUD operation methods, help us free hands!

MyBatis – Plus integration

First we need to integrate MyBatis-Plus in the SpringBoot project, and then we will explain how to use it in detail!

  • inpom.xmlTo add related dependencies, mainly MyBatis-Plus, MyBatis-Plus Generator and Velocity template engine;
<dependencies>
    <! - Mybatis - Plus depend on -- -- >
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.2 rainfall distribution on 10-12</version>
    </dependency>
    <! --Mybatis -- Plus code generator -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.3.2 rainfall distribution on 10-12</version>
    </dependency>
    <! --Velocity template generation engine -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.2</version>
    </dependency>
</dependencies>
Copy the code
  • Configuration file in SpringBootapplication.ymlAdd the following configuration, configure the data source and MyBatis-Plus;
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mall? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml Mapper.xml path
  global-config:
    db-config:
      id-type: auto The global default primary key type is set to increment
  configuration:
    auto-mapping-behavior: partial Only non-nested ResultMaps are automatically mapped
    map-underscore-to-camel-case: true Enable automatic hump naming rule mapping
Copy the code
  • Add MyBatis-Plus Java configuration using@MapperScanNote Configure the Mapper interface path that needs to be scanned. MyBatis-Plus has its own paging function. Configure the paging plug-inPaginationInterceptor.
/** * Created by macro on 2019/4/8. */
@Configuration
@MapperScan("com.macro.mall.tiny.modules.*.mapper")
public class MyBatisConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor(a) {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        returnpaginationInterceptor; }}Copy the code

Code generator

MyBatis-Plus provides a code generator, you can one-click generation of controller, service, mapper, model, mapper.xml code, very convenient!

  • First we create the code generator classMyBatisPlusGenerator, run it directlymainMethod to generate the relevant code;
/** * MyBatisPlus code generator * Created by macro on 2020/8/20
public class MyBatisPlusGenerator {

    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir") + "/mall-tiny-plus";
        String moduleName = scanner("Module name");
        String[] tableNames = scanner("Table name, separated by multiple Commas").split(",");
        // Code generator
        AutoGenerator autoGenerator = new AutoGenerator();
        autoGenerator.setGlobalConfig(initGlobalConfig(projectPath));
        autoGenerator.setDataSource(initDataSourceConfig());
        autoGenerator.setPackageInfo(initPackageConfig(moduleName));
        autoGenerator.setCfg(initInjectionConfig(projectPath, moduleName));
        autoGenerator.setTemplate(initTemplateConfig());
        autoGenerator.setStrategy(initStrategyConfig(tableNames));
        autoGenerator.setTemplateEngine(new VelocityTemplateEngine());
        autoGenerator.execute();
    }

    /** * Read the console contents */
    private static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        System.out.println(("Please enter" + tip + ":"));
        if (scanner.hasNext()) {
            String next = scanner.next();
            if (StrUtil.isNotEmpty(next)) {
                returnnext; }}throw new MybatisPlusException("Please enter the correct one" + tip + "!");
    }

    /** * Initialize the global configuration */
    private static GlobalConfig initGlobalConfig(String projectPath) {
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(projectPath + "/src/main/java");
        globalConfig.setAuthor("macro");
        globalConfig.setOpen(false);
        globalConfig.setSwagger2(true);
        globalConfig.setBaseResultMap(true);
        globalConfig.setFileOverride(true);
        globalConfig.setDateType(DateType.ONLY_DATE);
        globalConfig.setEntityName("%s");
        globalConfig.setMapperName("%sMapper");
        globalConfig.setXmlName("%sMapper");
        globalConfig.setServiceName("%sService");
        globalConfig.setServiceImplName("%sServiceImpl");
        globalConfig.setControllerName("%sController");
        return globalConfig;
    }

    /** * Initializes data source configuration */
    private static DataSourceConfig initDataSourceConfig(a) {
        Props props = new Props("generator.properties");
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl(props.getStr("dataSource.url"));
        dataSourceConfig.setDriverName(props.getStr("dataSource.driverName"));
        dataSourceConfig.setUsername(props.getStr("dataSource.username"));
        dataSourceConfig.setPassword(props.getStr("dataSource.password"));
        return dataSourceConfig;
    }

    /** * Initialize package configuration */
    private static PackageConfig initPackageConfig(String moduleName) {
        Props props = new Props("generator.properties");
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setModuleName(moduleName);
        packageConfig.setParent(props.getStr("package.base"));
        packageConfig.setEntity("model");
        return packageConfig;
    }

    /** * Initializes template configuration */
    private static TemplateConfig initTemplateConfig(a) {
        TemplateConfig templateConfig = new TemplateConfig();
        // You can configure the Controller, service, and Entity templates
        // The mapper. XML template needs to be configured separately
        templateConfig.setXml(null);
        return templateConfig;
    }

    /** * Initializes policy configuration */
    private static StrategyConfig initStrategyConfig(String[] tableNames) {
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setEntityLombokModel(true);
        strategyConfig.setRestControllerStyle(true);
        // Wildcard mode can be enabled when the table name contains asterisks
        if (tableNames.length == 1 && tableNames[0].contains("*")) {
            String[] likeStr = tableNames[0].split("_");
            String likePrefix = likeStr[0] + "_";
            strategyConfig.setLikeTable(new LikeTable(likePrefix));
        } else {
            strategyConfig.setInclude(tableNames);
        }
        return strategyConfig;
    }

    /** * Initializes the custom configuration */
    private static InjectionConfig initInjectionConfig(String projectPath, String moduleName) {
        // Custom configuration
        InjectionConfig injectionConfig = new InjectionConfig() {
            @Override
            public void initMap(a) {
                // Can be used to customize attributes}};// The template engine is Velocity
        String templatePath = "/templates/mapper.xml.vm";
        // Customize the output configuration
        List<FileOutConfig> focList = new ArrayList<>();
        // Custom configurations are printed first
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // Customize the output file name. If your Entity has a prefix or suffix, note that the XML name will change accordingly!!
                return projectPath + "/src/main/resources/mapper/" + moduleName
                        + "/" + tableInfo.getEntityName() + "Mapper"+ StringPool.DOT_XML; }}); injectionConfig.setFileOutConfigList(focList);returninjectionConfig; }}Copy the code
  • Then, inresourcesAdd a configuration file to the directorygenerator.properties, add the data source configuration of the code generator and the name of the base package where the business code is stored;
dataSource.url=jdbc:mysql://localhost:3306/mall? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
dataSource.driverName=com.mysql.cj.jdbc.Driver
dataSource.username=root
dataSource.password=root
package.base=com.macro.mall.tiny.modules
Copy the code
  • Careful friends can findMyBatisPlusGeneratorA lot of configuration code did not add annotations, in fact, MyBatis-Plus source code in Chinese annotations is very perfect, just view the source code can, here excerpt a paragraphDataSourceConfigSource code;
/** * Database configuration **@author YangHu, hcl
 * @since2016/8/30 * /
@Data
@Accessors(chain = true)
public class DataSourceConfig {

    /** * database information */
    private IDbQuery dbQuery;
    /** * Database type */
    private DbType dbType;
    /** * PostgreSQL schemaName */
    private String schemaName;
    /** * Type conversion */
    private ITypeConvert typeConvert;
    /** * keyword processor *@since3.3.2 rainfall distribution on 10-12 * /
    private IKeyWordsHandler keyWordsHandler;
    /** * driver connection URL */
    private String url;
    /** * Driver name */
    private String driverName;
    /** * Database connection user name */
    private String username;
    /** * Database connection password */
    private String password;
    
    // omit some code......

}
Copy the code
  • Code generators support two modes, one that generates single-table code, such as just generatepms_brandThe table code can be entered firstpmsAfter the inputpms_brand;

  • Generate a list of single-table code structures;

  • Another type of code that directly generates the entire module requires a wildcard*Such as generateumsThe module code can be entered firstumsAfter the inputums_*;

  • Generate an overview of the entire module code structure.

Customize the generated template

MyBatis-Plus uses a templating engine to generate code, which supports the Velocity (default), Freemarker, and Beetl templating engine.

  • First we can find the default template from the source of MyBatis-Plus Generator dependency package and copy it to the projectresources/templatesDirectory;

  • inMyBatisPlusGeneratorClass forTemplateConfigConfigure the path of each template.
/** * MyBatisPlus code generator * Created by macro on 2020/8/20
public class MyBatisPlusGenerator {
    
    /** * Initializes template configuration */
    private static TemplateConfig initTemplateConfig(a) {
        TemplateConfig templateConfig = new TemplateConfig();
        // You can configure the Controller, service, and Entity templates
        templateConfig.setEntity("templates/entity.java");
        templateConfig.setMapper("templates/mapper.java");
        templateConfig.setController("templates/controller.java");
        templateConfig.setService("templates/service.java");
        templateConfig.setServiceImpl("templates/serviceImpl.java");
        // The mapper. XML template needs to be configured separately
        templateConfig.setXml(null);
        returntemplateConfig; }}Copy the code
  • To customize the template, during the customization process we can find a lot of built-in variables for output to the template, hereservice.java.vmTemplates are examples, such aspackage,tableThese variables;
package ${package.Service}; import ${package.Entity}.${entity}; import ${superServiceClassPackage}; /** * <p> * $! $${date} */ #if(${kotlin}) interface ${table.servicename} : ${superServiceClass}<${entity}> #else public interface ${table.serviceName} extends ${superServiceClass}<${entity}> { } #endCopy the code
  • Knowing where these variables come from will help us customize our templates, because these variables are actually fromAbstractTemplateEnginethegetObjectMapMethod, specific variable role can refer to the source code.
/** * Template engine abstract class **@author hubin
 * @sinceThe 2018-01-10 * /
public abstract class AbstractTemplateEngine {
        /** * Render object MAP information **@paramTableInfo Table information object *@return ignore
         */
        public Map<String, Object> getObjectMap(TableInfo tableInfo) {
            Map<String, Object> objectMap = new HashMap<>(30);
            ConfigBuilder config = getConfigBuilder();
            if (config.getStrategyConfig().isControllerMappingHyphenStyle()) {
                objectMap.put("controllerMappingHyphenStyle", config.getStrategyConfig().isControllerMappingHyphenStyle());
                objectMap.put("controllerMappingHyphen", StringUtils.camelToHyphen(tableInfo.getEntityPath()));
            }
            objectMap.put("restControllerStyle", config.getStrategyConfig().isRestControllerStyle());
            objectMap.put("config", config);
            objectMap.put("package", config.getPackageInfo());
            GlobalConfig globalConfig = config.getGlobalConfig();
            objectMap.put("author", globalConfig.getAuthor());
            objectMap.put("idType", globalConfig.getIdType() == null ? null : globalConfig.getIdType().toString());
            objectMap.put("logicDeleteFieldName", config.getStrategyConfig().getLogicDeleteFieldName());
            objectMap.put("versionFieldName", config.getStrategyConfig().getVersionFieldName());
            objectMap.put("activeRecord", globalConfig.isActiveRecord());
            objectMap.put("kotlin", globalConfig.isKotlin());
            objectMap.put("swagger2", globalConfig.isSwagger2());
            objectMap.put("date".new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
            objectMap.put("table", tableInfo);
            objectMap.put("enableCache", globalConfig.isEnableCache());
            objectMap.put("baseResultMap", globalConfig.isBaseResultMap());
            objectMap.put("baseColumnList", globalConfig.isBaseColumnList());
            objectMap.put("entity", tableInfo.getEntityName());
            objectMap.put("entitySerialVersionUID", config.getStrategyConfig().isEntitySerialVersionUID());
            objectMap.put("entityColumnConstant", config.getStrategyConfig().isEntityColumnConstant());
            objectMap.put("entityBuilderModel", config.getStrategyConfig().isEntityBuilderModel());
            objectMap.put("chainModel", config.getStrategyConfig().isChainModel());
            objectMap.put("entityLombokModel", config.getStrategyConfig().isEntityLombokModel());
            objectMap.put("entityBooleanColumnRemoveIsPrefix", config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix());
            objectMap.put("superEntityClass", getSuperClassName(config.getSuperEntityClass()));
            objectMap.put("superMapperClassPackage", config.getSuperMapperClass());
            objectMap.put("superMapperClass", getSuperClassName(config.getSuperMapperClass()));
            objectMap.put("superServiceClassPackage", config.getSuperServiceClass());
            objectMap.put("superServiceClass", getSuperClassName(config.getSuperServiceClass()));
            objectMap.put("superServiceImplClassPackage", config.getSuperServiceImplClass());
            objectMap.put("superServiceImplClass", getSuperClassName(config.getSuperServiceImplClass()));
            objectMap.put("superControllerClassPackage", verifyClassPacket(config.getSuperControllerClass()));
            objectMap.put("superControllerClass", getSuperClassName(config.getSuperControllerClass()));
            returnObjects.isNull(config.getInjectionConfig()) ? objectMap : config.getInjectionConfig().prepareObjectMap(objectMap); }}Copy the code

CRUD operations

The power of MyBatis-Plus lies not only in its code generation capabilities, but also in its rich CRUD methods, allowing us to implement single-table CRUD with almost no hand-written SQL!

  • That we generated beforePmsBrandMapperInterface due to inheritanceBaseMapperInterface, directly with various CRUD methods;
/** * <p> ** Mapper interface * </p> **@author macro
 * @sinceThe 2020-08-20 * /
public interface PmsBrandMapper extends BaseMapper<PmsBrand> {}Copy the code
  • Let’s seeBaseMapperIn the method, can basically meet our daily needs;

  • That we generated beforePmsBrandServiceInterface due to inheritanceIServiceInterface, also has various CRUD methods;
/** ** <p> **@author macro
 * @sinceThe 2020-08-20 * /
public interface PmsBrandService extends IService<PmsBrand> {}Copy the code
  • You can look at the ratioBaseMapperMore abundant in;

  • With theseIServiceandBaseMapperProvide these methods, we single table query almost no handwritten SQL implementation, using MyBatis-Plus implementation beforePmsBrandControllerThe method is more relaxed!
/** ** <p> * brand table front-end controller * </p> **@author macro
 * @sinceThe 2020-08-20 * /
@API (tags = "PmsBrandController", description = "Product Brand Management ")
@RestController
@RequestMapping("/brand")
public class PmsBrandController {

    private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);

    @Autowired
    private PmsBrandService brandService;

    @apiOperation (" Get all brand list ")
    @RequestMapping(value = "/listAll", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<List<PmsBrand>> getBrandList() {
        return CommonResult.success(brandService.list());
    }

    @apiOperation (" Add brand ")
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
        CommonResult commonResult;
        boolean result = brandService.save(pmsBrand);
        if (result) {
            commonResult = CommonResult.success(pmsBrand);
            LOGGER.debug("createBrand success:{}", pmsBrand);
        } else {
            commonResult = CommonResult.failed("Operation failed");
            LOGGER.debug("createBrand failed:{}", pmsBrand);
        }
        return commonResult;
    }

    @apiOperation (" Update the specified brand name ")
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult updateBrand(@RequestBody PmsBrand pmsBrand) {
        CommonResult commonResult;
        boolean result = brandService.updateById(pmsBrand);
        if (result) {
            commonResult = CommonResult.success(pmsBrand);
            LOGGER.debug("updateBrand success:{}", pmsBrand);
        } else {
            commonResult = CommonResult.failed("Operation failed");
            LOGGER.debug("updateBrand failed:{}", pmsBrand);
        }
        return commonResult;
    }

    @apiOperation (" delete brand with specified id ")
    @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult deleteBrand(@PathVariable("id") Long id) {
        boolean result = brandService.removeById(id);
        if (result) {
            LOGGER.debug("deleteBrand success :id={}", id);
            return CommonResult.success(null);
        } else {
            LOGGER.debug("deleteBrand failed :id={}", id);
            return CommonResult.failed("Operation failed"); }}@apiOperation (" paging query brand list ")
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                        @ ApiParam (" page ") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "3")
                                                        @apiparam (" number per page ") Integer pageSize) {
        Page<PmsBrand> page = new Page<>(pageNum, pageSize);
        Page<PmsBrand> pageResult = brandService.page(page);
        return CommonResult.success(CommonPage.restPage(pageResult));
    }

    @apiOperation (" Get brand details for specified ID ")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
        returnCommonResult.success(brandService.getById(id)); }}Copy the code

Project source code address

Github.com/macrozheng/…

In this paper, making github.com/macrozheng/… Already included, welcome everyone Star!