从0开始搭建一个生产级SpringBoot2.0.X项目(二)SpringBoot应用连接数据库集成mybatis-plus
前言
最近有个想法想整理一个内容比较完整springboot项目初始化Demo。
连接Oracle数据库集成mybatis-plus,自定义WrapperFactory。配置代码生成器
一、引入jar包
<!--oracle驱动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.ojdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
</dependency>
<!-- oracle8 所需的字符集 -->
<dependency>
<groupId>cn.easyproject</groupId>
<artifactId>orai18n</artifactId>
<version>12.1.0.2.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<!--mybatis-plus-->
二、新建配置文件application-dev.yaml
以根据不同的环境配置不同的属性文件,application-{profile}
表示环境名称。
例如,application-dev.
yaml用于开发环境,application-prod.
yaml用于生产环境。
增加数据库配置 hikari连接池配置
spring:
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@//127.0.0.1:1521/orcl
username: murg_demo
password: 654321
hikari:
connection-test-query: SELECT 1 FROM DUAL
minimum-idle: 10
maximum-pool-size: 50
idle-timeout: 120000
max-lifetime: 180000
connection-timeout: 18000
logging:
level:
org.springframework.security: debug
启动服务看服务是否正常启动
三、自定义MyBatis 构造对象加工厂。
用于规定查询返回值格式 主要用于_处理和字段大小写规定
package com.murg.bootdemo.interceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.util.Map;
/**
* 自定义ObjectWrapperFactory 结果处理 去除_ 字段大写转小写
*/
public class MapWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
return object instanceof Map;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new MyMapWrapper(metaObject,(Map)object);
}
}
package com.murg.bootdemo.interceptor;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.MapWrapper;
import java.util.Map;
/**
* mybatis 结果处理 去除_ 字段大写转小写
*/
public class MyMapWrapper extends MapWrapper {
public MyMapWrapper(MetaObject metaObject, Map<String, Object> map) {
super(metaObject, map);
}
@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping
&& ((name.charAt(0) >= 'A' && name.charAt(0) <= 'Z')
|| name.contains("_"))) {
return underlineToCamelhump(name);
}
return name;
}
/**
* 将下划线风格替换为驼峰风格
* @param inputString
* @return
*/
public String underlineToCamelhump(String inputString) {
StringBuilder sb = new StringBuilder();
boolean nextUpperCase = false;
for (int i = 0; i < inputString.length(); i++) {
char c = inputString.charAt(i);
if (c == '_') {
if (sb.length() > 0) {
nextUpperCase = true;
}
} else {
if (nextUpperCase) {
if (NumberUtils.isNumber(String.valueOf(c))) {
sb.append("_").append(c);
} else {
sb.append(Character.toUpperCase(c));
}
nextUpperCase = false;
} else {
sb.append(Character.toLowerCase(c));
}
}
}
return sb.toString();
}
}
四、自定义WrapperFactoryConverter
以上配置好WrapperFactory后启动会报错
提示找不到合适的converter将string转化为ObjectWrapperFactory对象
缺少对应的converter,springboot不会提供反射机制来构建一个对象的converter,需要手写一个
package com.murg.bootdemo.interceptor;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
@ConfigurationPropertiesBinding
public class ObjectWrapperFactoryConverter implements Converter<String, ObjectWrapperFactory> {
@Override
public ObjectWrapperFactory convert(String source) {
try {
return (ObjectWrapperFactory) Class.forName(source).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
五、整合MyBatisPlus代码生成器 mybatis-plus-generator配合freemaker引擎
5.1下面这些文件直接创建在resources/generator1/templates
的目录下,把内容复制进去就行了。这些文件就是代码生成的格式模板。
controller.java.ftl
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* <p>
* ${table.comment!} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
}
</#if>
entity.java.ftl
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Getter;
import lombok.Setter;
<#if chainModel>
import lombok.experimental.Accessors;
</#if>
</#if>
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Getter
@Setter
<#if chainModel>
@Accessors(chain = true)
</#if>
</#if>
<#if table.convert>
@TableName("${schemaName}${table.name}")
</#if>
<#if swagger>
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#elseif entitySerialVersionUID>
public class ${entity} implements Serializable {
<#else>
public class ${entity} {
</#if>
<#if entitySerialVersionUID>
private static final long serialVersionUID = 1L;
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
<#if swagger>
@ApiModelProperty("${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if field.versionField>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if field.logicDeleteField>
@TableLogic
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if chainModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if chainModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
public Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName}=" + ${field.propertyName} +
<#else>
", ${field.propertyName}=" + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}
entity.kt.ftl
package ${package.Entity}
<#list table.importPackages as pkg>
import ${pkg}
</#list>
<#if swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
/**
* <p>
* ${table.comment}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if table.convert>
@TableName("${schemaName}${table.name}")
</#if>
<#if swagger>
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
<#if superEntityClass??>
class ${entity} : ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
class ${entity} : Model<${entity}>() {
<#elseif entitySerialVersionUID>
class ${entity} : Serializable {
<#else>
class ${entity} {
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
<#if swagger>
@ApiModelProperty("${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType ??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if field.versionField>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if field.logicDeleteField>
@TableLogic
</#if>
<#if field.propertyType == "Integer">
var ${field.propertyName}: Int? = null
<#else>
var ${field.propertyName}: ${field.propertyType}? = null
</#if>
</#list>
<#-- ---------- END 字段循环遍历 ---------->
<#if entityColumnConstant>
companion object {
<#list table.fields as field>
const val ${field.name?upper_case} : String = "${field.name}"
</#list>
}
</#if>
<#if activeRecord>
override fun pkVal(): Serializable? {
<#if keyPropertyName??>
return ${keyPropertyName}
<#else>
return null
</#if>
}
</#if>
override fun toString(): String {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName}=" + ${field.propertyName} +
<#else>
", ${field.propertyName}=" + ${field.propertyName} +
</#if>
</#list>
"}"
}
}
mapper.java.ftl
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
<#if mapperAnnotation>
import org.apache.ibatis.annotations.Mapper;
</#if>
/**
* <p>
* ${table.comment!} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if mapperAnnotation>
@Mapper
</#if>
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
</#if>
mapper.xml.ftl
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
<#if enableCache>
<!-- 开启二级缓存 -->
<cache type="${cacheClassName}"/>
</#if>
<#if baseResultMap>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
<id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
</resultMap>
</#if>
<#if baseColumnList>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
<#list table.commonFields as field>
${field.columnName},
</#list>
${table.fieldNames}
</sql>
</#if>
</mapper>
service.java.ftl
package ${package.Service};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
/**
* <p>
* ${table.comment!} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/
@Service
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> {
}
5.2创建生成代码类CodeGenerator
package com.murg.bootdemo.util;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.TemplateType;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Generator生成代码
**/
public class CodeGenerator {
public static void main(String[] args) {
generator();
}
public static void generator() {
final String[] moduleName = new String[1];
FastAutoGenerator.create(new DataSourceConfig.Builder("jdbc:oracle:thin:@//127.0.0.1:1521/orcl","murg_test","654321"))
// 全局配置
.globalConfig((scanner, builder) -> builder.author(StringUtils.defaultString(getGitUsername(), "codeGenerator"))
.fileOverride().outputDir(System.getProperty("user.dir") +"/src/main/java").disableOpenDir()
.enableSwagger().dateType(DateType.ONLY_DATE))
// 包配置
.packageConfig((scanner, builder) -> builder.parent("com.murg.bootdemo." + scanner.apply("请输入包名(从"
+ "com.murg.bootdemo."
+ "开始 )?")).xml("mapper")
.serviceImpl("service"))
.templateConfig((builder -> builder.disable(TemplateType.SERVICE, TemplateType.CONTROLLER).serviceImpl("/templates/service.java")
))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")
.toUpperCase()))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.serviceBuilder().convertServiceImplFileName((entityName -> entityName + ConstVal.SERVICE))
.entityBuilder().enableLombok().idType(IdType.INPUT).build()).templateEngine(new FreemarkerTemplateEngine())
//.addIgnoreColumns("prseno") 排除某个字段
.execute();
}
private static String getGitUsername() {
Runtime runtime = Runtime.getRuntime();
Process exec;
try {
exec = runtime.exec("git config user.name");
} catch (IOException e) {
e.printStackTrace();
return null;
}
InputStreamReader inputStreamReader = new InputStreamReader(exec.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
try {
return bufferedReader.readLine();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
5.3运行CodeGenerator 生成代码
点击左侧按钮 运行main方法控制台输出内容
按照控制台输出内容输入包名和表名,多个表名用英文逗号分隔,会在输入的包下生成文件
六、测试服务数据库连接 获取TT26相关数据
6.1启动类增加注解MapperScan 指定mapper扫描位置
@MapperScan("com.murg.bootdemo.**.mapper")
package com.murg.bootdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@MapperScan("com.murg.bootdemo.**.mapper")
@SpringBootApplication
public class BootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(BootdemoApplication.class, args);
}
}
6.2创建测试接口controller
引入Tt26Service 调用getOne方法查询数据库获取Tt26数据
getOne方法为MyBatis-Plus 通用IService使用不了解的同学可以了解一下MyBatis-Plus用法。
package com.murg.bootdemo.business.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.murg.bootdemo.business.entity.Tt26;
import com.murg.bootdemo.business.service.Tt26Service;
import com.murg.bootdemo.common.WebResult;
import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@RestController
public class TestRestController {
private final RestTemplate restTemplate;
private final Tt26Service tt26Service;
@RequestMapping(value = "/getTt26", method = RequestMethod.GET)
public Tt26 getTt26(@RequestParam String code){
return tt26Service.getOne(Wrappers.<Tt26>lambdaQuery().eq(Tt26::getCode,code))
}
}
启动服务访问接口测试结果