SpringBoot整合Druid+全局事務管理+Mybatis-Plus+代碼生成器
在springboot開發當中,Druid,全局事務管理,代碼生成器都是非常實用的,特此記錄下整合的過程
整合Druid連接池
springboot默認的連接池是:HikariCP,但是Druid的功能相對來說比較全面。
數據庫連接池瞭解和常用連接池對比
Druid連接池官網
第一步:引入相關JAR
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
第二步:配置相關參數
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
name: 數據源名稱
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf8&useSSL=false
# 連接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat,wall,slf4j,config
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
web-stat-filter:
enabled: true
url-pattern: "/"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
stat-view-servlet:
enabled: true
url-pattern: "/druid/*"
login-username: admin # 登錄賬號 不設置就不需要登錄就可以訪問druid可視化界面
login-password: 123456 # 登錄密碼
reset-enable: false
allow: "" # 白名單 表示所有
deny: 192.168.1.12 # 黑名單
第三步:在瀏覽器當中輸入:http://127.0.0.1:8080/druid/index.html 即可進入可視化界面
全局事務管理器
springboot當中添加事務直接使用註解@Transactional 即可,但是每個方法都要添加比較麻煩,可以直接通過切面的方式添加一個全局的事務管理器。注意事項是,要注意方法名開頭的問題
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
/**
* 配置全局事務的切點爲service層的所有方法 AOP切面表達式 可參考(https://blog.csdn.net/ycf921244819/article/details/106599489)
* TODO 設置service層所在位置
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* cn.hjljy.fastboot..*.service..*.*(..))";
/**
* 注入事務管理器
*/
@Autowired
private TransactionManager transactionManager;
/**
* 配置事務攔截器
*/
@Bean
public TransactionInterceptor txAdvice() {
RuleBasedTransactionAttribute txAttrRequired = new RuleBasedTransactionAttribute();
txAttrRequired.setName("REQUIRED事務");
//設置事務傳播機制,默認是PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務
txAttrRequired.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//設置異常回滾爲Exception 默認是RuntimeException
List<RollbackRuleAttribute> rollbackRuleAttributes = new ArrayList<>();
rollbackRuleAttributes.add(new RollbackRuleAttribute(Exception.class));
txAttrRequired.setRollbackRules(rollbackRuleAttributes);
RuleBasedTransactionAttribute txAttrRequiredReadOnly = new RuleBasedTransactionAttribute();
txAttrRequiredReadOnly.setName("SUPPORTS事務");
//設置事務傳播機制,PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行
txAttrRequiredReadOnly.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
//設置異常回滾爲Exception 默認是RuntimeException
txAttrRequiredReadOnly.setRollbackRules(rollbackRuleAttributes);
txAttrRequiredReadOnly.setReadOnly(true);
/*事務管理規則,聲明具備事務管理的方法名*/
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
//方法名規則限制,必須以下列開頭纔會加入事務管理當中
source.addTransactionalMethod("add*", txAttrRequired);
source.addTransactionalMethod("save*", txAttrRequired);
source.addTransactionalMethod("create*", txAttrRequired);
source.addTransactionalMethod("insert*", txAttrRequired);
source.addTransactionalMethod("submit*", txAttrRequired);
source.addTransactionalMethod("del*", txAttrRequired);
source.addTransactionalMethod("remove*", txAttrRequired);
source.addTransactionalMethod("update*", txAttrRequired);
source.addTransactionalMethod("exec*", txAttrRequired);
source.addTransactionalMethod("set*", txAttrRequired);
//對於查詢方法,根據實際情況添加事務管理 可能存在查詢多個數據時,已查詢出來的數據剛好被改變的情況
source.addTransactionalMethod("get*", txAttrRequiredReadOnly);
source.addTransactionalMethod("select*", txAttrRequiredReadOnly);
source.addTransactionalMethod("query*", txAttrRequiredReadOnly);
source.addTransactionalMethod("find*", txAttrRequiredReadOnly);
source.addTransactionalMethod("list*", txAttrRequiredReadOnly);
source.addTransactionalMethod("count*", txAttrRequiredReadOnly);
source.addTransactionalMethod("is*", txAttrRequiredReadOnly);
return new TransactionInterceptor(transactionManager, source);
}
/**
* 設置切面
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
整合Mybatis-Plus
第一步:引入JAR包
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
第二步:添加配置信息
mybatis-plus:
mapper-locations: classpath:mapper/*.xml #xml所在位置 不設置默認是在mapper類同級
configuration:
mapUnderscoreToCamelCase: true # 開啓駝峯匹配 默認爲true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印sql語句和入參數據
global-config:
db-config:
logic-delete-value: 1 #邏輯刪除 配合@TableLogic註解
logic-not-delete-value: 0 #邏輯不刪除
update-strategy: not_null # 更新時字段如果爲null,就不進行更新該字段。
insert-strategy: not_null # 插入時如果字段爲null,就不插入數據,建議數據庫表字段設置默認值
第三步:添加分頁和mapper掃描
@Configuration
@MapperScan("cn.hjljy.fastboot.mapper")
public class MybatisPlusConfiguration {
/**
* mybatis-plus分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
//設置分頁數據庫類型
page.setDbType(DbType.MYSQL);
page.setDialect(new MySqlDialect());
//優化count sql
page.setCountSqlParser(new JsqlParserCountOptimize(true));
//設置每頁最大值
page.setLimit(999L);
return page;
}
}
第四步:創建一個Mapper類繼承BaseMapper,就可以簡單使用了。
可以參考官方文檔入門:https://mp.baomidou.com/guide/quick-start.html
整合代碼生成器
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率
考慮到dto和po在大部分情況下字段都是一樣的,官方未提供DTO,所以可以拷貝一份entity.java.vm修改爲dto.java.vm放在resources目錄下面。然後根據自定義提示進行修改。
具體結果如下:
package $!{cfg.dtoPackage};
#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.EqualsAndHashCode;
#if(${chainModel})
import lombok.experimental.Accessors;
#end
#end
/**
* <p>
* $!{table.comment}
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${entityLombokModel})
@Data
#if(${superEntityClass})
@EqualsAndHashCode(callSuper = true)
#else
@EqualsAndHashCode(callSuper = false)
#end
#if(${chainModel})
@Accessors(chain = true)
#end
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}Dto對象", description="$!{table.comment}")
#end
#if(${superEntityClass})
public class ${entity}Dto extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity}Dto extends Model<${entity}> {
#else
public class ${entity}Dto implements Serializable {
#end
#if(${entitySerialVersionUID})
private static final long serialVersionUID=1L;
#end
## ---------- BEGIN 字段循環遍歷 ----------
#foreach($field in ${table.fields})
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
#if(${swagger2})
@ApiModelProperty(value = "${field.comment}")
#else
/**
* ${field.comment}
*/
#end
#end
#if(${field.keyFlag})
## 主鍵
#if(${field.keyIdentityFlag})
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
#elseif(!$null.isNull(${idType}) && "$!idType" != "")
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
#elseif(${field.convert})
@TableId("${field.annotationColumnName}")
#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充設置 -----
#if(${field.convert})
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
#else
@TableField(fill = FieldFill.${field.fill})
#end
#elseif(${field.convert})
@TableField("${field.annotationColumnName}")
#end
## 樂觀鎖註解
#if(${versionFieldName}==${field.name})
@Version
#end
## 邏輯刪除註解
#if(${logicDeleteFieldName}==${field.name})
@TableLogic
#end
private ${field.propertyType} ${field.propertyName};
#end
## ---------- END 字段循環遍歷 ----------
#if(!${entityLombokModel})
#foreach($field in ${table.fields})
#if(${field.propertyType.equals("boolean")})
#set($getprefix="is")
#else
#set($getprefix="get")
#end
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}) {
#end
this.${field.propertyName} = ${field.propertyName};
#if(${chainModel})
return this;
#end
}
#end
## --foreach end---
#end
## --end of #if(!${entityLombokModel})--
#if(${entityColumnConstant})
#foreach($field in ${table.fields})
public static final String ${field.name.toUpperCase()} = "${field.name}";
#end
#end
#if(${activeRecord})
@Override
protected Serializable pkVal() {
#if(${keyPropertyName})
return this.${keyPropertyName};
#else
return null;
#end
}
#end
#if(!${entityLombokModel})
@Override
public String toString() {
return "${entity}{" +
#foreach($field in ${table.fields})
#if($!{foreach.index}==0)
"${field.propertyName}=" + ${field.propertyName} +
#else
", ${field.propertyName}=" + ${field.propertyName} +
#end
#end
"}";
}
#end
}
具體代碼生成器的執行代碼如下:
public class CodeGenerator {
public static void main(String[] args) {
// 代碼生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("海加爾金鷹(www.hjljy.cn)");
gc.setOpen(false);
//設置實體類後綴
gc.setEntityName("%sPo");
//實體屬性 Swagger2 註解
gc.setSwagger2(true);
gc.setBaseColumnList(true);
gc.setBaseResultMap(true);
mpg.setGlobalConfig(gc);
// 數據源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
String scanner = scanner("請輸入整體業務包名");
String modelName = StringUtils.isBlank(scanner) ? "" : "."+scanner;
//moduleName是整體分模塊
pc.setParent("cn.hjljy.fastboot");
pc.setMapper("mapper"+modelName);
pc.setService("service"+modelName);
pc.setServiceImpl("service"+modelName+".impl");
pc.setEntity("pojo"+modelName+".po");
pc.setController("controller"+modelName);
mpg.setPackageInfo(pc);
String dtoPath = pc.getParent() + ".pojo.dto";
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 不輸出默認的XML 默認生成的xml在mapper層裏面
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
//配置自定義輸出的文件 xml和dto
//模板引擎是 velocity
String xmlTemplatePath = "/templates/mapper.xml.vm";
// 自定義輸出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定義配置會被優先輸出
focList.add(new FileOutConfig(xmlTemplatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟着發生變化!!
return projectPath + "/src/main/resources/mapper/" + scanner
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
String dtoTemplatePath = "/dto.java.vm";
// 自定義配置會被優先輸出
focList.add(new FileOutConfig(dtoTemplatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟着發生變化!!
return projectPath + "/src/main/java/cn/hjljy/fastboot/pojo/"+scanner+"/dto/" +
tableInfo.getEntityName() + "Dto" + StringPool.DOT_JAVA;
}
});
// 自定義配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map.put("dtoPackage", dtoPath);
this.setMap(map);
}
};
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多個英文逗號分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
//設置邏輯刪除字段
strategy.setLogicDeleteFieldName("status");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute();
}
/**
* <p>
* 讀取控制檯內容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("請輸入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("請輸入正確的" + tip + "!");
}
}
總結
算是框架裏面非常基礎的一些東西。不過能夠提高不少的開發效率!!!