[瞎折腾]使用freemarker编写MyBatis的Mapper.xml自动生成工具

MyBatis是 iBatis的新项目名,是一个java的持久化框架,和目前一家独大的Hibernate相比MyBatis显得比较的被冷漠。两个都是好框架,大家习惯用哪个而已。因为之前一直是用iBatis,现在看到新版的MyBatis想来学习一下。
MyBatis的官网有很详细的user guides,而且还有中文,看一两天就可以上手:
http://mybatis.github.io/mybatis-3/zh/index.html
新版的一个重点特性是新增动态SQL(dynamic SQL),简单来说就是能够使用while、if、elseif、loop,就是在Mapper.xml中加入条件控制,基于这个不算新的功能,我妄想写一个生成工具对每个表生成一份可以完全实现80%数据库操作的mapper.xml,最终工具是产生了,但是达不到理想的效果,原因有几方面:
1、不能满足所有的需求
2、不能生成级联的mapper
3、生成的mapper很大,解析起来非常耗时,即效率不好
4、实际应用意义不大
基于上面的几点,我放弃了对其的改进,不过在这个过程中有不少有用的知识点需要记录下来。另外补充的是,官方有一个自动生成的插件: http://code.google.com/p/mybatis/downloads/list?can=3&q=Product%3DGenerator 一样的不好用,还是自己写sql比较好。
一、MyBatis和Spring结合
MyBatis和Spring结合非常简单,配置也不复杂,除了Spring和Mybatis的jar包外还需要插件包mybatis-spring-1.0.1.jar ,上面的jar都可以在 http://code.google.com/p/mybatis/下载到。
现在有一个项目如图:
[瞎折腾]使用freemarker编写MyBatis的Mapper.xml自动生成工具_第1张图片
关注java/org.mybatis.jpetstore.persistence和resource/org.mybatis.jpetstore.persistence两个包,java下的是dao的接口,resource下的是数据表的Mapper。
里面有一个表如下:
create table category (
    catid varchar(10) not null,
    name varchar(80) null,
    descn varchar(255) null,
    constraint pk_category primary key (catid)
); 
现在你需要对这个表根据id进行select操作,首先需要定义一个接口CategoryMapper.java:
public interface CategoryMapper {
  Category getCategory(String categoryId);
} 
然后需要新建一个Mapper,CategoryMapper.xml:
<mapper namespace="org.mybatis.jpetstore.persistence.CategoryMapper">
  <cache />
   <select id="getCategoryList" resultType="Category">
    SELECT
      CATID AS categoryId,
      NAME,
      DESCN AS description
    FROM CATEGORY
  </select>
</mapper> 
最后就是在spring的配置文件中配置就可以了:
    <!-- scan for mappers and let them be autowired -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.mybatis.jpetstore.persistence" />
    </bean> 
直接用Spring的scans扫描整个包,我感觉这种方式即简单又方便,感觉很清爽,而且扩展性也很好。

二、利用freemarker生成动态SQL
下面的内容需要对freemarker有一定的认识,至于什么是freemarker,看官方的文档:
http://nchc.dl.sourceforge.net/project/freemarker/chinese-manual/FreeMarker_Manual_zh_CN.pdf 这份文档非常详细,只有你想不到,没有没提到的。
velocity跟freemarker两个是类似的java模板工具,前者没有宏定义,后者有,在没有什么特别复杂需求的时候建议用前者(纯属我个人观点)
Mapper生成工具具体流程:
通过jdbc读取数据库——》返回表名(table.sqlName)、所有字段的list(table.columns)、每个字段的名字(column.sqlName)、每个字段的类型(column.javaType) ——》对信息进行处理生成一些变量类名(className)、首字母小写的变量名(column.columnNameFirstLower)、大写的变量名(column.constantName)——》编写flt模板文件——》生成Mapper.xml。
需要说明的几点:
(1)、在Mybatis的Mapper.xml中是用#{value}和${value}显示变量的值,对于#{value},MyBatis会把value里面的双引号、单引号去掉,${value}则会把变量中的值原封不动的替换。
(2)、在freemarker中也是用${value}这种形式来显示变量的,因此用freemarker生成Mapper.xml需要达到的效果是${value}解析后得到${XXX}或者#{XXX}
为了解决上面的第二点,有必要定义两个宏:
<#macro mapperEl value>${r"#{"}${value}}</#macro>用来生成#{value}
<#macro jspEl value>${r"${"}${value}}</#macro> 用来生成${value}
SQL语句分析:
sql太复杂了,下面只讲where的部分,在sql语句中有很大一部分是condition部分,where部分组成大概有下面几点:
(1)、当column是string时,column = #{value}
(2)、当column是date时, trunc(column, 'MI') = trunc(#{value}, 'MI')
(3)、条件之间有 AND OR 两种关系
(4)、like 的条件连接,需要再根据(1)、(2)两点来分类
(5)、除去上面的外加一个extraConditions,允许用户写一些好复杂的条件
大概就5点吧,直接上代码:
<sql id="${table.sqlName}_Where_Clause" >
  <where> 
    <choose>
      <when test="useAnd">
            <#list table.columns as column>
              <if test="${column.columnNameFirstLower} != null" >
                    <#if column.javaType == "java.util.Date" >
                         <![CDATA[ 
                            AND    trunc(tb.${column.sqlName},'MI') = trunc(<@mapperEl column.columnNameFirstLower/> ,'MI')
                          ]]> 
                    <#else>
                        AND tb.${column.sqlName} = <@mapperEl column.columnNameFirstLower/> 
                    </#if>
               </if>
            </#list>
            <#list table.columns as column>
              <#if column.javaType == "java.lang.String">
                <if test="${column.columnNameFirstLower}LinkLike != null" >
                  AND tb.${column.sqlName} LIKE <@mapperLike column.columnNameFirstLower/>
                </if>
              <#elseif column.javaType == "java.util.Date" >
                    <if test="${column.columnNameFirstLower}BeginTime != null" >
                        AND trunc(tb.${column.sqlName},'MI') >= trunc(<@mapperBegin column.columnNameFirstLower/>,'MI')
                     </if>
                     <if test="${column.columnNameFirstLower}EndTime != null" >
                        AND trunc(tb.${column.sqlName},'MI') <= trunc(<@mapperEnd column.columnNameFirstLower/>,'MI')
                     </if>
              <#else>
            </#list>
      </when>
      <otherwise>
            <#list table.columns as column>
              <if test="${column.columnNameFirstLower} != null" >
                OR tb.${column.sqlName} = <@mapperEl column.columnNameFirstLower/>
               </if>
            </#list>
            <#list table.columns as column>
              <#if column.javaType == "java.lang.String">
                <if test="${column.columnNameFirstLower}LinkLike != null" >
                  OR tb.${column.sqlName} LIKE <@mapperLike column.columnNameFirstLower/>
                </if>
              <#elseif column.javaType == "java.util.Date" >
                    <if test="${column.columnNameFirstLower}BeginTime != null" >
                        OR trunc(tb.${column.sqlName},'MI') >= trunc(<@mapperBegin column.columnNameFirstLower/>,'MI')
                     </if>
                     <if test="${column.columnNameFirstLower}EndTime != null" >
                        OR trunc(tb.${column.sqlName},'MI') <= trunc(<@mapperEnd column.columnNameFirstLower/>,'MI')
                     </if>
              <#else>
            </#list>
      </otherwise>
    </choose>
    <if test="extraConditions != null">
        <@jspEl 'extraConditions'/>

    </if>
  </where>
  <if test="orderBy!= null">
      <@jspEl 'sortColumns'/>

  </if>
  <if test="groupBy!= null">
      <@jspEl 'groupColumns'/>

  </if>
</sql>
生成的结果:
<sql id="category_Where_Clause" >
  <where> 
    <choose>
      <when test="useAnd">
              <if test="catid != null" >
                AND category.catid = #{catid} 
               </if>
              <if test="name != null" >
                AND category.name = #{name} 
               </if>
              <if test="descn != null" >
                AND category.descn = #{descn} 
               </if>
                <if test="catidLinkLike != null" >
                  AND category.catid LIKE #{catid}
                </if>
                <if test="nameLinkLike != null" >
                  AND category.name LIKE #{name}
                </if>
                <if test="descnLinkLike != null" >
                  AND category.descn LIKE #{descn}
                </if>
      </when>
      <otherwise>
              <if test="catid != null" >
                OR category.catid = #{catid}
               </if>
              <if test="name != null" >
                OR category.name = #{name}
               </if>
              <if test="descn != null" >
                OR category.descn = #{descn}
               </if>
                <if test="catidLinkLike != null" >
                  OR category.catid LIKE #{catid}
                </if>
                <if test="nameLinkLike != null" >
                  OR category.name LIKE #{name}
                </if>
                <if test="descnLinkLike != null" >
                  OR category.descn LIKE #{descn}
                </if>
      </otherwise>
    </choose>
    <if test="extraConditions != null">
${extraConditions}    
    </if>
  </where>
  <if test="orderBy!= null">
${orderBy}  
  </if>
  <if test="groupBy!= null">
${groupBy}  
  </if>
</sql>

然后是对于javaBean的生成模板:${className}Bean.java
<#assign className = table.className>   
<#assign classNameLower = className?uncap_first> 
package ${basepackage}.model;

import java.io.Serializable;
import ${basepackage}.model.BaseEntitySupport


public class ${className}Bean extends BaseEntitySupport implements java.io.Serializable{
    private static final long serialVersionUID = 8751282105532159742L;

    <#list table.columns as column>
    private ${column.javaType} ${column.columnNameLower};
    </#list>


    <#list table.columns as column>
    public ${className} set${column.columnName}(${column.javaType} ${column.columnName}) {
        this.${column.columnNameLower} = ${column.columnName};
        return this;
    }

    public ${column.javaType} get${column.columnName}() {
        return this.${column.columnNameLower};
    }
    </#list>

}
生成结果CategoryBean .java:
import java.io.Serializable;
import org.mybatis.jpetstore.persistence.model.BaseEntitySupport


public class CategoryBean extends BaseEntitySupport implements java.io.Serializable{
    private static final long serialVersionUID = 8751282105532159742L;

    private java.lang.String catid;
    private java.lang.String name;
    private java.lang.String descn;


    public Category setCatid(java.lang.String Catid) {
        this.catid = Catid;
        return this;
    }

    public java.lang.String getCatid() {
        return this.catid;
    }
    public Category setName(java.lang.String Name) {
        this.name = Name;
        return this;
    }

    public java.lang.String getName() {
        return this.name;
    }
    public Category setDescn(java.lang.String Descn) {
        this.descn = Descn;
        return this;
    }

    public java.lang.String getDescn() {
        return this.descn;
    }

}
工具生成的代码大概就是这个样子,只要加上insert、update、select、delete基本就成型。
可是,就如上面说的,这个生成工具没有什么实际的意义,所以还是作为瞎折腾的产物吧。

你可能感兴趣的:(java,freemarker,mybatis,generator)