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<String> 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配置文件样例如下,其中包含了主要的一些配置信息,如用于描述数据库链接的<jdbcConnection>标签,用于定义数据库和Java类型转换的<javaTypeResolver>标签等。

<?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对象,并通过setContext(Context context)方法传递到具体的Plugin实现中:

public class Context extends PropertyHolder{

    /**
    * <context>标签的id属性
    */
    private String id;

    /**
    * jdbc链接信息,对应<jdbcConnection>标签中的信息
    */
    private JDBCConnectionConfiguration jdbcConnectionConfiguration;

    /**
    * 类型映射配置,对应<javaTypeResolver>
    */
    private JavaTypeResolverConfiguration javaTypeResolverConfiguration;

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

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

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

    public boolean validate(List<String> 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>标签下的配置信息,包括映射成的Mapper名称,PO名称等
    * 也可以在table标签下自定义<property>标签并通过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标签来决定是否要为这张表生成对应的悲观锁方法,配置样例如下:

 <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>

代码中通过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则对应新的标签,如<where> <include>等。悲观锁的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 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() ? "有" : "无") + "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<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对数据行上悲观锁\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() ? "有" : "无") + "Blob类型))。");
        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }
}

到此这篇关于Mybatis Generator Plugin悲观锁实现示例的文章就介绍到这了,更多相关Mybatis Generator Plugin悲观锁 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解mybatis generator代码生成器的使用

    MyBatis Generator简介 MyBatis Generator(MBG)是MyBatis MyBatis 和iBATIS的代码生成器.它将为所有版本的MyBatis以及版本2.2.0之后的iBATIS版本生成代码.它将内省数据库表(或许多表),并将生成可用于访问表的工件.这减少了设置对象和配置文件以与数据库表交互的初始麻烦.MBG寻求对简单CRUD(创建,检索,更新,删除)的大部分数据库操作产生重大影响.您仍然需要为连接查询或存储过程手动编写SQL和对象代码. MyBatis Gen

  • 使用MyBatis-Generator如何自动生成映射文件

    目录 MyBatis-Generator自动生成映射文件 1.使用cmd命令方式生成 2.使用maven方式生成 3.如果开发工具为eclipse 自动生成MyBatis映射文件工具 问题 MyBatis-Generator自动生成映射文件 生成的方式一共有三种 1.使用cmd命令方式生成 首先在generator.xml中指定数据库驱动包位置,然后在mybatis-generator-core-1.3.1包下创建一个src文件夹(否则生成的文件没地方放) 生产的Mapper.xml文件与dom

  • Mybatis Generator具体使用小技巧

    目录 1.问题描述 2.解决方案 2.1 pom中指定mybatis generator 插件 2.2 generatorConfig.xml 2.3 执行 2.4 总结 1.问题描述 mybatis generator 简单说就是生成一些mybatis的公共方法,用了好多年了,一直也没记录过,最近使用稍微有了点变话,简单记录下,方便下次使用,也希望可以帮到其他人. 2.解决方案 最近使用主要有两点变化: (1)以前使用,指定了本地数据库驱动jar包,本次直接用maven,dependency指

  • mybatis-generator生成文件覆盖问题的解决

    目录 mybatis-generator生成文件覆盖 解决方案 mybatis-generator避免覆盖自定义的sql方法 mybatis-generator生成文件覆盖 在Idea中使用Mybatis-generator plugin时遇到的问题,我的mybatis配置到的DB的服务中,每次部署微服务时需要install db这个微服务,将其打成jar包,供其他服务引用. 可是发现,我每次install或者package时候,mybatis-generator都会随编译自动运行,导致工程中的

  • MyBatis Generator生成的$ sql是否存在注入风险详解

    目录 代理商sql注入问题排查 准备测试demo entity Product.java ProductExample.java 控制层ProductController.java service层 ProductService.java ProductServiceImpl.java mapper ProductController.java ProductController.xml 测试 测试1:正常逻辑测试 测试2:测试不存在的表字段 测试3:like注入测试1 测试3:like注入测试

  • Mybatis Generator 获取不到字段注释的解决

    目录 Mybatis Generator 获取不到字段注释 解决方法 Oracle 数据库 MySql 数据库 详解 mybatis-generator生成数据表中注释 1.克隆项目 2.修改pom文件 3.配置对应的解析生成包 Mybatis Generator 获取不到字段注释 环境限制,暂时只提供Oracle和Mysql的解决方法,其它数据库如果遇到同样问题,原理是一样的,具体就看该数据库应当去配置哪个属性. 解决方法 下面的配置均指的是Mybatis Generator 的配置文件(一般

  • Mybatis Generator Plugin悲观锁实现示例

    目录 前言 实现背景: 实现Mybatis悲观锁 完整代码 前言 Mybatis Generator插件可以快速的实现基础的数据库CRUD操作,它同时支持JAVA语言和Kotlin语言,将程序员从重复的Mapper和Dao层代码编写中释放出来.Mybatis Generator可以自动生成大部分的SQL代码,如update,updateSelectively,insert,insertSelectively,select语句等.但是,当程序中需要SQL不在自动生成的SQL范围内时,就需要使用自定

  • SpringBoot整合MyBatis实现乐观锁和悲观锁的示例

    本文以转账操作为例,实现并测试乐观锁和悲观锁. 全部代码:https://github.com/imcloudfloating/Lock_Demo GitHub Page:https://cloudli.top 死锁问题 当 A, B 两个账户同时向对方转账时,会出现如下情况: 时刻 事务 1 (A 向 B 转账) 事务 2 (B 向 A 转账) T1 Lock A Lock B T2 Lock B (由于事务 2 已经 Lock A,等待) Lock A (由于事务 1 已经 Lock B,等

  • MyBatis Generator的简单使用方法示例

    添加配置文件 在项目resource目录下创建mybatis-generator文件夹 在文件夹下创建generatorConfig.xml,配置需要生成代码的数据表 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//

  • PHP+redis实现的悲观锁机制示例

    本文实例讲述了PHP+redis实现的悲观锁.分享给大家供大家参考,具体如下: 锁机制 通常使用的锁分为乐观锁,悲观锁这两种,简单介绍下这两种锁,作为本文的背景知识,对这类知识已经有足够了解的同学可以跳过这部分. 乐观锁 先来看下百度百科上的解释:大多是基于数据版本( Version )记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 "version" 字段来实现.读取出数据时,将此版本号一同读出,之后更新时,对此版本

  • Mysql中悲观锁与乐观锁应用介绍

    目录 1.锁 2.悲观锁 3.乐观锁 4.如何选择 1.锁 ​ 生活中:锁在我们身边无处不在,比如我出门玩去了需要把门锁上,比如我需要把钱放到保险柜里面,必须上锁以保证我财产的安全. 代码中:比如多个线程需要同时操作修改共享变量,这时需要给变量上把锁(syncronized),保证变量值是对的. 数据库表:当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁). sql脚本 CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL COMMENT

  • Mybatis generator mapper文件覆盖原文件的示例代码

    generatorConfig.xml编写 <?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

  • Mysql悲观锁和乐观锁的使用示例

    悲观锁 悲观锁,认为数据是悲观的.当我们查询数据的时候加上锁.防止其他线程篡改,直到对方拿到锁,才能修改. 比如,有如下的表.status=1表示可以下单,status=2表示不可以下订单.假如在并发的过程中有两个用户同时查到status=1,那么从逻辑上来说都可以去新增订单,但是会造成商品超卖. 如下例子 CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL,

  • Java的MyBatis框架中MyBatis Generator代码生成器的用法

    关于Mybatis Generator MyBatis Generator (MBG) 是一个Mybatis的代码生成器 MyBatis 和 iBATIS. 他可以生成Mybatis各个版本的代码,和iBATIS 2.2.0版本以后的代码. 他可以内省数据库的表(或多个表)然后生成可以用来访问(多个)表的基础对象. 这样和数据库表进行交互时不需要创建对象和配置文件. MBG的解决了对数据库操作有最大影响的一些简单的CRUD(插入,查询,更新,删除)操作. 您仍然需要对联合查询和存储过程手写SQL

  • 详解使用MyBatis Generator自动创建代码

    这两天需要用到MyBatis的代码自动生成的功能,由于MyBatis属于一种半自动的ORM框架,所以主要的工作就是配置Mapping映射文件,但是由于手写映射文件很容易出错,所以可利用MyBatis生成器自动生成实体类.DAO接口和Mapping映射文件.这样可以省去很多的功夫,将生成的代码copy到项目工程中即可. 1. 目录说明 使用自动生成有很多方式,可以在eclipse中安装插件,但是以下将要介绍的这种方式我认为很轻松,最简单,不需要装插件,只需要下几个jar包即可,把它们放在一个目录下

随机推荐