本文提供了一种自动生成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. struts2文件下载 <result type="stream">

    <!--struts.xml配置--> <action name="download" class="com.unmi.action.DownloadA ...

  2. Android_WebServices_介绍

    本博文为子墨原创,转载请注明出处! http://blog.csdn.net/zimo2013/article/details/38036289 1.WebService的介绍 WebService为 ...

  3. D3D 光照和材料 小样例

    1.实现一个旋转的圆柱体,体现d3d光照效果 2.程序实现 #pragma once #pragma comment(lib,"winmm.lib") #pragma commen ...

  4. avalonJS入门(一)

    前端神器avalonJS入门(一) posted @ 2014-10-31 17:44 vajoy 阅读(1665) 评论(32) 编辑 收藏   avalonJS是司徒正美开发和维护的前端mvvm框 ...

  5. 用javascript实现2048的小游戏

    前段时间,看了一个视频,用javascript实现的2048小游戏,发现不难,都是一些基出的语法和简单逻辑. 整个2048游戏没有很多的数据,所有,实现起来还是很有成就感的. 先上图,简直就和原版游戏 ...

  6. 转载:21个免费的UI界面设计工具、资源及网站

    我们刚刚介绍了移动设计初探:触屏网页设计.本文将介绍一些UI界面与设计使用的元素.软件和网站.内容很丰富,适合用户体验设计师.界面设计师.产品设计师.JS前段开发.手机产品设计以及iPad和平板电脑产 ...

  7. NET Framework 4.5新特性 数据库的连接加密保护。

    NET Framework 4.5新特性 (一) 数据库的连接加密保护. NET Framework 4.5 ado.net数据库连接支持使用SecureString内存流方式保密文本.  一旦使用这 ...

  8. Build MySQL 5.7.4 in RedHat

    Install Cmake 1. download cmake source code at  http://www.cmake.org/files/v3.1/cmake-3.1.0.tar.gz 2 ...

  9. Docker 01 Introduction

    Docker的组成: Docker Engine,一个轻量级.强大的开源容器虚拟化平台,使用包含了工作流的虚拟化技术,帮助用户建立.并容器化一个应用. Docker Hub,提供的一个SaaS服务,用 ...

  10. C# 订单流水号生成

    例如流水号格式如下:XX201604120001,2位前缀加8位日期加4位流水号 首先各种搜索出现如下解决方案 public class SerialNoHelper { /// <summar ...