Mybatis Generator Plugin悲觀鎖實(shí)現(xiàn)示例
前言
Mybatis Generator插件可以快速的實(shí)現(xiàn)基礎(chǔ)的數(shù)據(jù)庫(kù)CRUD操作,它同時(shí)支持JAVA語(yǔ)言和Kotlin語(yǔ)言,將程序員從重復(fù)的Mapper和Dao層代碼編寫(xiě)中釋放出來(lái)。Mybatis Generator可以自動(dòng)生成大部分的SQL代碼,如update,updateSelectively,insert,insertSelectively,select語(yǔ)句等。但是,當(dāng)程序中需要SQL不在自動(dòng)生成的SQL范圍內(nèi)時(shí),就需要使用自定義Mapper來(lái)實(shí)現(xiàn),即手動(dòng)編寫(xiě)DAO層和Mapper文件(這里有一個(gè)小坑,當(dāng)數(shù)據(jù)庫(kù)實(shí)體增加字段時(shí),對(duì)應(yīng)的自定義Mapper也要及時(shí)手動(dòng)更新)。拋開(kāi)復(fù)雜的定制化SQL如join,group by等,其實(shí)還是有一些比較常用的SQL在基礎(chǔ)的Mybatis Generator工具中沒(méi)有自動(dòng)生成,比如分頁(yè)能力,悲觀鎖,樂(lè)觀鎖等,而Mybatis Generator也為這些訴求提供了Plugin的能力。通過(guò)自定義實(shí)現(xiàn)Plugin可以改變Mybatis Generator在生成Mapper和Dao文件時(shí)的行為。本文將從悲觀鎖為例,讓你快速了解如何實(shí)現(xiàn)Mybatis Generator Plugin。
實(shí)現(xiàn)背景:
- 數(shù)據(jù)庫(kù):MYSQL
- mybatis generator runtime:MyBatis3
實(shí)現(xiàn)Mybatis悲觀鎖
當(dāng)業(yè)務(wù)出現(xiàn)需要保證強(qiáng)一致的場(chǎng)景時(shí),可以通過(guò)在事務(wù)中對(duì)數(shù)據(jù)行上悲觀鎖后再進(jìn)行操作來(lái)實(shí)現(xiàn),這就是經(jīng)典的”一鎖二判三更新“。在交易或是支付系統(tǒng)中,這種訴求非常普遍。Mysql提供了Select...For Update語(yǔ)句來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)行上悲觀鎖。本文將不對(duì)Select...For Update進(jìn)行詳細(xì)的介紹,有興趣的同學(xué)可以查看其它文章深入了解。
Mybatis Generator Plugin為這種具有通用性的SQL提供了很好的支持。通過(guò)繼承org.mybatis.generator.api.PluginAdapter類(lèi)即可自定義SQL生成邏輯并在在配置文件中使用。PluginAdapter是Plugin接口的實(shí)現(xiàn)類(lèi),提供了Plugin的默認(rèn)實(shí)現(xiàn),本文將介紹其中比較重要的幾個(gè)方法:
public interface Plugin {
/**
* 將Mybatis Generator配置文件中的上下文信息傳遞到Plugin實(shí)現(xiàn)類(lèi)中
* 這些信息包括數(shù)據(jù)庫(kù)鏈接,類(lèi)型映射配置等
*/
void setContext(Context context);
/**
* 配置文件中的所有properties標(biāo)簽
**/
void setProperties(Properties properties);
/**
* 校驗(yàn)該P(yáng)lugin是否執(zhí)行,如果返回false,則該插件不會(huì)執(zhí)行
**/
boolean validate(List<String> warnings);
/**
* 當(dāng)DAO文件完成生成后會(huì)觸發(fā)該方法,可以通過(guò)實(shí)現(xiàn)該方法在DAO文件中新增方法或?qū)傩?
**/
boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
IntrospectedTable introspectedTable);
/**
* 當(dāng)SQL XML 文件生成后會(huì)調(diào)用該方法,可以通過(guò)實(shí)現(xiàn)該方法在MAPPER XML文件中新增XML定義
**/
boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable);
}
這里結(jié)合Mybatis Generator的配置文件和生成的DAO(也稱(chēng)為Client文件)和Mapper XML文件可以更好的理解。Mybatis Generator配置文件樣例如下,其中包含了主要的一些配置信息,如用于描述數(shù)據(jù)庫(kù)鏈接的<jdbcConnection>標(biāo)簽,用于定義數(shù)據(jù)庫(kù)和Java類(lèi)型轉(zhuǎn)換的<javaTypeResolver>標(biāo)簽等。
<?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>
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="COM.ibm.db2.jdbc.app.DB2Driver"
connectionURL="jdbc:db2:TEST"
userId="db2admin"
password="db2admin">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="test.model" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="test.xml" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<property name="printLog" value="true"/>
<table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
<property name="useActualColumnNames" value="true"/>
<generatedKey column="ID" sqlStatement="DB2" identity="true" />
<columnOverride column="DATE_FIELD" property="startDate" />
<ignoreColumn column="FRED" />
<columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
</table>
</context>
</generatorConfiguration>
這些都被映射成Context對(duì)象,并通過(guò)setContext(Context context)方法傳遞到具體的Plugin實(shí)現(xiàn)中:
public class Context extends PropertyHolder{
/**
* <context>標(biāo)簽的id屬性
*/
private String id;
/**
* jdbc鏈接信息,對(duì)應(yīng)<jdbcConnection>標(biāo)簽中的信息
*/
private JDBCConnectionConfiguration jdbcConnectionConfiguration;
/**
* 類(lèi)型映射配置,對(duì)應(yīng)<javaTypeResolver>
*/
private JavaTypeResolverConfiguration javaTypeResolverConfiguration;
/**
* ...其它標(biāo)簽對(duì)應(yīng)的配置信息
*/
}
setProperties則將context下的<properties>標(biāo)簽收集起來(lái)并映射成Properties類(lèi),它實(shí)際上是一個(gè)Map容器,正如Properties類(lèi)本身就繼承了Hashtable。以上文中的配置文件為例,可以通過(guò)properties.get("printLog")獲得值"true"。
validate方法則代表了這個(gè)Plugin是否執(zhí)行,它通常進(jìn)行一些非常基礎(chǔ)的校驗(yàn),比如是否兼容對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)或者是Mybatis版本:
public boolean validate(List<String> warnings) {
if (StringUtility.stringHasValue(this.getContext().getTargetRuntime()) && !"MyBatis3".equalsIgnoreCase(this.getContext().getTargetRuntime())) {
logger.warn("itfsw:插件" + this.getClass().getTypeName() + "要求運(yùn)行targetRuntime必須為MyBatis3!");
return false;
} else {
return true;
}
}
如果validate方法返回false,則無(wú)論什么場(chǎng)景下都不會(huì)運(yùn)行這個(gè)Plugin。
接著是最重要的兩個(gè)方法,分別是用于在DAO中生成新的方法clientGenerated和在XML文件中生成新的SQL sqlMapDocumentGenerated。
先說(shuō)clientGenerated,這個(gè)方法共有三個(gè)參數(shù),interfaze是當(dāng)前已經(jīng)生成的客戶端Dao接口,topLevelClass是指生成的實(shí)現(xiàn)類(lèi),這個(gè)類(lèi)可能為空,introspectedTable是指當(dāng)前處理的數(shù)據(jù)表,這里包含了從數(shù)據(jù)庫(kù)中獲取的關(guān)于表的各種信息,包括列名稱(chēng),列類(lèi)型等。這里可以看一下introspectedTable中幾個(gè)比較重要的方法:
public abstract class IntrospectedTable {
/**
* 該方法可以獲得配置文件中該表對(duì)應(yīng)<table>標(biāo)簽下的配置信息,包括映射成的Mapper名稱(chēng),PO名稱(chēng)等
* 也可以在table標(biāo)簽下自定義<property>標(biāo)簽并通過(guò)getProperty方法獲得值
*/
public TableConfiguration getTableConfiguration() {
return tableConfiguration;
}
/**
* 這個(gè)方法中定義了默認(rèn)的生成規(guī)則,可以通過(guò)calculateAllFieldsClass獲得返回類(lèi)型
*/
public Rules getRules() {
return rules;
}
}
悲觀鎖的clientGenerated方法如下:
// Plugin配置,是否要生成selectForUpdate語(yǔ)句
private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
@Override
public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
if (StringUtility.isTrue(implementUpdate)) {
Method method = new Method(METHOD_NAME);
FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
method.setReturnType(returnType);
method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
String docComment = "/**\n" +
" * 使用id對(duì)數(shù)據(jù)行上悲觀鎖\n" +
" */";
method.addJavaDocLine(docComment);
interfaze.addMethod(method);
log.debug("(悲觀鎖插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
}
return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}
這里可以通過(guò)在對(duì)應(yīng)table下新增property標(biāo)簽來(lái)決定是否要為這張表生成對(duì)應(yīng)的悲觀鎖方法,配置樣例如下:
<table tableName="demo" domainObjectName="DemoPO" mapperName="DemoMapper"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
enableInsert="true"
selectByExampleQueryId="true">
<property name="implementUpdateWithCAS" value="true"/>
</table>
代碼中通過(guò)mybatis提供的Method方法,定義了方法的名稱(chēng),參數(shù),返回類(lèi)型等,并使用interfaze.addMethod方法將方法添加到客戶端的接口中。
再到sqlMapDocumentGenerated這個(gè)方法,這個(gè)方法中傳入了Document對(duì)象,它對(duì)應(yīng)生成的XML文件,并通過(guò)XmlElement來(lái)映射XML文件中的元素。通過(guò)document.getRootElement().addElement可以將自定義的XML元素插入到Mapper文件中。自定義XML元素就是指拼接X(jué)mlElement,XmlElement的addAttribute方法可以為XML元素設(shè)置屬性,addElement則可以為XML標(biāo)簽添加子元素。有兩種類(lèi)型的子元素,分別是TextElement和XmlElement本身,TextElement則直接填充標(biāo)簽中的內(nèi)容,而XmlElement則對(duì)應(yīng)新的標(biāo)簽,如<where> <include>等。悲觀鎖的SQL生成邏輯如下:
// Plugin配置,是否要生成selectForUpdate語(yǔ)句
private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
@Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
if (!StringUtility.isTrue(implementUpdate)) {
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
XmlElement selectForUpdate = new XmlElement("select");
selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
StringBuilder sb;
String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
selectForUpdate.addElement(new TextElement("select"));
sb = new StringBuilder();
if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
sb.append('\'');
sb.append(introspectedTable.getSelectByExampleQueryId());
sb.append("' as QUERYID,");
selectForUpdate.addElement(new TextElement(sb.toString()));
}
XmlElement baseColumn = new XmlElement("include");
baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
selectForUpdate.addElement(baseColumn);
if (introspectedTable.hasBLOBColumns()) {
selectForUpdate.addElement(new TextElement(","));
XmlElement blobColumns = new XmlElement("include");
blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
selectForUpdate.addElement(blobColumns);
}
sb.setLength(0);
sb.append("from ");
sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
selectForUpdate.addElement(new TextElement(sb.toString()));
TextElement whereXml = new TextElement("where id = #{id} for update");
selectForUpdate.addElement(whereXml);
document.getRootElement().addElement(selectForUpdate);
log.debug("(悲觀鎖插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "無(wú)") + "Blob類(lèi)型))。");
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
完整代碼
@Slf4j
public class SelectForUpdatePlugin extends PluginAdapter {
private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
private static final String METHOD_NAME = "selectByIdForUpdate";
@Override
public boolean validate(List<String> list) {
return true;
}
@Override
public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
if (StringUtility.isTrue(implementUpdate)) {
Method method = new Method(METHOD_NAME);
FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
method.setReturnType(returnType);
method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
String docComment = "/**\n" +
" * 使用id對(duì)數(shù)據(jù)行上悲觀鎖\n" +
" */";
method.addJavaDocLine(docComment);
interfaze.addMethod(method);
log.debug("(悲觀鎖插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
}
return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}
@Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
if (!StringUtility.isTrue(implementUpdate)) {
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
XmlElement selectForUpdate = new XmlElement("select");
selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
StringBuilder sb;
String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
selectForUpdate.addElement(new TextElement("select"));
sb = new StringBuilder();
if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
sb.append('\'');
sb.append(introspectedTable.getSelectByExampleQueryId());
sb.append("' as QUERYID,");
selectForUpdate.addElement(new TextElement(sb.toString()));
}
XmlElement baseColumn = new XmlElement("include");
baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
selectForUpdate.addElement(baseColumn);
if (introspectedTable.hasBLOBColumns()) {
selectForUpdate.addElement(new TextElement(","));
XmlElement blobColumns = new XmlElement("include");
blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
selectForUpdate.addElement(blobColumns);
}
sb.setLength(0);
sb.append("from ");
sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
selectForUpdate.addElement(new TextElement(sb.toString()));
TextElement whereXml = new TextElement("where id = #{id} for update");
selectForUpdate.addElement(whereXml);
document.getRootElement().addElement(selectForUpdate);
log.debug("(悲觀鎖插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "無(wú)") + "Blob類(lèi)型))。");
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
}
到此這篇關(guān)于Mybatis Generator Plugin悲觀鎖實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Mybatis Generator Plugin悲觀鎖 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)網(wǎng)頁(yè)解析示例
這篇文章主要介紹了java實(shí)現(xiàn)網(wǎng)頁(yè)解析示例,需要的朋友可以參考下2014-04-04
java求100以內(nèi)的素?cái)?shù)示例分享
素?cái)?shù)是指因數(shù)只有1和本身的數(shù)字,這篇文章主要介紹了java求100以內(nèi)的素?cái)?shù)示例,需要的朋友可以參考下2014-03-03
IDEA JetBrains Mono字體介紹和安裝教程(詳解)
這篇文章主要介紹了IDEA JetBrains Mono字體介紹和安裝教程,本給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
關(guān)于Java類(lèi)的構(gòu)造方法詳解
這篇文章主要介紹了關(guān)于Java類(lèi)的構(gòu)造方法詳解的相關(guān)資料,需要的朋友可以參考下2023-01-01
java中重寫(xiě)父類(lèi)方法加不加@Override詳解
這篇文章主要介紹了java中重寫(xiě)父類(lèi)方法加不加@Override詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案
這篇文章主要介紹了JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能
最近有一個(gè)需求是關(guān)于視頻上傳播放的,需要設(shè)計(jì)一個(gè)方案,中間談到了Minio這個(gè)技術(shù),于是來(lái)學(xué)習(xí)一下,所以本文給大家介紹了SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-07-07
servlet實(shí)現(xiàn)文件下載的步驟及說(shuō)明詳解
這篇文章主要為大家詳細(xì)介紹了servlet實(shí)現(xiàn)文件下載的步驟及說(shuō)明,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
基于SpringBoot生成二維碼的幾種實(shí)現(xiàn)方式
本文將基于Spring Boot介紹兩種生成二維碼的實(shí)現(xiàn)方式,一種是基于Google開(kāi)發(fā)工具包,另一種是基于Hutool來(lái)實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2022-03-03

