MyBatis是
iBatis的新项目名,是一个java的持久化框架,和目前一家独大的Hibernate相比MyBatis显得比较的被冷漠。两个都是好框架,大家习惯用哪个而已。因为之前一直是用iBatis,现在看到新版的MyBatis想来学习一下。
MyBatis的官网有很详细的user guides,而且还有中文,看一两天就可以上手:
新版的一个重点特性是新增动态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/下载到。
现在有一个项目如图:
关注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的配置文件中配置就可以了:
直接用Spring的scans扫描整个包,我感觉这种方式即简单又方便,感觉很清爽,而且扩展性也很好。
<!-- scan for mappers and let them be autowired --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="org.mybatis.jpetstore.persistence" /> </bean>
二、利用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基本就成型。
可是,就如上面说的,这个生成工具没有什么实际的意义,所以还是作为瞎折腾的产物吧。