经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
Mybatis Generator Plugin悲观锁实现示例
来源:jb51  时间:2021/9/28 9:55:03  对本文有异议

前言

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的默认实现,本文将介绍其中比较重要的几个方法:

  1. public interface Plugin {
  2. /**
  3. * 将Mybatis Generator配置文件中的上下文信息传递到Plugin实现类中
  4. * 这些信息包括数据库链接,类型映射配置等
  5. */
  6. void setContext(Context context);
  7.  
  8. /**
  9. * 配置文件中的所有properties标签
  10. **/
  11. void setProperties(Properties properties);
  12.  
  13. /**
  14. * 校验该Plugin是否执行,如果返回false,则该插件不会执行
  15. **/
  16. boolean validate(List<String> warnings);
  17.  
  18. /**
  19. * 当DAO文件完成生成后会触发该方法,可以通过实现该方法在DAO文件中新增方法或属性
  20. **/
  21. boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
  22. IntrospectedTable introspectedTable);
  23.  
  24. /**
  25. * 当SQL XML 文件生成后会调用该方法,可以通过实现该方法在MAPPER XML文件中新增XML定义
  26. **/
  27. boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable);
  28. }

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE generatorConfiguration
  3. PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  4. "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
  5.  
  6. <generatorConfiguration>
  7. <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
  8.  
  9. <context id="DB2Tables" targetRuntime="MyBatis3">
  10. <jdbcConnection driverClass="COM.ibm.db2.jdbc.app.DB2Driver"
  11. connectionURL="jdbc:db2:TEST"
  12. userId="db2admin"
  13. password="db2admin">
  14. </jdbcConnection>
  15.  
  16. <javaTypeResolver >
  17. <property name="forceBigDecimals" value="false" />
  18. </javaTypeResolver>
  19.  
  20. <javaModelGenerator targetPackage="test.model" targetProject="\MBGTestProject\src">
  21. <property name="enableSubPackages" value="true" />
  22. <property name="trimStrings" value="true" />
  23. </javaModelGenerator>
  24.  
  25. <sqlMapGenerator targetPackage="test.xml" targetProject="\MBGTestProject\src">
  26. <property name="enableSubPackages" value="true" />
  27. </sqlMapGenerator>
  28.  
  29. <javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="\MBGTestProject\src">
  30. <property name="enableSubPackages" value="true" />
  31. </javaClientGenerator>
  32.  
  33. <property name="printLog" value="true"/>
  34.  
  35. <table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
  36. <property name="useActualColumnNames" value="true"/>
  37. <generatedKey column="ID" sqlStatement="DB2" identity="true" />
  38. <columnOverride column="DATE_FIELD" property="startDate" />
  39. <ignoreColumn column="FRED" />
  40. <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
  41. </table>
  42.  
  43. </context>
  44. </generatorConfiguration>

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

  1. public class Context extends PropertyHolder{
  2.  
  3. /**
  4. * <context>标签的id属性
  5. */
  6. private String id;
  7.  
  8. /**
  9. * jdbc链接信息,对应<jdbcConnection>标签中的信息
  10. */
  11. private JDBCConnectionConfiguration jdbcConnectionConfiguration;
  12.  
  13. /**
  14. * 类型映射配置,对应<javaTypeResolver>
  15. */
  16. private JavaTypeResolverConfiguration javaTypeResolverConfiguration;
  17.  
  18. /**
  19. * ...其它标签对应的配置信息
  20. */
  21. }
  22.  

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

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

  1. public boolean validate(List<String> warnings) {
  2. if (StringUtility.stringHasValue(this.getContext().getTargetRuntime()) && !"MyBatis3".equalsIgnoreCase(this.getContext().getTargetRuntime())) {
  3. logger.warn("itfsw:插件" + this.getClass().getTypeName() + "要求运行targetRuntime必须为MyBatis3!");
  4. return false;
  5. } else {
  6. return true;
  7. }
  8. }

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

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

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

  1. public abstract class IntrospectedTable {
  2. /**
  3. * 该方法可以获得配置文件中该表对应<table>标签下的配置信息,包括映射成的Mapper名称,PO名称等
  4. * 也可以在table标签下自定义<property>标签并通过getProperty方法获得值
  5. */
  6. public TableConfiguration getTableConfiguration() {
  7. return tableConfiguration;
  8. }
  9.  
  10. /**
  11. * 这个方法中定义了默认的生成规则,可以通过calculateAllFieldsClass获得返回类型
  12. */
  13. public Rules getRules() {
  14. return rules;
  15. }
  16. }
  17.  

悲观锁的clientGenerated方法如下:

  1. // Plugin配置,是否要生成selectForUpdate语句
  2. private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
  3.  
  4. @Override
  5. public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
  6. String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
  7. if (StringUtility.isTrue(implementUpdate)) {
  8. Method method = new Method(METHOD_NAME);
  9. FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
  10. method.setReturnType(returnType);
  11. method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
  12. String docComment = "/**\n" +
  13. " * 使用id对数据行上悲观锁\n" +
  14. " */";
  15. method.addJavaDocLine(docComment);
  16. interfaze.addMethod(method);
  17. log.debug("(悲观锁插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
  18. }
  19.  
  20. return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
  21. }
  22.  

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

  1. <table tableName="demo" domainObjectName="DemoPO" mapperName="DemoMapper"
  2. enableCountByExample="true"
  3. enableUpdateByExample="true"
  4. enableDeleteByExample="true"
  5. enableSelectByExample="true"
  6. enableInsert="true"
  7. selectByExampleQueryId="true">
  8. <property name="implementUpdateWithCAS" value="true"/>
  9. </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生成逻辑如下:

  1. // Plugin配置,是否要生成selectForUpdate语句
  2. private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
  3.  
  4. @Override
  5. public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
  6. String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
  7. if (!StringUtility.isTrue(implementUpdate)) {
  8. return super.sqlMapDocumentGenerated(document, introspectedTable);
  9. }
  10.  
  11. XmlElement selectForUpdate = new XmlElement("select");
  12. selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
  13. StringBuilder sb;
  14.  
  15. String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
  16. selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
  17. selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
  18. selectForUpdate.addElement(new TextElement("select"));
  19.  
  20. sb = new StringBuilder();
  21. if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
  22. sb.append('\'');
  23. sb.append(introspectedTable.getSelectByExampleQueryId());
  24. sb.append("' as QUERYID,");
  25. selectForUpdate.addElement(new TextElement(sb.toString()));
  26. }
  27.  
  28. XmlElement baseColumn = new XmlElement("include");
  29. baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
  30. selectForUpdate.addElement(baseColumn);
  31. if (introspectedTable.hasBLOBColumns()) {
  32. selectForUpdate.addElement(new TextElement(","));
  33. XmlElement blobColumns = new XmlElement("include");
  34. blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
  35. selectForUpdate.addElement(blobColumns);
  36. }
  37.  
  38. sb.setLength(0);
  39. sb.append("from ");
  40. sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
  41. selectForUpdate.addElement(new TextElement(sb.toString()));
  42. TextElement whereXml = new TextElement("where id = #{id} for update");
  43. selectForUpdate.addElement(whereXml);
  44.  
  45. document.getRootElement().addElement(selectForUpdate);
  46. log.debug("(悲观锁插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "无") + "Blob类型))。");
  47. return super.sqlMapDocumentGenerated(document, introspectedTable);
  48. }
  49.  

完整代码

  1. @Slf4j
  2. public class SelectForUpdatePlugin extends PluginAdapter {
  3.  
  4. private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
  5.  
  6. private static final String METHOD_NAME = "selectByIdForUpdate";
  7.  
  8. @Override
  9. public boolean validate(List<String> list) {
  10. return true;
  11. }
  12.  
  13. @Override
  14. public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
  15. String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
  16. if (StringUtility.isTrue(implementUpdate)) {
  17. Method method = new Method(METHOD_NAME);
  18. FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
  19. method.setReturnType(returnType);
  20. method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
  21. String docComment = "/**\n" +
  22. " * 使用id对数据行上悲观锁\n" +
  23. " */";
  24. method.addJavaDocLine(docComment);
  25. interfaze.addMethod(method);
  26. log.debug("(悲观锁插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
  27. }
  28.  
  29. return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
  30. }
  31.  
  32. @Override
  33. public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
  34. String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
  35. if (!StringUtility.isTrue(implementUpdate)) {
  36. return super.sqlMapDocumentGenerated(document, introspectedTable);
  37. }
  38.  
  39. XmlElement selectForUpdate = new XmlElement("select");
  40. selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
  41. StringBuilder sb;
  42.  
  43. String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
  44. selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
  45. selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
  46. selectForUpdate.addElement(new TextElement("select"));
  47.  
  48. sb = new StringBuilder();
  49. if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
  50. sb.append('\'');
  51. sb.append(introspectedTable.getSelectByExampleQueryId());
  52. sb.append("' as QUERYID,");
  53. selectForUpdate.addElement(new TextElement(sb.toString()));
  54. }
  55.  
  56. XmlElement baseColumn = new XmlElement("include");
  57. baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
  58. selectForUpdate.addElement(baseColumn);
  59. if (introspectedTable.hasBLOBColumns()) {
  60. selectForUpdate.addElement(new TextElement(","));
  61. XmlElement blobColumns = new XmlElement("include");
  62. blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
  63. selectForUpdate.addElement(blobColumns);
  64. }
  65.  
  66. sb.setLength(0);
  67. sb.append("from ");
  68. sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
  69. selectForUpdate.addElement(new TextElement(sb.toString()));
  70. TextElement whereXml = new TextElement("where id = #{id} for update");
  71. selectForUpdate.addElement(whereXml);
  72.  
  73. document.getRootElement().addElement(selectForUpdate);
  74. log.debug("(悲观锁插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "无") + "Blob类型))。");
  75. return super.sqlMapDocumentGenerated(document, introspectedTable);
  76. }
  77. }

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号