#0 系列目录#java
本文提供了一种自动生成sql语句的方法,它针对的对象是有主键或惟一索引的单表,提供的操做有增、删、改、查4种
。理解本文和本文的提供的代码须要有java注解的知识,由于本文是基于注解生成sql的。sql
#1 准备# ##1.1 为何在StatementHandler拦截## 在SQL执行流程分析(源码篇)章节介绍了一次sqlsession的完整执行过程,从中能够知道sql的解析是在StatementHandler里完成的,因此为了重写sql须要拦截StatementHandler。数据库
##1.2 MetaObject简介## 在实现里大量使用了MetaObject这个对象,所以有必要先介绍下它。MetaObject是Mybatis提供的一个的工具类,经过它包装一个对象后能够获取或设置该对象的本来不可访问的属性(好比那些私有属性)
。它有个三个重要方法常常用到:缓存
MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) 用于包装对象;session
Object getValue(String name) 用于获取属性的值(支持OGNL的方法);app
void setValue(String name, Object value) 用于设置属性的值(支持OGNL的方法);ide
#2 拦截器签名#工具
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})}) public class AutoMapperInterceptor implements Interceptor { ... }
从签名里能够看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型
),拦截的方法是名称为prepare参数为Connection类型的方法。源码分析
#3 intercept实现#ui
@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>
#3 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(); }
#4 plugin实现#
public Object plugin(Object target) { // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标自己,减小目标被代理的 // 次数 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }