深入浅出Mybatis-sql自动生成
本文提供了一种自动生成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自动生成的更多相关文章
- 使用Mybatis Generator自动生成Mybatis相关代码
本文将简要介绍怎样利用Mybatis Generator自动生成Mybatis的相关代码: 一.构建一个环境: 1. 首先创建一个表: CREATE TABLE pet (name VARCHAR(2 ...
- Springboot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库
1. Springboot mybatis 介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数获取 ...
- SpringBoot 添加mybatis generator 自动生成代码插件
自动生成数据层代码,提高开发效率 1.pom添加插件,并指定配置文件路径 <!-- mybatis generator 自动生成代码插件 --> <plugin> <gr ...
- idea中mybatis generator自动生成代码配置 数据库是sqlserver
好长时间没有写博客了,最近公司要用java语言,开始学习java,属于初学者,今天主要记录一下mybatis generator自动生成代码,首先在如下图的目录中新建两个文件,如下图 generato ...
- SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件
原文链接 我们这一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池 ...
- 使用mybatis plus自动生成controller、service、dao、mapper、entity代码
官网:http://mp.baomidou.com(这个项目不仅仅可以用于代码生成,还有分页等其他功能,是对mybatis的一层封装) 要求:基于sql自动生成domain.controller.se ...
- IDEA Maven Mybatis generator 自动生成代码(实例讲解)(转)
IDEA Maven Mybatis generator 自动生成代码(实例讲解) MyBatis Generator • 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的 ...
- (转)MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码
http://blog.csdn.net/yerenyuan_pku/article/details/71909325 什么是逆向工程 MyBatis的一个主要的特点就是需要程序员自己编写sql,那么 ...
- 使用mybatis插件自动生成代码以及问题处理
1.pom.xml中加入依赖插件 <!-- mybatis generator 自动生成代码插件 --> <plugin> <groupId>org.mybatis ...
- MyBatis框架之mybatis逆向工程自动生成代码
http://www.jb51.net/article/82062.htm Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们 ...
随机推荐
- HDU 5045 Contest
pid=5045">主题链接~~> 做题感悟:比赛时这题后来才写的,有点小尴尬.两个人商议着写写了非常久才写出来,I want to Powerful ,I believe me ...
- 12个有趣的c面试题目
1.gets()函数 问:请找出以下代码里的问题: #include<stdio.h> int main(void) { char buff[10]; memset ...
- MySQL replace into 说明(insert into 增强版)
MySQL replace into 说明(insert into 增强版) 在插入数据到一个表时,通常是这种情况:1. 先推断数据是否存在: 2. 假设不存在,则插入:3.假设存在,则更新. 在 S ...
- Invent 2014回顾
AWS re:Invent 2014回顾 亚马逊在2014年11月11-14日的拉斯维加斯举行了一年一度的re:Invent大会.在今年的大会上,亚马逊一股脑发布和更新了很多服务.现在就由我来带领 ...
- 打印man手册为pdf文件
只需要一个命令就可以了! merlin@tfAnalysis:~/projects/tfadc$ man -t errno | ps2pdf - ~/errno.pdf 输出的文件很漂亮.
- Grub启动配置文件
和许多其他linux发行版一样,Fedora使用Grub作为32位和64位X86系统的启动加载器(bootloader).grub的配置文件主要是/boot/grub/grub.conf,而/boot ...
- 成C++应用程序世界------异常处理
一. 概述 C++自身有着很强的纠错能力,发展到现在,已经建立了比較完好的异常处理机制.C++的异常情况无非两种,一种是语法错误,即程序中出现了错误的语句,函数,结构和类,致使编译程序无法进行.还有一 ...
- 【转】怎么导出jar包
如何导出jar包 右键工程->Export->Java->JAR file->Next-> Next 选中工程和工程中你要打包的内容,如果是Android的项目,需要把M ...
- Toast,AlertDialog的误解
在一般的软件开发中,子线程中是不能更改UI主线程中创建的UI控件的.之前的理解是Toast也不能在子线程中创建.事实上并不是这样子的. @Override protected void onCreat ...
- Oracle琐碎笔记2
备注:以下所有操作均在sqlplus中执行. 开始前输入:spool c:\jiyi.txt;结束后输入:spool off;就会记忆操作的所有记录save c:\sql.sql;保存sql脚本可以使 ...