Mybatis Generator Plugin悲观锁实现示例

网友投稿 706 2022-12-04

Mybatis Generator Plugin悲观锁实现示例

Mybatis Generator Plugin悲观锁实现示例

目录前言实现背景:实现Mybatis悲观锁完整代码

前言

Mybatis Generator插件可以快速的实现基础的数据库CRUD操作,它同时支持java语言和Kotlin语言,将程序员从重复的Mapper和Dao层代码编写中释放出来。Mybatis Generator可以自动生成大部分的SQL代码,如update,updateSelectively,insert,insertSelectively,select语句等。但是,当程序中需要SQL不在自动生成的SQL范围内时,就需要使用自定义Mapper来实现,即手动编写DAO层和Mapper文件(这里有一个小坑,当数据库实体增加字段时,对应的自定义Mapper也要及时手动更新)。抛开复杂的定制化SQL如join,group by等,其实还是有一些比较常用的SQL在基础的Mybatis Generator工具中没有自动生成,比如分页能力,悲观锁,乐观锁等,而Mybatis Generator也为这些诉求提供了Plugin的能力。通过自定义实现Plugin可以改变Mybatis Generator在生成Mapper和Dao文件时的行为。本文将从悲观锁为例,让你快速了解如何实现Mybatis Generator Plugin。

实现背景:

数据库:mysql

mybatis generator runtime:MyBatis3

实现Mybatis悲观锁

当业务出现需要保证强一致的场景时,可以通过在事务中对数据行上悲观锁后再进行操作来实现,这就是经典的”一锁二判三更新“。在交易或是支付系统中,这种诉求非常普遍。Mysql提供了Select...For Update语句来实现对数据行上悲观锁。本文将不对Select...For Update进行详细的介绍,有兴趣的同学可以查看其它文章深入了解。

Mybatis Generator Plugin为这种具有通用性的SQL提供了很好的支持。通过继承org.mybatis.generator.api.PluginAdapter类即可自定义SQL生成逻辑并在在配置文件中使用。PluginAdapter是Plugin接口的实现类,提供了Plugin的默认实现,本文将介绍其中比较重要的几个方法:

public interface Plugin {

/**

* 将Mybatis Generator配置文件中的上下文信息传递到Plugin实现类中

* 这些信息包括数据库链接,类型映射配置等

*/

void setContext(Context context);

/**

* 配置文件中的所有properties标签

**/

void setProperties(Properties properties);

/**

* 校验该Plugin是否执行,如果返回false,则该插件不会执行

**/

boolean validate(List warnings);

/**

* 当DAO文件完成生成后会触发该方法,可以通过实现该方法在DAO文件中新增方法或属性

**/

boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,

IntrospectedTable introspectedTable);

/**

* 当SQL XML 文件生成后会调用该方法,可以通过实现该方法在MAPPER XML文件中新增XML定义

**/

boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable);

}

这里结合Mybatis Generator的配置文件和生成的DAO(也称为Client文件)和Mapper XML文件可以更好的理解。Mybatis Generator配置文件样例如下,其中包含了主要的一些配置信息,如用于描述数据库链接的标签,用于定义数据库和Java类型转换的标签等。

PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

connectionURL="jdbc:db2:TEST"

userId="db2admin"

password="db2admin">

connectionURL="jdbc:db2:TEST"

userId="db2admin"

password="db2admin">

这些都被映射成Context对象,并通过setContext(Context context)方法传递到具体的Plugin实现中:

public class Context extends PropertyHolder{

/**

* 标签的id属性

*/

private String id;

/**

* jdbc链接信息,对应标签中的信息

*/

private JDBCConnectionConfiguration jdbcConnectionConfiguration;

/**

* 类型映射配置,对应

*/

private JavaTypeResolverConfiguration javaTypeResolverConfiguration;

/**

* ...其它标签对应的配置信息

*/

}

setProperties则将context下的标签收集起来并映射成Properties类,它实际上是一个Map容器,正如Properties类本身就继承了Hashtable。以上文中的配置文件为例,可以通过properties.get("printLog")获得值"true"。

validate方法则代表了这个Plugin是否执行,它通常进行一些非常基础的校验,比如是否兼容对应的数据库驱动或者是Mybatis版本

public boolean validate(List warnings) {

if (StringUtility.stringHasValue(this.getContext().getTargetRuntime()) && !"MyBatis3".equalsIgnoreCase(this.getContext().getTargetRuntime())) {

logger.warn("itfsw:插件" + this.getClass().getTypeName() + "要求运行targetRuntime必须为MyBatis3!");

return false;

} else {

return true;

}

}

如果validate方法返回false,则无论什么场景下都不会运行这个Plugin。

接着是最重要的两个方法,分别是用于在DAO中生成新的方法clientGenerated和在XML文件中生成新的SQL sqlMapDocumentGenerated。

先说clientGenerated,这个方法共有三个参数,interfaze是当前已经生成的客户端Dao接口,topLevelClass是指生成的实现类,这个类可能为空,introspectedTable是指当前处理的数据表,这里包含了从数据库中获取的关于表的各种信息,包括列名称,列类型等。这里可以看一下introspectedTable中几个比较重要的方法:

public abstract class IntrospectedTable {

/**

* 该方法可以获得配置文件中该表对应

* 也可以在table标签下自定义标签并通过getProperty方法获得值

*/

public TableConfiguration getTableConfiguration() {

return tableConfiguration;

}

/**

* 这个方法中定义了默认的生成规则,可以通过calculateAllFieldsClass获得返回类型

*/

public Rules getRules() {

return rules;

}

}

悲观锁的clientGenerated方法如下:

// Plugin配置,是否要生成selectForUpdate语句

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对数据行上悲观锁\n" +

" */";

method.addJavaDocLine(docComment);

interfaze.addMethod(method);

log.debug("(悲观锁插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");

}

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

}

这里可以通过在对应table下新增property标签来决定是否要为这张表生成对应的悲观锁方法,配置样例如下:

enableCountByExample="true"

enableUpdateByExample="true"

enableDeleteByExample="true"

enableSelectByExample="true"

enableInsert="true"

selectByExampleQueryId="true">

代码中通过mybatis提供的Method方法,定义了方法的名称,参数,返回类型等,并使用interfaze.addMethod方法将方法添加到客户端的接口中。

再到sqlMapDocumentGenerated这个方法,这个方法中传入了Document对象,它对应生成的XML文件,并通过XmlElement来映射XML文件中的元素。通过document.getRootElement().addElement可以将自定义的XML元素插入到Mapper文件中。自定义XML元素就是指拼接XmlElement,XmlElement的addAttribute方法可以为XML元素设置属性,addElement则可以为XML标签添加子元素。有两种类型的子元素,分别是TextElement和XmlElement本身,TextElement则直接填充标签中的内容,而XmlElement则对应新的标签,如 等。悲观锁的SQL生成逻辑如下:

// Plugin配置,是否要生成selectForUpdate语句

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 AttXJUlWribute("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.getBaseColumnListXJUlWId()));

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() ? "有" : "无") + "Blob类型))。");

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 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对数据行上悲观锁\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.addElemXJUlWent(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() ? "有" : "无") + "Blob类型))。");

return super.sqlMapDocumentGenerated(document, introspectedTable);

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring使用Setter完成依赖注入方式
下一篇:http中get请求与post请求区别及如何选择
相关文章

 发表评论

暂时没有评论,来抢沙发吧~