一个简单的类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<? 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 第二种更灵活一些。 看看是怎么调用你写的方法的:
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);
}
实现
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();
//获取全部列
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一样。
转载于:https://my.oschina.net/u/3217171/blog/3042406