本文提供了一种自动生成sql语句的方法,它针对的对象是有主键或唯一索引的单表,提供的操作有增、删、改、查4种。理解本文和本文的提供的代码需要有java注解的知识,因为本文是基于注解生成sql的。本文适配的mybatis版本是3.2.2。

准备

为什么在StatementHandler拦截

深入浅出MyBatis-Sqlsession章节介绍了一次sqlsession的完整执行过程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了自动生成sql需要拦截StatementHandler。

MetaObject简介

在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObject是Mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:

1)       MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)

2)       Object getValue(String name)

3)       void setValue(String name, Object value)

方法1)用于包装对象;方法2)用于获取属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);

插件的原理

参见深入浅出Mybatis-插件原理。

有了上面这些基础知识的准备后,就可以我们的主题了。

拦截器签名

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class AutoMapperInterceptor implements Interceptor {
...
}

从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。

intercept的实现

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class AutoMapperInterceptor implements Interceptor {
private static final Log logger = LogFactory.getLog(AutoMapperInterceptor.class);
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); @Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
// 分离代理对象链
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 分离最后一个代理对象的目标类
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
if (null == originalSql || "".equals(originalSql)) {
String newSql = "";
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
.getValue("delegate.mappedStatement");
// 根据ID生成相应类型的sql语句(id需剔除namespace信息)
String id = mappedStatement.getId();
id = id.substring(id.lastIndexOf(".") + 1);
if ("insert".equals(id)) {
newSql = SqlBuilder.buildInsertSql(parameterObject);
} else if ("update".equals(id)) {
newSql = SqlBuilder.buildUpdateSql(parameterObject);
} else if ("delete".equals(id)) {
newSql = SqlBuilder.buildDeleteSql(parameterObject);
} else if ("select".equals(id)) {
newSql = SqlBuilder.buildSelectSql(parameterObject);
}
logger.debug("Auto generated sql:" + newSql);
//
SqlSource sqlSource = buildSqlSource(configuration, newSql, parameterObject.getClass());
List<ParameterMapping> parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();
metaStatementHandler.setValue("delegate.boundSql.sql", sqlSource.getBoundSql(parameterObject).getSql());
metaStatementHandler.setValue("delegate.boundSql.parameterMappings", parameterMappings);
}
// 调用原始statementHandler的prepare方法
statementHandler = (StatementHandler) metaStatementHandler.getOriginalObject();
statementHandler.prepare((Connection) invocation.getArgs()[0]);
// 传递给下一个拦截器处理
return invocation.proceed();
} @Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
} @Override
public void setProperties(Properties properties) { } private SqlSource buildSqlSource(Configuration configuration, String originalSql,
Class<?> parameterType) {
SqlSourceBuilder builder = new SqlSourceBuilder(configuration);
return builder.parse(originalSql, parameterType, null);
}
}

StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。

在上面的方法里有个两个循环,通过他们可以分离出原始的RoutingStatementHandler(而不是代理对象)。

有了插件帮你生成sql语句后,mapper配置文件里单表的增删改查部分就不需要再配置sql代码了,但由于插件需要通过id来生成不同类型的sql语句,因此必要的配置还是需要的,而且相应的id必须是下面的这几个(区分大小写):

	<update id="update" parameterType="UserDto"></update>
<insert id="insert" parameterType="UserDto"></insert>
<delete id="delete" parameterType="UserDto"></delete>
<select id="select" parameterType="UserDto" resultType="UserDto""></select>

SqlBuilder

SqlBuilder的相应方法接受一个dto对象作为参数,它们根据这个对象的属性值和配置的注解生成相应的sql。

@TableMapperAnnotation(tableName = "t_user", uniqueKeyType = UniqueKeyType.Single, uniqueKey = " userid ")
public class UserDto {
@FieldMapperAnnotation(dbFieldName = "userid", jdbcType = JdbcType.INTEGER)
private Integer userid;
@FieldMapperAnnotation(dbFieldName = "username", jdbcType = JdbcType.VARCHAR)
private String username;
...
}

这个对象包含了两种注解,一个是TableMapperAnnotation注解,它保存了表名、唯一键类型和构成唯一键的字段;另一个是FieldMapperAnnotation注解,它保存了数据库字段名和字段类型信息。这两个注解都是必须的。SqlBuilder生成sql时会用到他们,下面以生成insert语句的方法为例,其他方法类似:

    public static String buildInsertSql(Object object) throws Exception {
if (null == object) {
throw new RuntimeException("Sorry,I refuse to build sql for a null object!");
}
Map dtoFieldMap = PropertyUtils.describe(object);
// 从参数对象里提取注解信息
TableMapper tableMapper = buildTableMapper(object.getClass());
// 从表注解里获取表名等信息
TableMapperAnnotation tma = (TableMapperAnnotation) tableMapper.getTableMapperAnnotation();
String tableName = tma.tableName();
StringBuffer tableSql = new StringBuffer();
StringBuffer valueSql = new StringBuffer(); tableSql.append("insert into ").append(tableName).append("(");
valueSql.append("values("); boolean allFieldNull = true;
// 根据字段注解和属性值联合生成sql语句
for (String dbFieldName : tableMapper.getFieldMapperCache().keySet()) {
FieldMapper fieldMapper = tableMapper.getFieldMapperCache().get(dbFieldName);
String fieldName = fieldMapper.getFieldName();
Object value = dtoFieldMap.get(fieldName);
// 由于要根据字段对象值是否为空来判断是否将字段加入到sql语句中,因此DTO对象的属性不能是简单类型,反而必须是封装类型
if (value == null) {
continue;
}
allFieldNull = false;
tableSql.append(dbFieldName).append(",");
valueSql.append("#{").append(fieldName).append(",").append("jdbcType=")
.append(fieldMapper.getJdbcType().toString()).append("},");
}
if (allFieldNull) {
throw new RuntimeException("Are you joking? Object " + object.getClass().getName()
+ "'s all fields are null, how can i build sql for it?!");
}
tableSql.delete(tableSql.lastIndexOf(","), tableSql.lastIndexOf(",") + 1);
valueSql.delete(valueSql.lastIndexOf(","), valueSql.lastIndexOf(",") + 1);
return tableSql.append(") ").append(valueSql).append(")").toString();
}

plugin的实现

    public Object plugin(Object target) {
// 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的
// 次数
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}

源码

点击下载

深入浅出Mybatis-sql自动生成的更多相关文章

  1. 使用Mybatis Generator自动生成Mybatis相关代码

    本文将简要介绍怎样利用Mybatis Generator自动生成Mybatis的相关代码: 一.构建一个环境: 1. 首先创建一个表: CREATE TABLE pet (name VARCHAR(2 ...

  2. Springboot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库

    1. Springboot mybatis 介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数获取 ...

  3. SpringBoot 添加mybatis generator 自动生成代码插件

    自动生成数据层代码,提高开发效率 1.pom添加插件,并指定配置文件路径 <!-- mybatis generator 自动生成代码插件 --> <plugin> <gr ...

  4. idea中mybatis generator自动生成代码配置 数据库是sqlserver

    好长时间没有写博客了,最近公司要用java语言,开始学习java,属于初学者,今天主要记录一下mybatis generator自动生成代码,首先在如下图的目录中新建两个文件,如下图 generato ...

  5. SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件

    原文链接 我们这一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池 ...

  6. 使用mybatis plus自动生成controller、service、dao、mapper、entity代码

    官网:http://mp.baomidou.com(这个项目不仅仅可以用于代码生成,还有分页等其他功能,是对mybatis的一层封装) 要求:基于sql自动生成domain.controller.se ...

  7. IDEA Maven Mybatis generator 自动生成代码(实例讲解)(转)

    IDEA Maven Mybatis generator 自动生成代码(实例讲解) MyBatis Generator • 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的 ...

  8. (转)MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码

    http://blog.csdn.net/yerenyuan_pku/article/details/71909325 什么是逆向工程 MyBatis的一个主要的特点就是需要程序员自己编写sql,那么 ...

  9. 使用mybatis插件自动生成代码以及问题处理

    1.pom.xml中加入依赖插件 <!-- mybatis generator 自动生成代码插件 --> <plugin> <groupId>org.mybatis ...

  10. MyBatis框架之mybatis逆向工程自动生成代码

    http://www.jb51.net/article/82062.htm Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们 ...

随机推荐

  1. Android-Launcher开发之ShortCut(1)

    下面源代码来自Launcher2.3的样例 1.默认每一个应用的主Activity都会自带 <category android:name="android.intent.categor ...

  2. 一个只能用在Win下的密码验证函数(显示星号,可删除)

    以前做小程序时图好玩在网上找的代码.输入的密码会以星号显示出来,并且输入错了可以删除.因为用了专有库函数,所以只能在Windows平台使用,少用为好,不过可能还有点用.嗯…就这样了 #include ...

  3. 使用Vim或Codeblocks格式化代码

    在网上的代码,有很多的代码都是丢失缩进的,几行还好,手动改改,多了呢,不敢想象,没有缩进的代码.别说排错,就是阅读都是困难的,还好,有两个常用工具可以轻松的解决问题. (一)Vim(简单方便,可将代码 ...

  4. Shell的学习就从重装系统开始吧

    小标题:与Fedora的爱恨情仇 干巴巴的shell学习实在枯燥,看来学习姿势还是要从实践入手 起因:传说中的不作死就不会死,昨晚偶遇一本PDF,讲glade编辑界面的,以下就被吸引了,跟着讲解搞出个 ...

  5. git 简单教程更新

    0.初始化 $ git config --global user.name "xxx" $ git config --global user.email "xxx@gma ...

  6. go orcale

    golang连接orcale   使用glang有一段时间了,最开始其实并不太喜欢他的语法,但是后来熟悉之后发现用起来还挺爽的.之前数据库一直使用mysql,连接起来没有什么问题,github上有很多 ...

  7. Web Api的安全性

    Web Api的安全性 系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 这一篇文章我们主要来探讨一下Web Api的安全性,到目前为止所有的 ...

  8. Step one : 熟悉HTML

    //H1 1 <html> <head> <title>BeiJing</title> </head> <body> <h ...

  9. 闲话Android 之 屏幕大小、pixel、分辨率、dpi、dip

    之前都是在自研的产品上做开发,而且我们的屏幕是1dip=1px的,所以在写App布局的时候,随便写单位,也没觉得什么不妥.可是近期我把以前的App里面的一些自定义控件在emulator上跑的时候,才发 ...

  10. golang 之 bson 与 struct 转换

    bson的介绍不说了golang下的解析包找到2个 一个是mongo的http://labix.org/gobson,另外一个比较小众https://github.com/sbunce/bson这里用 ...