背景

项目中使用Mybatis做持久层框架,但由于开发成员水平不一,写dao的时候,各有各的偏好,有时候还会写出带sql注入漏洞的代码。

出现sql注入漏洞,一般是#和$的区别没弄明白:
$ 直接把字符串原封不动的搬进sql,有sql注入的风险
# 是预留一个问号,作为参数插入的,即可通过预编译sql的方式避免sql注入

于是想使用Mybatis generator这个工具来统一生成代码(java bean,mapper,xml)

使用

Mybatis generator可以通过如下方式运行

  • 命令行
下载mybatis-generator-core.jar,然后配置generatorConfig.xml文件,执行如下命令
java -jar mybatis-generator-core-1.3.7.jar -configfile generatorConfig.xml -overwrite
  • IDE插件,run as
安装eclipse/idea插件
  • 通过main方法执行
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(MainGenerate.class.getClassLoader().getResourceAsStream("generatorConfig.xml")); DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
System.out.println("----done----");
}

问题及解决方法

分页问题

默认生成的xml是没有分页查询的,可通过

<plugin type="org.mybatis.generator.plugins.RowBoundsPlugin">

来实现分页,不过...

  • 在低版本的generator插件里是不包含这个的。
  • 使用这个插件生成分页代码后,会多一个selectByExampleWithRowbounds(XxxExample example, RowBounds rowBounds) 的方法,但是XxxMapper.xml文件中的selectByExampleWithRowbounds元素,可以发现select语句并没有使用limit 或者 rownum。

    实际上RowBounds原理是通过ResultSet的游标来实现分页,容易出现性能问题
解决办法

可使用pagehelper来解决,See Github

除此之外,我们也可以通过自定义分页插件来解决

  • Oracle插件
package com.yejg.mybatis.generator.plugins;

// 省略import

public class OraclePaginationPlugin extends PluginAdapter {

	public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper(); Field begin = new Field();
begin.setName("begin");
begin.setVisibility(JavaVisibility.PRIVATE);
begin.setType(integerWrapper);
topLevelClass.addField(begin);
context.getCommentGenerator().addFieldComment(begin, introspectedTable); Method setBegin = new Method();
setBegin.setVisibility(JavaVisibility.PUBLIC);
setBegin.setName("setBegin");
setBegin.addParameter(new Parameter(integerWrapper, "begin"));
setBegin.addBodyLine("this.begin = begin;");
topLevelClass.addMethod(setBegin);
context.getCommentGenerator().addGeneralMethodComment(setBegin, introspectedTable); Method getBegin = new Method();
getBegin.setVisibility(JavaVisibility.PUBLIC);
getBegin.setReturnType(integerWrapper);
getBegin.setName("getBegin");
getBegin.addBodyLine("return begin;");
topLevelClass.addMethod(getBegin);
context.getCommentGenerator().addGeneralMethodComment(getBegin, introspectedTable); Field end = new Field();
end.setName("end");
end.setVisibility(JavaVisibility.PRIVATE);
end.setType(integerWrapper);
topLevelClass.addField(end);
context.getCommentGenerator().addFieldComment(end, introspectedTable); Method setEnd = new Method();
setEnd.setVisibility(JavaVisibility.PUBLIC);
setEnd.setName("setEnd");
setEnd.addParameter(new Parameter(integerWrapper, "end"));
setEnd.addBodyLine("this.end = end;");
topLevelClass.addMethod(setEnd);
context.getCommentGenerator().addGeneralMethodComment(setEnd, introspectedTable); Method getEnd = new Method();
getEnd.setVisibility(JavaVisibility.PUBLIC);
getEnd.setReturnType(integerWrapper);
getEnd.setName("getEnd");
getEnd.addBodyLine("return end;");
topLevelClass.addMethod(getEnd);
context.getCommentGenerator().addGeneralMethodComment(getEnd, introspectedTable); return true;
} @Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
XmlElement parentElement = document.getRootElement(); XmlElement paginationPrefixElement = new XmlElement("sql");
paginationPrefixElement.addAttribute(new Attribute("id", "Oracle_Paging_Prefix"));
XmlElement pageStart = new XmlElement("if");
pageStart.addAttribute(new Attribute("test", "begin != null and end != null"));
pageStart.addElement(new TextElement("select * from ( select row_.*, rownum rownum_ from ( "));
context.getCommentGenerator().addComment(paginationPrefixElement);
paginationPrefixElement.addElement(pageStart);
parentElement.addElement(paginationPrefixElement); XmlElement paginationSuffixElement = new XmlElement("sql");
paginationSuffixElement.addAttribute(new Attribute("id", "Oracle_Paging_Suffix"));
XmlElement pageEnd = new XmlElement("if");
pageEnd.addAttribute(new Attribute("test", "begin != null and end != null"));
pageEnd.addElement(new TextElement("<![CDATA[ ) row_ ) where rownum_ > #{begin} and rownum_ <= #{end} ]]>"));
context.getCommentGenerator().addComment(paginationSuffixElement);
paginationSuffixElement.addElement(pageEnd);
parentElement.addElement(paginationSuffixElement); return super.sqlMapDocumentGenerated(document, introspectedTable);
} @Override
public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) { XmlElement pageStart = new XmlElement("include"); //$NON-NLS-1$
pageStart.addAttribute(new Attribute("refid", "Oracle_Paging_Prefix"));
// context.getCommentGenerator().addComment(pageStart);
element.getElements().add(0, pageStart); XmlElement isNotNullElement = new XmlElement("include"); //$NON-NLS-1$
isNotNullElement.addAttribute(new Attribute("refid", "Oracle_Paging_Suffix"));
// context.getCommentGenerator().addComment(isNotNullElement);
element.getElements().add(isNotNullElement); return super.sqlMapUpdateByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
} /**
* This plugin is always valid - no properties are required
*/
public boolean validate(List<String> warnings) {
return true;
}
}
  • MySQl插件
package com.yejg.mybatis.generator.plugins;

// 省略import

public class MySQLPaginationPlugin extends PluginAdapter {

	@Override
public boolean validate(List<String> list) {
return true;
} /**
* 为每个Example类添加limit和offset属性已经set、get方法
*/
@Override
public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) { PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper(); Field limit = new Field();
limit.setName("limit");
limit.setVisibility(JavaVisibility.PRIVATE);
limit.setType(integerWrapper);
topLevelClass.addField(limit); Method setLimit = new Method();
setLimit.setVisibility(JavaVisibility.PUBLIC);
setLimit.setName("setLimit");
setLimit.addParameter(new Parameter(integerWrapper, "limit"));
setLimit.addBodyLine("this.limit = limit;");
topLevelClass.addMethod(setLimit); Method getLimit = new Method();
getLimit.setVisibility(JavaVisibility.PUBLIC);
getLimit.setReturnType(integerWrapper);
getLimit.setName("getLimit");
getLimit.addBodyLine("return limit;");
topLevelClass.addMethod(getLimit); Field offset = new Field();
offset.setName("offset");
offset.setVisibility(JavaVisibility.PRIVATE);
offset.setType(integerWrapper);
topLevelClass.addField(offset); Method setOffset = new Method();
setOffset.setVisibility(JavaVisibility.PUBLIC);
setOffset.setName("setOffset");
setOffset.addParameter(new Parameter(integerWrapper, "offset"));
setOffset.addBodyLine("this.offset = offset;");
topLevelClass.addMethod(setOffset); Method getOffset = new Method();
getOffset.setVisibility(JavaVisibility.PUBLIC);
getOffset.setReturnType(integerWrapper);
getOffset.setName("getOffset");
getOffset.addBodyLine("return offset;");
topLevelClass.addMethod(getOffset); return true;
} /**
* 为Mapper.xml的selectByExample添加limit
*/
@Override
public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) { XmlElement ifLimitNotNullElement = new XmlElement("if");
ifLimitNotNullElement.addAttribute(new Attribute("test", "limit != null")); XmlElement ifOffsetNotNullElement = new XmlElement("if");
ifOffsetNotNullElement.addAttribute(new Attribute("test", "offset != null"));
ifOffsetNotNullElement.addElement(new TextElement("limit ${offset}, ${limit}"));
ifLimitNotNullElement.addElement(ifOffsetNotNullElement); XmlElement ifOffsetNullElement = new XmlElement("if");
ifOffsetNullElement.addAttribute(new Attribute("test", "offset == null"));
ifOffsetNullElement.addElement(new TextElement("limit ${limit}"));
ifLimitNotNullElement.addElement(ifOffsetNullElement); element.addElement(ifLimitNotNullElement); return true;
}
}

生成的xml不是覆盖旧文件,有时还有重复的段

问题原因在于:

在IntrospectedTableMyBatis3Impl.getGeneratedXmlFiles方法中,isMergeable值被写死为true了。

GeneratedXmlFile gxf = new GeneratedXmlFile(document,
getMyBatis3XmlMapperFileName(), getMyBatis3XmlMapperPackage(),
context.getSqlMapGeneratorConfiguration().getTargetProject(),
true, context.getXmlFormatter());

而MyBatisGenerator.writeGeneratedXmlFile方法中使用到该属性了。代码如下:

if (targetFile.exists()) {
if (gxf.isMergeable()) {
source = XmlFileMergerJaxp.getMergedSource(gxf, targetFile);
} else if (shellCallback.isOverwriteEnabled()) {
source = gxf.getFormattedContent();
warnings.add(getString("Warning.11", targetFile.getAbsolutePath()));
} else {
source = gxf.getFormattedContent();
targetFile = getUniqueFileName(directory, gxf.getFileName());
warnings.add(getString("Warning.2", targetFile.getAbsolutePath()));
}
} else {
source = gxf.getFormattedContent();
}
解决办法

方法一:可直接修改源码,把isMergeable写成false

方法二:拿到GeneratedXmlFile对象,通过反射把isMergeable改成false

// 可以在前面自定义的Plugin中,sqlMapGenerated方法中拿到GeneratedXmlFile对象
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
try {
java.lang.reflect.Field field = sqlMap.getClass().getDeclaredField("isMergeable");
field.setAccessible(true);
field.setBoolean(sqlMap, false);
} catch (Exception e) { }
return true;
}

注释问题

默认的注释完全没什么用,不如自定义注释,把数据库表字段的注释作为bean字段的注释

解决办法
public class MyCommentGenerator implements CommentGenerator {

	private Properties systemPro;
private boolean suppressAllComments;
private SimpleDateFormat dateFormat; public MyCommentGenerator() {
super();
systemPro = System.getProperties();
suppressAllComments = false;
dateFormat = new SimpleDateFormat("yyyy-MM-dd");
} /**
* 生成java model的类头上的注释
*/
@Override
public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
} topLevelClass.addJavaDocLine("/**");
StringBuffer sb = new StringBuffer();
sb.append(" * ");
sb.append(introspectedTable.getRemarks());
sb.append(" [");
sb.append(introspectedTable.getFullyQualifiedTable().toString().toLowerCase());
sb.append("]");
topLevelClass.addJavaDocLine(sb.toString());
topLevelClass.addJavaDocLine(" * @author " + systemPro.getProperty("user.name"));
topLevelClass.addJavaDocLine(" */");
} /**
* 添加字段注释
*/
@Override
public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append("/** ").append(introspectedColumn.getRemarks().replace("\n", " ")).append(" */");
field.addJavaDocLine(sb.toString());
} // 省略了其他方法
}

不过在使用的时候发现通过

introspectedColumn.getRemarks()

获取到的注释为null,此问题可通过修改xml配置文件来处理

<jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:YEJG" userId="XXX" password="XXX">
<!-- 针对oracle数据库 -->
<property name="remarksReporting" value="true" /> <!-- 针对mysql数据库 -->
<!-- <property name="useInformationSchema" value="true" /> -->
</jdbcConnection>

序列问题

其实这算不算什么问题,xml配置一下就可以了

<table tableName="users">
<property name="useActualColumnNames" value="true" />
<generatedKey type="pre" column="SERIAL_NO" sqlStatement="select users_seq.nextval from dual"></generatedKey>
</table>

这里需要注意下,generatedKey不要写在property前面了,mybatis generator对顺序有要求的。

字段命名方式问题

数据库表字段是USER_ID形式,生成的bean的字段变成userId形式

解决办法

可在generatorConfig.xml中添加如下配置

<property name="useActualColumnNames" value="true" />

不过这么一来,bean中的字段就都变成大写的了,期望生成user_id的形式,可通过修改源码来解决

// DatabaseIntrospector#getColumns,把column_name先toLowerCase处理一下
introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME").toLowerCase());

关于代码

以上代码已上传到Github

Mybatis generator代码生成的更多相关文章

  1. Mybatis Generator 代码生成配置

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...

  2. MyBatis学习---逆向工程 Mybatis Generator代码生成

    [目录]

  3. Introduction to MyBatis Generator Mybatis代码生成介绍

    Mybatis官方提供了代码生成工具,这里是官方网站: http://mybatis.github.io/generator/index.html 可以自动生成 Java POJOs, Mapper. ...

  4. 取代 Mybatis Generator,这款代码生成神器配置更简单,开发效率更高!

    作为一名 Java 后端开发,日常工作中免不了要生成数据库表对应的持久化对象 PO,操作数据库的接口 DAO,以及 CRUD 的 XML,也就是 mapper. Mybatis Generator 是 ...

  5. 数据库逆向框架代码生成工具:MyBatis Generator的使用

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...

  6. MyBatis Generator 详解

    MyBatis Generator中文文档 MyBatis Generator中文文档地址:http://mbg.cndocs.tk/ 该中文文档由于尽可能和原文内容一致,所以有些地方如果不熟悉,看中 ...

  7. MyBatis Generator 详解 【转来纯为备忘】

    版权声明:版权归博主所有,转载请带上本文链接!联系方式:abel533@gmail.com   目录(?)[+] MyBatis Generator中文文档 运行MyBatis Generator X ...

  8. mybatis Generator配置文件详解

    这里按照配置的顺序对配置逐个讲解,更细的内容可以配合中文文档参照. 1. 配置文件头 <?xml version="1.0" encoding="UTF-8&quo ...

  9. MyBatis学习总结_15_定制Mybatis自动代码生成的maven插件

    ==================================================================================================== ...

随机推荐

  1. (转)web前端知识精简

    Web前端技术由 html.css 和 javascript 三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低于任何一门后端语言.而我们在学习它的时候往往是先从某一个点切入,然后不断地接触和学 ...

  2. 131.leetcode-Palindrome Partitioning

    解法一. class Solution { public: vector<vector<string>> partition(string s) { vector<vec ...

  3. 背水一战 Windows 10 (79) - 自定义控件: Layout 系统, 控件模板, 事件处理

    [源码下载] 背水一战 Windows 10 (79) - 自定义控件: Layout 系统, 控件模板, 事件处理 作者:webabcd 介绍背水一战 Windows 10 之 控件(自定义控件) ...

  4. 重新编译安装swoole支持OpenSSL

    1.下载:wget http://pecl.php.net/get/swoole-1.9.22.tgz 2.解压:tar zxvf swoole-1.9.22.tgz 3.扩展模块:cd swoole ...

  5. Trinity的分步运行

    当使用Trinity组装时,如果数据量过大,可以考虑使用--min_kmer_cov 2参数丢弃uniquely occurring kmer, 从而降低内存消耗 设置--no_distributed ...

  6. SpringDataSolr 过滤(或者叫筛选)查询

    // 被本类调用 private Map searchList(Map searchMap) { // 1.1关键字查询 SimpleHighlightQuery highlightQuery = n ...

  7. 从文本中读取字符——feof函数问题

    feof()函数 函数原型:int feof(FILE *fp): 函数功能:检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()函数清除 (函数feof ...

  8. 第二十七节:Java基础面向对象-静态,单例模式,继承详情知识点

    前言 Java基础面向对象-静态,单例模式,继承详情知识点.静态-static关键字,static变量,静态代码块,代码块(不加静态),对象创建过程,单例模式,继承. 静态-static关键字 // ...

  9. 【详细】Android入门到放弃篇-YES OR NO-》各种UI组件,布局管理器,单元Activity

    问:达叔,你放弃了吗? 答:不,放弃是不可能的,丢了Android,你会心疼吗?如果别人把你丢掉,你是痛苦呢?还是痛苦呢?~ 引导语 有人说,爱上一个人是痛苦的,有人说,喜欢一个人是幸福的. 人与人之 ...

  10. 用jQuery修改右键菜单

    概述 以前在网上找过屏蔽右键菜单的代码,也找过屏蔽F12的代码,今天无意之中看到别人的右键菜单很有意思,我也想来搞一个. 思路 建立一个菜单并且隐藏起来. 用window.oncontextmenu屏 ...