-
MyBatis Generator 详解
2014-12-23 14:27:11MyBatis Generator中文文档 MyBatis Generator中文文档地址: http://mbg.cndocs.ml/ 该中文文档由于尽可能和原文内容一致,所以有些地方如果不熟悉,看中文版的文档的也会有一定的障碍,所以本章根据该中文...MyBatis Generator中文文档
MyBatis Generator中文文档地址:
该中文文档由于尽可能和原文内容一致,所以有些地方如果不熟悉,看中文版的文档的也会有一定的障碍,所以本章根据该中文文档以及实际应用,使用通俗的语言来讲解详细的配置。
本文中所有节点的链接都是对应的中文文档地址,可以点击查看详细信息。
注:本文后面提到的MBG全部指代MyBatis Generator。
MyBatis Generator 1.3.4 扩展,可以设置 Mapper(Dao)后缀
运行MyBatis Generator
有4种运行MBG的方法,具体请看文档 运行 MyBatis Generator
MBG下载地址:http://repo1.maven.org/maven2/org/mybatis/generator/mybatis-generator-core/
XML配置详解
在MBG中,最主要也最重要的就是XML配置文件,因此本篇文章主要的内容就是XML配置。
这里按照配置的顺序对配置逐个讲解,更细的内容可以配合中文文档参照。
1. 配置文件头
<?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">
使用最新版的MBG需要使用上面的xml头,配置文件必须包含上面的
DOCTYPE
。2. 根节点
<generatorConfiguration>
generatorConfiguration
节点没有任何属性,直接写节点即可,如下:<generatorConfiguration> <!-- 具体配置内容 --> </generatorConfiguration>
3.
<generatorConfiguration>
子元素从这段开始,就是配置的主要内容,这些配置都是
generatorConfiguration
元素的子元素。包含以下子元素(有严格的顺序):
-
<properties>
(0个或1个) -
<classPathEntry>
(0个或多个) -
<context>
(1个或多个)
3.1
<properties>
元素这个元素用来指定外部的属性元素,不是必须的元素。
元素用于指定一个需要在配置中解析使用的外部属性文件,引入属性文件后,可以在配置中使用
${property}
这种形式的引用,通过这种方式引用属性文件中的属性值。 对于后面需要配置的**jdbc信息**和targetProject
属性会很有用。这个属性可以通过
resource
或者url
来指定属性文件的位置,这两个属性只能使用其中一个来指定,同时出现会报错。-
resource
:指定**classpath**下的属性文件,使用类似com/myproject/generatorConfig.properties
这样的属性值。 -
url
:可以指定文件系统上的特定位置,例如file:///C:/myfolder/generatorConfig.properties
3.2
<classPathEntry>
元素这个元素可以0或多个,不受限制。
最常见的用法是通过这个属性指定驱动的路径,例如:
<classPathEntry location="E:\mysql\mysql-connector-java-5.1.29.jar"/>
重点提醒:本文之前在这里有误导,特别强调。
注意,classPathEntry只在下面这两种情况下才有效:
- 当加载 JDBC 驱动内省数据库时
- 当加载根类中的 JavaModelGenerator 检查重写的方法时
因此,如果你需要加载其他用途的jar包,classPathEntry起不到作用,不能这么写,解决的办法就是将你用的jar包添加到类路径中,在Eclipse等IDE中运行的时候,添加jar包比较容易。当从命令行执行的时候,需要用java -cp xx.jar,xx2.jar xxxMainClass这种方式在-cp后面指定来使用(注意-jar会导致-cp无效)。
3.3
<context>
元素在MBG的配置中,至少需要有一个
<context>
元素。<context>
元素用于指定生成一组对象的环境。例如指定要连接的数据库,要生成对象的类型和要处理的数据库中的表。运行MBG的时候还可以指定要运行的<context>
。该元素只有一个**必选属性**
id
,用来唯一确定一个<context>
元素,该id
属性可以在运行MBG的使用。此外还有几个**可选属性**:
-
defaultModelType
:**这个属性很重要**,这个属性定义了MBG如何生成**实体类**。
这个属性有以下可选值:conditional
:*这是默认值*,这个模型和下面的hierarchical
类似,除了如果那个单独的类将只包含一个字段,将不会生成一个单独的类。 因此,如果一个表的主键只有一个字段,那么不会为该字段生成单独的实体类,会将该字段合并到基本实体类中。flat
:该模型为每一张表只生成一个实体类。这个实体类包含表中的所有字段。**这种模型最简单,推荐使用。**hierarchical
:如果表有主键,那么该模型会产生一个单独的主键实体类,如果表还有BLOB字段, 则会为表生成一个包含所有BLOB字段的单独的实体类,然后为所有其他的字段生成一个单独的实体类。 MBG会在所有生成的实体类之间维护一个继承关系。
-
targetRuntime
:此属性用于指定生成的代码的运行时环境。该属性支持以下可选值:MyBatis3
:*这是默认值*MyBatis3Simple
Ibatis2Java2
Ibatis2Java5
一般情况下使用默认值即可,有关这些值的具体作用以及区别请查看中文文档的详细内容。
-
introspectedColumnImpl
:该参数可以指定扩展org.mybatis.generator.api.IntrospectedColumn
该类的实现类。该属性的作用可以查看扩展MyBatis Generator。
一般情况下,我们使用如下的配置即可:
<context id="Mysql" defaultModelType="flat">
如果你希望不生成和
Example
查询有关的内容,那么可以按照如下进行配置:<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
使用
MyBatis3Simple
可以避免在后面的<table>
中逐个进行配置(后面会提到)。MBG配置中的其他几个元素,基本上都是
<context>
的子元素,这些子元素(有严格的配置顺序)包括:<property>
(0个或多个)<plugin>
(0个或多个)<commentGenerator>
(0个或1个)<jdbcConnection>
(1个)<javaTypeResolver>
(0个或1个)<javaModelGenerator>
(1个)<sqlMapGenerator>
(0个或1个)<javaClientGenerator>
(0个或1个)<table>
(1个或多个)
其中
<property>
属性比较特殊,后面讲解的时候都会和父元素一起进行讲解。在讲解<property>
属性前,我们先看看**什么是分隔符?**。这里通过一个例子说明。假设在Mysql数据库中有一个表名为
user info
,你没有看错,中间是一个空格,这种情况下如果写出select * from user info
这样的语句,肯定是要报错的,在Mysql中的时候我们一般会写成如下的样子:select * from `user info`
这里的使用的**反单引号(`)**就是**分隔符**,**分隔符**可以用于**表名**或者**列名**。
下面继续看
<property>
支持的属性:autoDelimitKeywords
beginningDelimiter
endingDelimiter
javaFileEncoding
javaFormatter
xmlFormatter
由于这些属性比较重要,这里一一讲解。
首先是
autoDelimitKeywords
,当表名或者字段名为SQL关键字的时候,可以设置该属性为true,MBG会自动给表名或字段名添加**分隔符**。然后这里继续上面的例子来讲
beginningDelimiter
和endingDelimiter
属性。
由于beginningDelimiter
和endingDelimiter
的默认值为双引号("
),在Mysql中不能这么写,所以还要将这两个默认值改为**反单引号(`)**,配置如下:<property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/>
属性
javaFileEncoding
设置要使用的Java文件的编码,默认使用当前平台的编码,只有当生产的编码需要特殊指定时才需要使用,一般用不到。最后两个
javaFormatter
和xmlFormatter
属性**可能会**很有用,如果你想使用模板来定制生成的java文件和xml文件的样式,你可以通过指定这两个属性的值来实现。接下来分节对其他的子元素逐个进行介绍。
3.3.1
<plugin>
元素该元素可以配置0个或者多个,不受限制。
<plugin>
元素用来定义一个插件。插件用于扩展或修改通过MyBatis Generator (MBG)代码生成器生成的代码。插件将按在配置中配置的顺序执行。
3.3.2
<commentGenerator>
元素该元素最多可以配置1个。
这个元素非常有用,相信很多人都有过这样的需求,就是希望MBG生成的代码中可以包含**注释信息**,具体就是生成表或字段的备注信息。
使用这个元素就能很简单的实现我们想要的功能。这里先介绍该元素,介绍完后会举例如何扩展实现该功能。
该元素有一个可选属性
type
,可以指定用户的实现类,该类需要实现org.mybatis.generator.api.CommentGenerator
接口。而且必有一个默认的构造方法。这个属性接收默认的特殊值DEFAULT
,会使用默认的实现类org.mybatis.generator.internal.DefaultCommentGenerator
。默认的实现类中提供了两个可选属性,需要通过
<property>
属性进行配置。suppressAllComments
:**阻止**生成注释,默认为false
suppressDate
:**阻止**生成的注释包含时间戳,默认为false
一般情况下由于MBG生成的注释信息没有任何价值,而且有时间戳的情况下每次生成的注释都不一样,使用**版本控制**的时候每次都会提交,因而一般情况下我们都会屏蔽注释信息,可以如下配置:
<commentGenerator> <property name="suppressAllComments" value="true"/> <property name="suppressDate" value="true"/> </commentGenerator>
接下来我们简单举例实现生成包含表字段注释信息的注释
因为系统提供了一个默认的实现类,所以对我们来说,自己实现一个会很容易,最简单的方法就是复制默认实现类代码到一个新的文件中,修改类名如
MyCommentGenerator
,在你自己的实现类中,你可以选择是否继续支持上面的两个属性,你还可以增加对其他属性的支持。我们通过下面一个方法的修改来了解,其他几个方法请自行修改(写本章的时候我也没有完全实现该类,所以不提供完整源码了):
@Override public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) { if (introspectedColumn.getRemarks() != null && !introspectedColumn.getRemarks().equals("")) { field.addJavaDocLine("/**"); field.addJavaDocLine(" * " + introspectedColumn.getRemarks()); addJavadocTag(field, false); field.addJavaDocLine(" */"); } }
这个方法是给字段添加注释信息的,其中
IntrospectedColumn
包含了字段的完整信息,通过getRemarks
方法可以获取字段的注释信息。上面这个方法修改起来还是很容易的。除了字段的注释外还有Getter
和Setter
,以及类的注释。此外还有生成XML的注释,大家可以根据默认的实现进行修改。完成我们自己的实现类后,我们还需要做如下配置:
<commentGenerator type="com.github.abel533.mybatis.generator.MyCommentGenerator"/>
3.3.3
<jdbcConnection>
元素<jdbcConnection>
用于指定数据库连接信息,该元素必选,并且只能有一个。配置该元素只需要注意如果JDBC驱动不在**classpath**下,就需要通过
<classPathEntry>
元素引入jar包,这里**推荐**将jar包放到**classpath**下。该元素有两个必选属性:
driverClass
:访问数据库的JDBC驱动程序的完全限定类名connectionURL
:访问数据库的JDBC连接URL
该元素还有两个可选属性:
userId
:访问数据库的用户IDpassword
:访问数据库的密码
此外该元素还可以接受多个
<property>
子元素,这里配置的<property>
属性都会添加到JDBC驱动的属性中。这个元素配置起来最容易,这里举个简单例子:
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test" userId="root" password=""> </jdbcConnection>
3.3.4
<javaTypeResolver>
元素该元素最多可以配置一个。
这个元素的配置用来指定JDBC类型和Java类型如何转换。
该元素提供了一个可选的属性
type
,和<commentGenerator>
比较类型,提供了默认的实现DEFAULT
,一般情况下使用默认即可,需要特殊处理的情况可以通过其他元素配置来解决,不建议修改该属性。该属性还有一个可以配置的
<property>
元素。可以配置的属性为
forceBigDecimals
,该属性可以控制是否强制DECIMAL
和NUMERIC
类型的字段转换为Java类型的java.math.BigDecimal
,默认值为false
,一般不需要配置。默认情况下的转换规则为:
- 如果
精度>0
或者长度>18
,就会使用java.math.BigDecimal
- 如果
精度=0
并且10<=长度<=18
,就会使用java.lang.Long
- 如果
精度=0
并且5<=长度<=9
,就会使用java.lang.Integer
- 如果
精度=0
并且长度<5
,就会使用java.lang.Short
如果设置为
true
,那么一定会使用java.math.BigDecimal
,配置示例如下:<javaTypeResolver > <property name="forceBigDecimals" value="true" /> </javaTypeResolver>
3.3.5
<javaModelGenerator>
元素该元素必须配置一个,并且最多一个。
该元素用来控制生成的实体类,根据
<context>
中配置的defaultModelType
,一个表可能会对应生成多个不同的实体类。一个表对应多个类实际上并不方便,所以前面也推荐使用flat
,这种情况下一个表对应一个实体类。该元素只有两个属性,都是必选的。
targetPackage
:生成实体类存放的包名,一般就是放在该包下。实际还会受到其他配置的影响(<table>
中会提到)。targetProject
:指定目标项目路径,可以是绝对路径或相对路径(如 targetProject="src/main/java")。
该元素支持以下几个
<property>
子元素属性:constructorBased
:该属性只对MyBatis3
有效,如果true
就会使用构造方法入参,如果false
就会使用setter
方式。默认为false
。enableSubPackages
:如果true
,MBG会根据catalog
和schema
来生成子包。如果false
就会直接用targetPackage
属性。默认为false
。immutable
:该属性用来配置实体类属性是否可变,如果设置为true
,那么constructorBased
不管设置成什么,都会使用构造方法入参,并且不会生成setter
方法。如果为false
,实体类属性就可以改变。默认为false
。-
rootClass
:设置所有实体类的基类。如果设置,需要使用类的全限定名称。并且如果MBG能够加载rootClass
,那么MBG不会覆盖和父类中完全匹配的属性。匹配规则:- 属性名完全相同
- 属性类型相同
- 属性有
getter
方法 - 属性有
setter
方法
-
trimStrings
:是否对数据库查询结果进行trim
操作,如果设置为true
就会生成类似这样public void setUsername(String username) {this.username = username == null ? null : username.trim();}
的setter
方法。默认值为false
。
配置示例如下:
<javaModelGenerator targetPackage="test.model" targetProject="src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator>
3.3.6
<sqlMapGenerator>
元素该元素可选,最多配置一个。但是有如下两种必选的特殊情况:
- 如果
targetRuntime
目标是**iBATIS2**,该元素必须配置一个。 - 如果
targetRuntime
目标是**MyBatis3**,只有当<javaClientGenerator>
需要XML时,该元素必须配置一个。 如果没有配置<javaClientGenerator>
,则使用以下的规则:- 如果指定了一个
<sqlMapGenerator>
,那么MBG将只生成XML的SQL映射文件和实体类。 - 如果没有指定
<sqlMapGenerator>
,那么MBG将只生成实体类。
- 如果指定了一个
该元素只有两个属性(和前面提过的
<javaModelGenerator>
的属性含义一样),都是必选的。targetPackage
:生成实体类存放的包名,一般就是放在该包下。实际还会受到其他配置的影响(<table>
中会提到)。targetProject
:指定目标项目路径,可以是绝对路径或相对路径(如 targetProject="src/main/resources")。
该元素支持
<property>
子元素,只有一个可以配置的属性:enableSubPackages
:如果true
,MBG会根据catalog
和schema
来生成子包。如果false
就会直接用targetPackage
属性。默认为false
。
配置示例:
<sqlMapGenerator targetPackage="test.xml" targetProject="src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator>
3.3.7
<javaClientGenerator>
元素该元素可选,最多配置一个。
如果不配置该元素,就不会生成Mapper接口。
该元素有3个必选属性:
type
:该属性用于选择一个预定义的客户端代码(可以理解为Mapper接口)生成器,用户可以自定义实现,需要继承org.mybatis.generator.codegen.AbstractJavaClientGenerator
类,必选有一个默认的构造方法。 该属性提供了以下预定的代码生成器,首先根据<context>
的targetRuntime
分成三类:- MyBatis3:
- ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
- MIXEDMAPPER:XML和注解的混合形式,(上面这种情况中的)
SqlProvider
注解方法会被XML替代。 - XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
- MyBatis3Simple:
- ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
- XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
- Ibatis2Java2或**Ibatis2Java5**:
- IBATIS:生成的对象符合iBATIS的DAO框架(不建议使用)。
- GENERIC-CI:生成的对象将只依赖于SqlMapClient,通过构造方法注入。
- GENERIC-SI:生成的对象将只依赖于SqlMapClient,通过
setter
方法注入。 - SPRING:生成的对象符合Spring的DAO接口
- MyBatis3:
targetPackage
:生成实体类存放的包名,一般就是放在该包下。实际还会受到其他配置的影响(<table>
中会提到)。targetProject
:指定目标项目路径,可以是绝对路径或相对路径(如 targetProject="src/main/java")。
该元素还有一个可选属性:
implementationPackage
:如果指定了该属性,实现类就会生成在这个包中。
该元素支持
<property>
子元素设置的属性:enableSubPackages
exampleMethodVisibility
methodNameCalculator
rootInterface
useLegacyBuilder
这几个属性不太常用,具体作用请看完整的文档,这里对
rootInterface
做个简单介绍。rootInterface
用于指定一个所有生成的接口都继承的父接口。 这个值可以通过<table>
配置的rootInterface
属性覆盖。这个属性对于通用Mapper来说,可以让生成的所有接口都继承该接口。
配置示例:
<javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="src\main\java"/>
3.3.8
<table>
元素该元素至少要配置一个,可以配置多个。
该元素用来配置要通过内省的表。只有配置的才会生成实体类和其他文件。
该元素有一个必选属性:
tableName
:指定要生成的表名,可以使用SQL通配符匹配多个表。
例如要生成全部的表,可以按如下配置:
<table tableName="%" />
该元素包含多个可选属性:
schema
:数据库的schema,可以使用SQL通配符匹配。如果设置了该值,生成SQL的表名会变成如schema.tableName
的形式。catalog
:数据库的catalog,如果设置了该值,生成SQL的表名会变成如catalog.tableName
的形式。alias
:如果指定,这个值会用在生成的select查询SQL的表的别名和列名上。 列名会被别名为 alias_actualColumnName(别名_实际列名) 这种模式。domainObjectName
:生成对象的基本名称。如果没有指定,MBG会自动根据表名来生成名称。enableXXX
:XXX代表多种SQL方法,该属性用来指定是否生成对应的XXX语句。selectByPrimaryKeyQueryId
:DBA跟踪工具会用到,具体请看详细文档。selectByExampleQueryId
:DBA跟踪工具会用到,具体请看详细文档。modelType
:和<context>
的defaultModelType
含义一样,这里可以针对表进行配置,这里的配置会覆盖<context>
的defaultModelType
配置。escapeWildcards
:这个属性表示当查询列,是否对schema和表名中的SQL通配符 ('_' and '%') 进行转义。 对于某些驱动当schema或表名中包含SQL通配符时(例如,一个表名是MY_TABLE,有一些驱动需要将下划线进行转义)是必须的。默认值是false
。delimitIdentifiers
:是否给标识符增加**分隔符**。默认false
。当catalog
,schema
或tableName
中包含空白时,默认为true
。delimitAllColumns
:是否对所有列添加**分隔符**。默认false
。
该元素包含多个可用的
<property>
子元素,可选属性为:constructorBased
:和<javaModelGenerator>
中的属性含义一样。ignoreQualifiersAtRuntime
:生成的SQL中的表名将不会包含schema
和catalog
前缀。immutable
:和<javaModelGenerator>
中的属性含义一样。modelOnly
:此属性用于配置是否为表只生成实体类。如果设置为true
就不会有Mapper接口。如果配置了<sqlMapGenerator>
,并且modelOnly
为true
,那么XML映射文件中只有实体对象的映射元素(<resultMap>
)。如果为true
还会覆盖属性中的enableXXX
方法,将不会生成任何CRUD方法。rootClass
:和<javaModelGenerator>
中的属性含义一样。rootInterface
:和<javaClientGenerator>
中的属性含义一样。runtimeCatalog
:运行时的catalog
,当生成表和运行环境的表的catalog
不一样的时候可以使用该属性进行配置。runtimeSchema
:运行时的schema
,当生成表和运行环境的表的schema
不一样的时候可以使用该属性进行配置。runtimeTableName
:运行时的tableName
,当生成表和运行环境的表的tableName
不一样的时候可以使用该属性进行配置。selectAllOrderByClause
:该属性值会追加到selectAll
方法后的SQL中,会直接跟order by
拼接后添加到SQL末尾。useActualColumnNames
:如果设置为true,那么MBG会使用从数据库元数据获取的列名作为生成的实体对象的属性。 如果为false(默认值),MGB将会尝试将返回的名称转换为驼峰形式。 在这两种情况下,可以通过 元素显示指定,在这种情况下将会忽略这个(useActualColumnNames)属性。useColumnIndexes
:如果是true,MBG生成resultMaps的时候会使用列的索引,而不是结果中列名的顺序。useCompoundPropertyNames
:如果是true,那么MBG生成属性名的时候会将列名和列备注接起来. 这对于那些通过第四代语言自动生成列(例如:FLD22237),但是备注包含有用信息(例如:"customer id")的数据库来说很有用. 在这种情况下,MBG会生成属性名FLD2237_CustomerId。
除了
<property>
子元素外,<table>
还包含以下子元素:<generatedKey>
(0个或1个)<columnRenamingRule>
(0个或1个)<columnOverride>
(0个或多个)<ignoreColumn>
(0个或多个)
下面对这4个元素进行详细讲解。
1.
<generatedKey>
元素这个元素最多可以配置一个。
这个元素用来指定自动生成主键的属性(identity字段或者sequences序列)。如果指定这个元素,MBG在生成insert的SQL映射文件中插入一个<selectKey>
元素。 这个元素**非常重要**,这个元素包含下面两个必选属性:column
:生成列的列名。sqlStatement
:将返回新值的 SQL 语句。如果这是一个identity列,您可以使用其中一个预定义的的特殊值。预定义值如下:- Cloudscape
- DB2
- DB2_MF
- Derby
- HSQLDB
- Informix
- MySql
- SqlServer
- SYBASE
- JDBC:这会配置MBG使用MyBatis3支持的JDBC标准的生成key来生成代码。 这是一个独立于数据库获取标识列中的值的方法。 重要: 只有当目标运行为MyBatis3时才会产生正确的代码。 如果与iBATIS2一起使用目标运行时会产生运行时错误的代码。
这个元素还包含两个可选属性:
identity
:当设置为true
时,该列会被标记为identity
列, 并且<selectKey>
元素会被插入在insert
后面。 当设置为false
时,<selectKey>
会插入到insert
之前(通常是序列)。**重要**: 即使您type
属性指定为post
,您仍然需要为identity
列将该参数设置为true
。 这将标志MBG从插入列表中删除该列。默认值是false
。type
:type=post and identity=true
的时候生成的<selectKey>
中的order=AFTER
,当type=pre
的时候,identity只能为false
,生成的<selectKey>
中的order=BEFORE
。可以这么理解,自动增长的列只有插入到数据库后才能得到ID,所以是AFTER
,使用序列时,只有先获取序列之后,才能插入数据库,所以是BEFORE
。
配置示例一:
<table tableName="user login info" domainObjectName="UserLoginInfo"> <generatedKey column="id" sqlStatement="Mysql"/> </table>
对应的生成的结果:
<insert id="insert" parameterType="test.model.UserLoginInfo"> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into `user login info` (Id, username, logindate, loginip) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{logindate,jdbcType=TIMESTAMP}, #{loginip,jdbcType=VARCHAR}) </insert>
配置示例二:
<table tableName="user login info" domainObjectName="UserLoginInfo"> <generatedKey column="id" sqlStatement="select SEQ_ID.nextval from dual"/> </table>
对应的生成结果:
<insert id="insert" parameterType="test.model.UserLoginInfo"> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Integer"> select SEQ_ID.nextval from dual </selectKey> insert into `user login info` (Id, username, logindate, loginip) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{logindate,jdbcType=TIMESTAMP},#{loginip,jdbcType=VARCHAR}) </insert>
2.
<columnRenamingRule>
元素该元素最多可以配置一个,使用该元素可以在生成列之前,对列进行重命名。这对那些存在同一前缀的字段想在生成属性名时去除前缀的表非常有用。 例如假设一个表包含以下的列:
CUST_BUSINESS_NAME
CUST_STREET_ADDRESS
CUST_CITY
CUST_STATE
生成的所有属性名中如果都包含
CUST
的前缀可能会让人不爽。这些前缀可以通过如下方式定义重命名规则:<columnRenamingRule searchString="^CUST_" replaceString="" />
注意,在内部,MBG使用
java.util.regex.Matcher.replaceAll
方法实现这个功能。 请参阅有关该方法的文档和在Java中使用正则表达式的例子。当
<columnOverride>
匹配一列时,这个元素(<columnRenamingRule>
)会被忽略。<columnOverride>
优先于重命名的规则。该元素有一个必选属性:
searchString
:定义将被替换的字符串的正则表达式。
该元素有一个可选属性:
replaceString
:这是一个用来替换搜索字符串列每一个匹配项的字符串。如果没有指定,就会使用空字符串。
关于
<table>
的<property>
属性useActualColumnNames
对此的影响可以查看完整文档。3.
<columnOverride>
元素该元素可选,可以配置多个。
该元素从将某些属性默认计算的值更改为指定的值。
该元素有一个必选属性:
column
:要重写的列名。
该元素有多个可选属性:
property
:要使用的Java属性的名称。如果没有指定,MBG会根据列名生成。 例如,如果一个表的一列名为STRT_DTE
,MBG会根据<table>
的useActualColumnNames
属性生成STRT_DTE
或strtDte
。javaType
:该列属性值为完全限定的Java类型。如果需要,这可以覆盖由JavaTypeResolver
计算出的类型。 对某些数据库来说,这是必要的用来处理**“奇怪的”**数据库类型(例如MySql的unsigned bigint
类型需要映射为java.lang.Object
)。jdbcType
:该列的JDBC类型(INTEGER, DECIMAL, NUMERIC, VARCHAR
等等)。 如果需要,这可以覆盖由JavaTypeResolver
计算出的类型。 对某些数据库来说,这是必要的用来处理怪异的JDBC驱动 (例如DB2的LONGVARCHAR
类型需要为iBATIS 映射为VARCHAR
)。typeHandler
:用户定义的需要用来处理这列的类型处理器。它必须是一个继承iBATIS的TypeHandler
类或TypeHandlerCallback
接口(该接口很容易继承)的全限定的类名。如果没有指定或者是空白,iBATIS会用默认的类型处理器来处理类型。**重要**:MBG不会校验这个类型处理器是否存在或者可用。 MGB只是简单的将这个值插入到生成的SQL映射的配置文件中。delimitedColumnName
:指定是否应在生成的SQL的列名称上增加**分隔符**。 如果列的名称中包含空格,MGB会自动添加**分隔符**, 所以这个重写只有当列名需要强制为一个合适的名字或者列名是数据库中的保留字时是必要的。
配置示例:
<table schema="DB2ADMIN" tableName="ALLTYPES" > <columnOverride column="LONG_VARCHAR_FIELD" javaType="java.lang.String" jdbcType="VARCHAR" /> </table>
4.
<ignoreColumn>
元素该元素可选,可以配置多个。
该元素可以用来屏蔽不需要生成的列。
该元素有一个必选属性:
column
:要忽略的列名。
该元素还有一个可选属性:
delimitedColumnName
:匹配列名的时候是否区分大小写。如果为true
则区分。默认值为false
,不区分大小写。
MyBatis Generator最佳实践
本节内容针对MyBatis3,使用iBATIS的不一定适用。
以下根据个人经验(对此有意见的可以留言)对一些配置看法列出如下几点:
-
关于实体类的
modelType
,建议使用defaultModelType="flat"
,只有一个对象的情况下管理毕竟方便,使用也简单。 -
关于注释
<commentGenerator>
,不管你是否要重写自己的注释生成器,有一点不能忘记,那就是注释中一定要保留@mbggenerated
,MBG通过该字符串来判断代码是否为代码生成器生成的代码,有该标记的的代码在重新生成的时候会被删除,不会重复。不会在XML中出现重复元素。 -
使用MBG生成的代码时,建议尽可能不要去修改自动生成的代码,而且要生成带有
@mbggenerated
,这样才不会在每次重新生成代码的时候需要手动修改好多内容。 -
仍然是注释相关,在
<commentGenerator>
中,建议一定要保留suppressAllComments
属性(使用默认值false
),一定要取消(设为true
)时间戳suppressDate
,避免重复提交SVN。 -
<jdbcConnection>
建议将JDBC驱动放到项目的**classpath**下,而不是使用<classPathEntry>
来引入jar包,主要考虑到所有开发人员的统一性。 -
当数据库字段使用
CHAR
时,建议在<javaModelGenerator>
中设置<property name="trimStrings" value="true" />
,可以自动去掉不必要的空格。 -
在
<javaClientGenerator>
中,建议设置type="XMLMAPPER"
,不建议使用注解或混合模式,比较代码和SQL完全分离易于维护。 -
建议尽可能在
<table>
中配置<generatedKey>
,避免手工操作,以便于MBG重复执行代码生成。
如果有其他有价值的经验,会继续补充。
综合以上信息,这里给出一个
Mysql
的简单配置:<?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> <context id="MysqlContext" targetRuntime="MyBatis3" defaultModelType="flat"> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <commentGenerator> <property name="suppressDate" value="true"/> </commentGenerator> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test" userId="root" password=""> </jdbcConnection> <javaModelGenerator targetPackage="test.model" targetProject="src\main\java"> <property name="trimStrings" value="true" /> </javaModelGenerator> <sqlMapGenerator targetPackage="test.xml" targetProject="src\main\resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="src\main\java"/> <table tableName="%"> <generatedKey column="id" sqlStatement="Mysql"/> </table> </context> </generatorConfiguration>
<table>
这里用的通配符匹配全部的表,另外所有表都有自动增长的id
字段。如果不是所有表的配置都一样,可以做针对性的配置。改动:去掉来不建议使用Example查询的建议,Example在单表操作上优势明显,可以看个人情况使用。
-
-
Generator
2019-09-03 23:20:48执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 跟普通函数的区别 function关键字与函数名...简介
基本概念
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
跟普通函数的区别
function
关键字与函数名之间有一个星号;- 函数体内部使用
yield
表达式,定义不同的内部状态。 Generator
函数不能跟new一起使用,会报错。
定义一个普通函数和定义一个Generator惰性函数
// 定义一个普通函数 function auto(){ console.log('函数执行') } auto();//这是一个普通函数函数名字加上()执行函数内部的代码 // Generator函数的*号当前函数变懒了 function* auto(){//这种形式的函数叫做Generator函数 //一遍情况下*是紧跟function关键词后面写的 //加了*号让这个函数变懒了,即使加上函数名字启动依然没有执行内部的代码 console.log('函数执行'); console.log('函数执行'); console.log('函数执行'); } var result = auto();
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数
helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello
,world
和return
语句(结束执行)。调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象。
下一步,必须调用遍历器对象的
next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next方法可以恢复执行。function* auto(){ console.log('函数执行'); console.log('函数执行'); console.log('函数执行'); } /* [[GeneratorStatus]]: "suspended" GeneratorStatus:表示当前generator的当前状态 'suspended':表示当前函数暂停 */ var result = auto(); //普通函数默认返回值是return undefined,那么result的也是undefined, //但是当前的函数加上*号在也不是undefined的了
// 那么怎么让当前的generator函数启动呢? function* auto(){ console.log('函数执行'); console.log('函数执行'); console.log('函数执行'); } var result = auto() //generator函数的返回结果就是iterator接口 result.next();//必须调用next方法那能让这个惰性函数启动(继续执行)
ES6 没有规定,
function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用
next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。遍历器对象的
next
方法的运行逻辑如下。- 遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。 - 下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式。 - 如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。 - 如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
。
yield
表达式与return
语句既有相似之处都能返回紧跟在语句后面的那个表达式的值。
不同之处
每次遇到
yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。注意:
yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。另外,
yield
表达式如果用在另一个表达式之中,必须放在圆括号里面。console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield 123)); // OK
function* auto(){//当前generator函数是暂停的(suspended状态)generator是惰性函数 yield '我是yield断点的1';//yield是generator惰性函数的断点 yield '我是yield断点的2'; yield '我是yield断点的3'; //只要没有碰到yield这个惰性函数执行完毕 return 2;//return让这个generator函数结束,value值是你的返回结果 //done:true表示当前以及执行完毕(他会把当前return的返回值放在value里面表示当前函数执行完毕) //在当前generator函数中(惰性函数),yield关键词就是惰性函数的断点 //(有yield关键词就会让这个惰性函数停止到这个断点)再次调用才就能再次执行一次惰性函数, //知道执行完这个函数为止(直到没有yield断点) //value:yield后面跟的值 done:false(没有执行完当前函数 //value:undefined(执行完当前函数) done:true(执行完当前函数) } var gen = auto(); gen.next();//调用next函数才能让这个函数执行(会执行在当前函数yield断点位置) //可以理解yield是发动机执行一次就让当前的惰性函数执行一次 // gen.next(); // gen.next();
与 Iterator 接口的关系
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的
Symbol.iterator
属性,从而使得该对象具有 Iterator 接口。Object.prototype[Symbol.iterator] = function* (){ for(let i in this){ yield this[i]; } } //-------------- function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); }
next 方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
这个功能有很重要的语法意义。
Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过
next
方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); }//yield后面跟着数据他的返回结果是undefined var a = foo(5); //a是foo这个惰性函数的执行的结果,而foo(generator)执行的结果是一个iterator //接口当前状态是suspended暂停状态 a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
for…of 循环
for...of
循环可以自动遍历 Generator 函数时生成的Iterator
对象,且此时不再需要调用next
方法。function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
function* fibonacci() { let [prev, curr] = [1, 1]; while(true){ [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { if (n > 10000000) break; console.log(n); }
Generator.prototype.return()
Generator 函数返回的遍历器对象,还有一个
return
方法,可以返回给定的值,并且终结遍历 Generator 函数。function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true }
yield*
如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; foo(); yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "y"
foo
和bar
都是 Generator 函数,在bar里面调用foo
,是不会有效果的。这个就需要用到
yield*
表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同于 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同于 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){//next函数已经在for of循环里面自动的调用了 console.log(v); } // "x" // "a" // "b" // "y"
再来看一个对比的例子。
function* inner() { yield 'hello!'; } function* outer1() { yield 'open'; yield inner(); yield 'close'; } var gen = outer1() gen.next().value // "open" gen.next().value // 返回一个遍历器对象 gen.next().value // "close" function* outer2() { yield 'open' yield* inner() yield 'close' } var gen = outer2() gen.next().value // "open" gen.next().value // "hello!" gen.next().value // "close"
上面例子中,
outer2
使用了yield*
,outer1
没使用。结果就是,outer1
返回一个遍历器对象,outer2
返回该遍历器对象的内部值。从语法角度看,如果
yield
表达式后面跟的是一个遍历器对象,需要在yield
表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*
表达式。作为对象属性的 Generator 函数
如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
let obj = { * myGeneratorMethod() { ··· } };
-
generator
2018-09-07 09:56:11&lt;div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod=&...目前,已经有很多文章讨论过了如何使用ES6 generators来取代JavaScript中经常遇到的“回调金字塔”。但是,其中提到的绝大多数方法都需要依赖于某个库,而对于其中的原理却提及甚少。
在本文中,我们将一步一步的将一个基于回调函数的例子修改为一个基于generator的例子。本文的目标是让你透彻地理解使用generator替代回调函数的原理。
Generator是JavaScript中一个新概念,但在编程语言中已经存在已久。你可能已经在其他的编程语言例如Python使用过它。如果没有,也不要害怕,我们在后面已经为你准备了一个简单明了的入门介绍。
如何运行例子
在我们开始之前,你需要安装Node 0.11.* 来运行文章中的例子。当你在运行这些例子时,你需要告诉Node使用ES6(也就是Harmony)来运行:
node -harmony example.js
。什么是一个generator
在我们深入讲述如何使用generator替代回调函数之前,我们先来说说什么是generator。
Generator很像是一个函数,但是你可以暂停它的执行。你可以向它请求一个值,于是它为你提供了一个值,但是余下的函数不会自动向下执行直到你再次向它请求一个值。
取号机也许是对generator的一个绝佳的比喻。你可以通过取一张票来向机器请求一个号码。你接收了你的号码,但是机器不会自动为你提供下一个。换句话说,取票机“暂停”直到有人请求另一个号码,此时它才会向后运行。
ES6中的generator
Generator在ES6中像一个函数一样被声明,除了在之前有一个星号的差别外:
function* ticketGenerator(){}
当你想要一个generator提供一个值然后暂停时,你需要使用yield关键字。yield有点像是return关键字,因为它们都返回一个值,但是函数在yield之后会进入暂停状态。
function ticketGenerator(){
yield 1;
yield 2;
yield 3;
}
在我们的例子中,我们定义了一个叫做ticketGenerator的迭代器。如果你向它请求一个值,它会返回1然后暂停。如果你再次向它发出一个请求,我们将得到2,然后是3。
当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。
next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。
现在我们从我们的取号机中取一些号码:
var takeANumber = ticketGenerator();
takeANumber.next();
//>{value: 1, done: false}
takeANumber.next();
//>{value: 2, done: false}
takeANumber.next();
//>{value: 3, done: false}
takeANumber.next();
//>{value: undefined, done: true}
现在我们的取号系统只能提供最多到3的号码,这实在是没什么用。我们想要让它无线增加下去,因此我们来创建一个循环。
function* ticketGenerator(){
for(var i=0; true; i++){
yield i;
}
}
现在,如果这是一个普通的函数,我们每次只会得到0。但是使用generator却不一样:
var takeANumber = ticketGenerator();
console.log(takeANumber.next().value); //0
console.log(takeANumber.next().value); //1
console.log(takeANumber.next().value); //2
console.log(takeANumber.next().value); //3
console.log(takeANumber.next().value); //4
每一次当我们调用next()时,generator执行下一个循环迭代然后暂停。这意味着我们拥有一个可以无限向下运行的generator。因为这个generator只是发生了暂停,你并没有冻结你的程序。事实上,generator是一个创建无限循环的好方法。
影响generator的状态
进一步探究迭代迭代generator对象的话,next()实际上还有另一个用途。如果你给next传递一个值,它会被视为generator中的一个yield语句的结果来对待。
因此next是一个在generator运行过程中向其传递信息的方式。我们将以此来修改我们的取号generator以便它能够被重置到0.我们希望能在任何时间点来重置取号机。
function* ticketGenerator(){
for(var i=0; true; i++){
var reset = yield i;
if(reset) {i = -1;}
}
}
正如你所看到的,如果yield返回了一个true然后我们将i设置为-1。那么for循环将会在循环的结尾将i增加1,因此下一次返回的i变成了0。
我们来看看实际情况如何:
var takeANumber = ticketGenerator(); console.log(takeANumber.next().value); //0 console.log(takeANumber.next().value); //1 console.log(takeANumber.next().value); //2 console.log(takeANumber.next(true).value); //0 console.log(takeANumber.next().value); //1
用generator替代回调函数
既然我们已经学到了一些关于generator的知识,现在让我们来谈谈generator和回调函数。正如你所知道的,当我们调用例如AJAX请求这样的异步代码时我们会使用回调函数。为了简单起见我们在例子中定义一个delay函数。
我们的delay函数将会是异步的 – 在指定的时间过后我们提供给delay的回调函数才会被执行,然后delay会给你的回调函数传递一个字符串告诉它究竟“沉睡”了多久。
在此期间你的其余代码将会继续执行下去。这就好像是进行一个AJAX请求一样 – 你发出请求,你的代码继续执行,当服务器返回一个结果时你的回调函数才执行。
现在,我们来定义delay函数:
function delay(time, callback){
setTimeout(function(){
callback("Slept for "+time);
},time);
}
到目前为止,还没有什么特别的东西。现在我们来使用它来delay两次。首先我们将delay1000ms,然后当delay结束后我们再另外delay 1200ms。
delay(1000,function(msg){
console.log(msg);
delay(1200,function(msg){
console.log(msg);
});
})
//...waits 1000ms
// > "Slept for 1000"
//...waits another 1200ms
// > "Slept for 1200
确保我们的两个delay依次被调用的唯一方法就是确保第二个delay在第一个delay的回调函数中。
如果我们要依次delay 12次,我们将需要嵌套的调用12次delay函数。这时你就会碰到“回调金字塔”,代码也变得丑陋不堪。
引入generator
Generator是解决“回调地狱”的有效方法之一。异步调用是很困难的事情,因为我们的函数不会等待异步调用完成,因此我们需要回调函数。
使用generator,我们可以让我们的代码进行等待。无需嵌套回调函数,我们可以使用generator确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
因此,如果我们可以在一个异步调用完成时暂停执行,这就意味着我们可以依次调用delay函数 – 就像delay函数是同步执行的一样。
我们应该怎么做
首先,我们知道我们进行异步调用的代码需要在一个generator而不是一个一般的函数中进行,因此我们来定义一个generator。
function* myDelayedMessages() {
/* delay 1000ms然后打印结果 */
/* delay 1000ms然后打印结果 */
}
接下来我们需要在我们的generator中调用delay。记住,delay接收一个回调函数。这个回调函数需要继续我们的generator,但是我们现在还没有一个generator因此我们先放上一个空函数。
function* myDelayedMessages(){
console.log(delay(1000,function(){}));
console.log(delay(1200,function(){}));
}
我们代码依然是异步的。这是因为我们还没有将放入任何的yield语句。Generator只是在它们看大一个yield语句时才暂停。
function* myDelayedMessages() {
console.log(yield delay(1000, function(){}));
console.log(yield delay(1200, function(){}));
}
我们现在已经更接近了一点了。然而,如果我们运行我们的generator什么也不会发生。因为没有什么东西告诉它要向下运行。
在这里你需要理解的最重要的概念是:generator需要在delay中的回调函数运行完成后继续往下运行,这就是它们如何知道暂停应该结束了的原因。
这意味着回调函数中的东西需要知道如何向前推动generator。我们在其中传递一个叫做resume的函数来为我们做这件事。记住我们现在还依然没有定义resume。
function* myDelayedMessages(resume) {
console.log(yield delay(1000, resume));
console.log(yield delay(1200, resume));
}
OK,现在我们的generator将会接收一个resume函数,这个函数将会向前推动generator。
现在到了关键步骤了,我们如何来编写resume,它又是怎么来了解我们的generator的。
如果你看看其他使用generator代替回调函数的例子,你会看到generator函数总是被另一个函数包裹着 – 通常是一个叫做“run”或者“execute”的函数。这些函数的目的有以下几个:
- 接收一个generator作为参数
- 使用这个generator来创建一个新的generator迭代器对象,我们将调用它的next方法
- 创建一个resume函数来使用这个generator迭代器对象来推进generator
- 将resume函数传递给这个generator以便generator能够访问resume
在最开始时调用next()函数,以便我们的代码在碰到第一个yield之前开始执行
现在我们来创建run函数:
function run(generatorFunction) { var generatorItr = generatorFunction(resume); function resume(callbackValue) { generatorItr.next(callbackValue); } generatorItr.next()}
现在我们有了一个能够接收一个generator函数的函数,并为它传递了一个了解如何推进generator迭代器对象的函数。注意到我们在resume函数中用到了next的第二个特性。resume是被传递给delay的回调函数,因此它接收delay函数提供的值。resume将这个值传递给next,因此yield语句的结果实际上是我们异步函数的结果!
我们现在要做的只是用run包裹上我们的generator函数,然后我们就能看到以下结果:
run(function* myDelayedMessages(resume) {
console.log(yield delay(1000, resume));
console.log(yield delay(1200, resume));
})
//...waits 1000ms
// > "Slept for 1000" //...waits 1200ms
// > "Slept for 1200"
现在,你能看到我们调用delay两次,并没有使用嵌套回调函数。如果你依然看到疑惑,我们现在概括的来讲述以下究竟发生了什么:
- run接收了我们的generator并创建了一个resume函数
- run创建了一个generator迭代器对象(我们在它上面调用next方法),提供了resume函数。接着它推动了generator迭代器向前运行。
- 我们的generator碰到了第一个yield语句并且调用delay。接着这个generator暂停。
- delay在1000ms之后完成然后调用resume。
- resume告诉我们的generator进行下一步。它将结果传递给delay以便console能够将它打印出来。
- 我们的generator碰到了第二个yield,调用delay然后再次暂停。
delay等待1200ms之后调用resume回调函数。 - resume再次推进generator。
- 再也没有yield的调用,这个generator完成执行。
结论我们已经成功的使用generator替代了回调嵌套方法。总结一下,使用generator替代回调函数要包含以下几个步骤:
- 创建一个run函数来接受一个generator,并为这个generator提供resume函数。
- 创建一个resume函数来推进generator,然后在resume被异步函数调用时将这个resume函数传递给generator。
- 将resume作为回调传递给我们所有的异步回调函数。这些异步函数在完成时执行resume,这使得我们的generator在每个异步调用完成之时仅仅向前一步。
虽然generator究竟是不是一个处理“回调地狱”的好方法还在讨论之中,但是它确实是一个加强你对ES6中generator和迭代器理解的练习。如果你在寻找一些不需要用到ES6的处理嵌套回调函数的方法,可以考虑promises。
本文译自Replacing callbacks with ES6 Generators,原文地址http://flippinawesome.org/2014/02/10/replacing-callbacks-with-es6-generators/
-
mybatis-generator xml,java合并问题
2017-12-15 14:49:32mybatis-generator 简介 Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们可以利用Mybatis-Generator来帮我们自动生成文件。项目中常用的: Student...mybatis-generator 简介
Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们可以利用Mybatis-Generator来帮我们自动生成文件。项目中常用的: StudentExample example = new StudentExample(); StudentExample.Criteria criteria = example.createCriteria(); deleteByExample insert insertSelective selectByExample ..等等诸如此类的方法,均是通过mybatis-genenrator可以自动生成的 使得开发起来很方便。 mybatis-generator 生成的sql xml几乎涵盖了单表的所有增删改查方法。很方便
mybatis-generator 问题
(mybatis-generator默认合并指的是 123 , 234 合并完 是 123234,而我们需要的结果是1234)
1 生成一次 CourseMapper.java,CourseMapper.xml等文件。 2 现在需要加个批量插入方法,在CouseMapper.java里加 public int batchInsert(List<Course> list); 在CourseMapper.xml里加 <insert id="batchInsert" parameterType="cc.gukeer.smartBoard.persistence.entity.Course"> your sql </insert> 3 现在数据库加了个字段a,需要对文件再次生成 我们发现,xml里面,updateByPrimaryKey 这样的方法再次生成了一次,而且CourseMapper.java里 的batchInsert 这个方法已经没有了。之前写的代码也就被覆盖了或者出现了问题。我们必须得回滚。。 4 为了解决这个问题,我们不得不扩展 CourseMapper.xml 写 A_CourseMapper.xml 对应 CourseMapper.xml 写 A_CourseMapper.xml 对应 CourseMapper.xml 并把我们的批量方法写到A_***里面,这样再次生成就不会被覆盖了。那么问题来了:
从图中可以看到,这无疑使得我们的文件更繁多,维护更加复杂。
问题解决-xml合并
出现这个问题的原因,我们主要来看一下generator的源码: org.mybatis.generator.api.MyBatisGenerator 的generate方法,是生成文件的一个入口。 for (GeneratedXmlFile gxf : this.generatedXmlFiles){ this.projects.add(gxf.getTargetProject()); File targetFile; String source; try{ File directory = this.shellCallback.getDirectory(gxf.getTargetProject(), gxf.getTargetPackage()); targetFile = new File(directory, gxf.getFileName()); if (targetFile.exists()){ String source; if (gxf.isMergeable()){ source = XmlFileMergerJaxp.getMergedSource(gxf, targetFile); }else if (this.shellCallback.isOverwriteEnabled()){ String source = gxf.getFormattedContent(); this.warnings.add(Messages.getString("Warning.11", targetFile.getAbsolutePath())); }else{ String source = gxf.getFormattedContent(); targetFile = getUniqueFileName(directory, gxf.getFileName()); this.warnings.add(Messages.getString("Warning.2", targetFile.getAbsolutePath())); } }
其中 有对gxf(GeneratedXmlFile) 是否合并判断,如果合并true,将新旧xml通过这个方法合并取合并得到的结果字符串,写到新的文件中。 XmlFileMergerJaxp.getMergedSource
XmlFileMergerJaxp.getMergedSource 部分代码如下:
DocumentType newDocType = newDocument.getDoctype(); ... List<Node> nodesToDelete = new ArrayList(); NodeList children = existingRootElement.getChildNodes(); int length = children.getLength(); for (int i = 0; i < length; i++) { Node node = children.item(i); if (isGeneratedNode(node)) { nodesToDelete.add(node); } else if ((isWhiteSpace(node)) && (isGeneratedNode(children.item(i + 1)))) { nodesToDelete.add(node); } } for (Node node : nodesToDelete) { existingRootElement.removeChild(node); }
这里采用的是org.w3c.dom.* 来解析的新旧xml文件。我们发现,这里有对旧文件遍历,判断这个节点方法是否已经生成过。如果是已经生成 isGeneratedNode的节点,那么加入到 nodesToDelete里面,最后把它给移除。那么问题就出在这个 isGeneratedNode判断方法上
private static boolean isGeneratedNode(Node node) { boolean rc = false; if ((node != null) && (node.getNodeType() == 1)) { Element element = (Element)node; String id = element.getAttribute("id"); if (id != null) { for (String prefix : MergeConstants.OLD_XML_ELEMENT_PREFIXES) { if (id.startsWith(prefix)) { rc = true; break; } } } if (!rc) { NodeList children = node.getChildNodes(); int length = children.getLength(); for (int i = 0; i < length; i++) { Node childNode = children.item(i); if (!isWhiteSpace(childNode)) { if (childNode.getNodeType() != 8) { break; } Comment comment = (Comment)childNode; String commentData = comment.getData(); for (String tag : MergeConstants.OLD_ELEMENT_TAGS) { if (commentData.contains(tag)) { rc = true; break; } } } } } } return rc; }
其实我们的需求是,之前有个方法,又生成了
,我们当然想用新的覆盖旧的,但是这里的判断显然不是根据这个id来判断的,所以这个方法返回false,这个方法又会追加生成一次。说到这里,我们只需要这样稍微修改一下即可:将新生成的节点列表保存为一个集合a。判断旧节点的id是否包含在这个集合a里面,如果在,那么旧的节点就删除。
具体代码如下:
//获取新生成的所有xml,所有element的id列表,删除之前同名的结点 NodeList newMethods = newRootElement.getChildNodes(); List<String> methods = new ArrayList<String>(); for (int i = 0; i < newMethods.getLength(); i++) { Node node = newMethods.item(i); try { if (node instanceof DeferredTextImpl) { continue; } Element ele = (Element) node; methods.add(ele.getAttribute("id")); } catch (Exception e) { //#text节点转换会异常 continue; } if (i == newMethods.getLength() - 1) { if (isWhiteSpace(node)) { break; } } } private static boolean isGeneratedNode(Node node, List<String> methods) { boolean rc = false; if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; String id = element.getAttribute("id"); //$NON-NLS-1$ if (methods.contains(id)) { return true; } if (id != null) { for (String prefix : MergeConstants.OLD_XML_ELEMENT_PREFIXES) { if (id.startsWith(prefix)) { rc = true; break; } } } 这样我们就能实现 xml新的覆盖旧的,并且保留旧的文件里自己写的方法,达到合并的效果。
问题解决-java合并
//和xml类似,这个是java代码合并判断代码 for (GeneratedJavaFile gjf : this.generatedJavaFiles) { this.projects.add(gjf.getTargetProject()); try { File directory = this.shellCallback.getDirectory(gjf.getTargetProject(), gjf.getTargetPackage()); File targetFile = new File(directory, gjf.getFileName()); String source; if (targetFile.exists()) { String source; if (this.shellCallback.isMergeSupported()){ source = this.shellCallback.mergeJavaFile(gjf.getFormattedContent(), targetFile.getAbsolutePath(), MergeConstants.OLD_ELEMENT_TAGS, gjf.getFileEncoding()); }else if (this.shellCallback.isOverwriteEnabled()){ String source = gjf.getFormattedContent(); this.warnings.add(Messages.getString("Warning.11", targetFile.getAbsolutePath())); } else{ String source = gjf.getFormattedContent(); targetFile = getUniqueFileName(directory, gjf.getFileName()); this.warnings.add(Messages.getString("Warning.2", targetFile.getAbsolutePath())); } }
我们来看看java代码合并的实现:this.shellCallback.mergeJavaFile
public String mergeJavaFile(String newFileSource, String existingFileFullPath, String[] javadocTags, String fileEncoding) throws ShellException { throw new UnsupportedOperationException(); }
很遗憾,它并不支持java合并。我们需要自己实现java合并。此处参考:http://blog.csdn.net/w980994974/article/details/76904587
直接上代码:package org.mybatis.generator.internal; import com.github.javaparser.JavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.TypeDeclaration; import org.mybatis.generator.config.MergeConstants; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.mybatis.generator.api.dom.OutputUtilities.newLine; public class JavaFileMergerJaxp { public String getNewJavaFile(String newFileSource, String existingFileFullPath) throws FileNotFoundException { CompilationUnit newCompilationUnit = JavaParser.parse(newFileSource); CompilationUnit existingCompilationUnit = JavaParser.parse(new File(existingFileFullPath)); return mergerFile(newCompilationUnit, existingCompilationUnit); } public String mergerFile(CompilationUnit newCompilationUnit, CompilationUnit existingCompilationUnit) { System.out.println("合并java代码"); StringBuilder sb = new StringBuilder(newCompilationUnit.getPackageDeclaration().get().toString()); newCompilationUnit.removePackageDeclaration(); //合并imports NodeList<ImportDeclaration> imports = newCompilationUnit.getImports(); imports.addAll(existingCompilationUnit.getImports()); Set importSet = new HashSet<ImportDeclaration>(); importSet.addAll(imports); NodeList<ImportDeclaration> newImports = new NodeList<ImportDeclaration>(); newImports.addAll(importSet); newCompilationUnit.setImports(newImports); for (ImportDeclaration i : newCompilationUnit.getImports()) { sb.append(i.toString()); } newLine(sb); NodeList<TypeDeclaration<?>> types = newCompilationUnit.getTypes(); NodeList<TypeDeclaration<?>> oldTypes = existingCompilationUnit.getTypes(); for (int i = 0; i < types.size(); i++) { //截取Class String classNameInfo = types.get(i).toString().substring(0, types.get(i).toString().indexOf("{") + 1); sb.append(classNameInfo); newLine(sb); newLine(sb); //合并fields List<FieldDeclaration> fields = types.get(i).getFields(); List<FieldDeclaration> oldFields = oldTypes.get(i).getFields(); List<FieldDeclaration> newFields = new ArrayList<FieldDeclaration>(); HashSet<FieldDeclaration> fieldDeclarations = new HashSet<FieldDeclaration>(); fieldDeclarations.addAll(fields); fieldDeclarations.addAll(oldFields); newFields.addAll(fieldDeclarations); for (FieldDeclaration f : newFields) { sb.append("\t" + f.toString()); newLine(sb); newLine(sb); } //合并methods List<MethodDeclaration> methods = types.get(i).getMethods(); List<MethodDeclaration> existingMethods = oldTypes.get(i).getMethods(); for (MethodDeclaration f : methods) { String res = f.toString().replaceAll("\r\n", "\r\n\t"); sb.append("\t" + res); newLine(sb); newLine(sb); } List<String> methodList = new ArrayList<String>(); for (MethodDeclaration m : methods) { methodList.add(m.getName().toString()); } methodList.add("toString"); methodList.add("hashCode"); methodList.add("equals"); for (MethodDeclaration m : existingMethods) { if (methodList.contains(m.getName().toString())) { continue; } boolean flag = true; for (String tag : MergeConstants.OLD_ELEMENT_TAGS) { if (m.toString().contains(tag)) { flag = false; break; } } if (flag) { String res = m.toString().replaceAll("\r\n", "\r\n\t"); sb.append("\t" + res); newLine(sb); newLine(sb); } } //判断是否有内部类 types.get(i).getChildNodes(); for (Node n : types.get(i).getChildNodes()) { if (n.toString().contains("static class")) { String res = n.toString().replaceAll("\r\n", "\r\n\t"); sb.append("\t" + res); } } } return sb.append(System.getProperty("line.separator") + "}").toString(); } }
参考 上面网址得到的代码稍微加了一些新旧方法的判断,逻辑和xml合并类似。不再赘述。
ps:其中java,xml是覆盖模式,还是合并模式,通过配置实现,
<context id="MySQLTables" targetRuntime="MyBatis3"> <!--true为合并,false为覆盖,新生成的文件会完全覆盖旧文件--> <property name="xmlMergeable" value="true" /> <property name="javaMergeable" value="true" />
总结
源码修改好了之后,jar包下载地址:(这个jar包是我放贵了对不起各位看客啊,你们需要的话,私聊我,qq:1211079133,免费给你。。。。。不要下载了,否则帐号被封了,说我刷分,我现在删除不了这个jar包了)
http://download.csdn.net/download/zjy1211079133/10159514使用方法:(下载jar包,替换你本地的pom使用的jar包 mybatis-generator-core-version.jar)
pom.xml配置:
<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin>
mybatis-generator.xml 配置:(其他公用配置自行百度)
<context id="MySQLTables" targetRuntime="MyBatis3"> <!--true为合并,false为覆盖,新生成的文件会完全覆盖旧文件--> <property name="xmlMergeable" value="true" /> <property name="javaMergeable" value="true" />
生成即可。具体参照:http://blog.csdn.net/isea533/article/details/42102297 使用
-
mybatis-generator 源码浅析与修改
2020-09-27 09:02:12title: mybatis-generator 源码浅析与修改 author: zjy date: 2017-12-15 14:42:59 tags: mybatis-generator 简介 Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写... -
mybatis-generator源码解析和部分修改
2020-09-28 08:24:54mybatis-generator 简介 Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们可以利用Mybatis-Generator来帮我们自动生成文件。项目中常用的: ... -
Generator函数
2020-04-01 12:24:06Generator函数 生成器generator是ES6标准引入的新的数据类型,一个generator看上去像一个函数,但可以返回多次,通过yield关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。 ... -
IDEA集成MyBatis Generator 插件 详解
2018-05-03 17:50:421、修改maven的pom文件 只需要将如下依赖添加到pom.xml文件中即可。(注意此处是以plugin的方式,放在<plugins><...org.mybatis.generator</groupId> <artifactId>mybati... -
关于使用mybatis-generator报错 Failed to execute goal org.mybatis.generator:mybatis-generator-maven-...
2019-01-15 13:54:57我查看了很多博客,有的说版本问题: ...有的说路径问题: ...根据这些建议修改,都没成功解决,后来我把generator.properties中的参数配置等号后面的引号都去...
-
21年新消息队列RabbitMQ视频教程AMQP教程
-
这是一个简易版租房合同
-
深度强化学习与GAN课程:深度学习中的高级主题
-
UE4 的 kDopTree
-
设计模式之简单工厂模式和抽象工厂模式
-
Forest 使用简介
-
Gof-命令模式
-
测试之第二十九集QQ思维导图之联系人
-
亚像素级别处理圆心坐标计算程序
-
leetcode_124 二叉树的最大路径和
-
Python专题精讲 文件目录与系统参数操作
-
c语言扫雷初阶
-
PythonExtensionPatterns:Python C扩展的安全编码实践示例-源码
-
机器学习手写数字识别系统项目完整代码和课设报告
-
Static关键字详解
-
直到:今天我学会了。-源码
-
前端学习 02 —— CSS 02
-
2021-01-28
-
UE4游戏逆向与安全+FPS游戏逆向与安全
-
nb:CLI纯文本笔记,书签和存档,以及加密,过滤和搜索,Git支持的版本控制和同步,Pandoc支持的转换等-源码