mybatis支持@Insert与@InsertProvider注解。这两个注解的实现以下:
入口java
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<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); Class<? extends Annotation> 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语句。相对于@Insert 第二种更灵活一些。 看看是怎么调用你写的方法的: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") Map<String, Object> params = (Map<String, Object>) 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<String, Object>()); } 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); }
实现mybatis
public class MyMapperImpl { public String insertSelect(ProviderContext pc,Object agrs){ Class clazz = pc.getMapperType(); System.out.println("xxxx"); return "select 1"; } }
这是一个简单的例子,但也是tk.mapper的原理。app
TK是在初始化完毕的时候,替换掉原有的SqlSource.ide
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的生成原理fetch
public String insertSelective(MappedStatement ms) { Class<?> entityClass = getEntityClass(ms); StringBuilder sql = new StringBuilder(); //获取所有列 Set<EntityColumn> columnList = 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("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">"); 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("</trim>"); sql.append("<trim prefix=\"VALUES(\" suffix=\")\" suffixOverrides=\",\">"); 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("</trim>"); return sql.toString(); }
能够看到tk.mapper生成的SQL语句和XML同样。ui