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