項目中使用Mybatis作持久層框架,但因爲開發成員水平不一,寫dao的時候,各有各的偏好,有時候還會寫出帶sql注入漏洞的代碼。java
出現sql注入漏洞,通常是#和$的區別沒弄明白: $ 直接把字符串原封不動的搬進sql,有sql注入的風險 # 是預留一個問號,做爲參數插入的,便可經過預編譯sql的方式避免sql注入
因而想使用Mybatis generator這個工具來統一輩子成代碼(java bean,mapper,xml)mysql
Mybatis generator能夠經過以下方式運行git
下載mybatis-generator-core.jar,而後配置generatorConfig.xml文件,執行以下命令 java -jar mybatis-generator-core-1.3.7.jar -configfile generatorConfig.xml -overwrite
安裝eclipse/idea插件
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是沒有分頁查詢的,可經過github
<plugin type="org.mybatis.generator.plugins.RowBoundsPlugin">
來實現分頁,不過...sql
可以使用pagehelper來解決,See Github
shell
除此以外,咱們也能夠經過自定義分頁插件來解決數據庫
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; } }
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; } }
問題緣由在於:
在IntrospectedTableMyBatis3Impl.getGeneratedXmlFiles方法中,isMergeable值被寫死爲true了。mybatis
GeneratedXmlFile gxf = new GeneratedXmlFile(document, getMyBatis3XmlMapperFileName(), getMyBatis3XmlMapperPackage(), context.getSqlMapGeneratorConfiguration().getTargetProject(), true, context.getXmlFormatter());
而MyBatisGenerator.writeGeneratedXmlFile方法中使用到該屬性了。代碼以下:oracle
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改爲falseapp
// 能夠在前面自定義的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