一个简单的类TK.MAPPER实现
mybatis支持@Insert与@InsertProvider注解。这两个注解的实现如下:
入口void parseStatement(Method method) { Class parameterTypeClass = getParameterType(method); LanguageDriver languageDriver = getLanguageDriver(method); //解析出SQL SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); if (sqlSource != null) { Options options = method.getAnnotation(Options.class); final String mappedStatementId = type.getName() + "." + method.getName(); Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; SqlCommandType sqlCommandType = getSqlCommandType(method); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = !isSelect; boolean useCache = isSelect;
private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterType, LanguageDriver languageDriver) { try { Class sqlAnnotationType = getSqlAnnotationType(method); Class sqlProviderAnnotationType = getSqlProviderAnnotationType(method); if (sqlAnnotationType != null) { if (sqlProviderAnnotationType != null) { throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); } //解析@Insert Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { //解析@InsertProvider Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }
通过代码发现@Insert解析,类似于XML文件形式,相当于将XML文件的类容写到JAVA里面。@InsertProvider是在执行SQL语句的时候,把参数给你,由你自己来解析出SQL语句。相对于 第二种更灵活一些。 看看是怎么调用你写的方法的:
private SqlSource createSqlSource(Object parameterObject) { try { int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1); String sql; if (providerMethodParameterTypes.length == 0) { sql = (String) providerMethod.invoke(providerType.newInstance()); } else if (bindParameterCount == 0) { sql = (String) providerMethod.invoke(providerType.newInstance(), providerContext); } else if (bindParameterCount == 1 && (parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) { sql = (String) providerMethod.invoke(providerType.newInstance(), extractProviderMethodArguments(parameterObject)); } else if (parameterObject instanceof Map) { @SuppressWarnings("unchecked") Mapparams = (Map ) parameterObject; sql = (String) providerMethod.invoke(providerType.newInstance(), extractProviderMethodArguments(params, providerMethodArgumentNames)); } else { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cannot invoke a method that holds " + (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments") + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object."); } Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap ()); } catch (BuilderException e) { throw e; } catch (Exception e) { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cause: " + e, e); } }
一个例子:
public interface MyMapper { @InsertProvider(type=MyMapperImpl.class,method = "insertSelect") public void insertSelect(String s1, ChannelOrders channelOrders);}
实现
public class MyMapperImpl { public String insertSelect(ProviderContext pc,Object agrs){ Class clazz = pc.getMapperType(); System.out.println("xxxx"); return "select 1"; }}
这是一个简单的例子,但也是tk.mapper的原理。
tk.mapper
TK是在初始化完毕的时候,替换掉原有的SqlSource.
public void setSqlSource(MappedStatement ms) throws Exception { if (this.mapperClass == getMapperClass(ms.getId())) { throw new MapperException("请不要配置或扫描通用Mapper接口类:" + this.mapperClass); } Method method = methodMap.get(getMethodName(ms)); try { //第一种,直接操作ms,不需要返回值 if (method.getReturnType() == Void.TYPE) { method.invoke(this, ms); } //第二种,返回SqlNode else if (SqlNode.class.isAssignableFrom(method.getReturnType())) { SqlNode sqlNode = (SqlNode) method.invoke(this, ms); DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode); setSqlSource(ms, dynamicSqlSource); } //第三种,返回xml形式的sql字符串 else if (String.class.equals(method.getReturnType())) { String xmlSql = (String) method.invoke(this, ms); SqlSource sqlSource = createSqlSource(ms, xmlSql); //替换原有的SqlSource setSqlSource(ms, sqlSource); } else { throw new MapperException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!"); } } catch (IllegalAccessException e) { throw new MapperException(e); } catch (InvocationTargetException e) { throw new MapperException(e.getTargetException() != null ? e.getTargetException() : e); } }
下面我们看一下InsertSelect的生成原理
public String insertSelective(MappedStatement ms) { Class entityClass = getEntityClass(ms); StringBuilder sql = new StringBuilder(); //获取全部列 SetcolumnList = EntityHelper.getColumns(entityClass); //Identity列只能有一个 Boolean hasIdentityKey = false; //先处理cache或bind节点 for (EntityColumn column : columnList) { if (!column.isInsertable()) { continue; } if (StringUtil.isNotEmpty(column.getSequenceName())) { //sql.append(column.getColumn() + ","); } else if (column.isIdentity()) { //这种情况下,如果原先的字段有值,需要先缓存起来,否则就一定会使用自动增长 //这是一个bind节点 sql.append(SqlHelper.getBindCache(column)); //如果是Identity列,就需要插入selectKey //如果已经存在Identity列,抛出异常 if (hasIdentityKey) { //jdbc类型只需要添加一次 if (column.getGenerator() != null && column.getGenerator().equals("JDBC")) { continue; } throw new MapperException(ms.getId() + "对应的实体类" + entityClass.getCanonicalName() + "中包含多个MySql的自动增长列,最多只能有一个!"); } //插入selectKey SelectKeyHelper.newSelectKeyMappedStatement(ms, column, entityClass, isBEFORE(), getIDENTITY(column)); hasIdentityKey = true; } else if (column.isUuid()) { //uuid的情况,直接插入bind节点 sql.append(SqlHelper.getBindValue(column, getUUID())); } } sql.append(SqlHelper.insertIntoTable(entityClass, tableName(entityClass))); sql.append(" "); for (EntityColumn column : columnList) { if (!column.isInsertable()) { continue; } if (StringUtil.isNotEmpty(column.getSequenceName()) || column.isIdentity() || column.isUuid()) { sql.append(column.getColumn() + ","); } else { sql.append(SqlHelper.getIfNotNull(column, column.getColumn() + ",", isNotEmpty())); } } sql.append(" "); sql.append(""); for (EntityColumn column : columnList) { if (!column.isInsertable()) { continue; } //优先使用传入的属性值,当原属性property!=null时,用原属性 //自增的情况下,如果默认有值,就会备份到property_cache中,所以这里需要先判断备份的值是否存在 if (column.isIdentity()) { sql.append(SqlHelper.getIfCacheNotNull(column, column.getColumnHolder(null, "_cache", ","))); } else { //其他情况值仍然存在原property中 sql.append(SqlHelper.getIfNotNull(column, column.getColumnHolder(null, null, ","), isNotEmpty())); } //当属性为null时,如果存在主键策略,会自动获取值,如果不存在,则使用null //序列的情况 if (StringUtil.isNotEmpty(column.getSequenceName())) { sql.append(SqlHelper.getIfIsNull(column, getSeqNextVal(column) + " ,", isNotEmpty())); } else if (column.isIdentity()) { sql.append(SqlHelper.getIfCacheIsNull(column, column.getColumnHolder() + ",")); } else if (column.isUuid()) { sql.append(SqlHelper.getIfIsNull(column, column.getColumnHolder(null, "_bind", ","), isNotEmpty())); } } sql.append(" "); return sql.toString(); }
可以看到tk.mapper生成的SQL语句和XML一样。