(一)MyBatis简介
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
(二)源码分析
项目是用映射器(dao)和映射文件(xxx.xml)的方式配置mybatis
先以下面的更新方法为例
dao接口的方法如下:
int updateSubjectById(Subject subject)throws Exception;
代码执行dao方法时,会调用MapperProxy类中的invoke()方法,往下执行会依次会调用
MapperMethod中的public Object execute(SqlSession sqlSession, Object[] args),
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
MethodSignature中的public Object convertArgsToSqlCommandParam(Object[] args),
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
因为dao中方法的参数为Subject subject,所以这里args参数为
hasNamedParameters意思是是否使用了注解参数,这里没有用到注解,值为false。
这里params.size()的值为1,如图
所以上面的方法返回的是subject,excute()中的param就是subject。
convertArgsToSqlCommandParam()方法执行完后,依次进入rowCountResult(sqlSession.update(command.getName(), param))
,SqlSessionTemplate中update()方法,
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}
这时SqlSessionInterceptor拦截器(SqlSessiontemplate中的私有类)会拦截到。
private class SqlSessionInterceptor implements InvocationHandler {
...
Object result=method.invoke(sqlSession, args);
...
}
其中arg[0]的值为com.services.forum.dao.SubjectDAO.updateSubjectById()
方法,args[1]的值为SqlSessionTemplate中public int update(String statement, Object parameter)
中的parameter,即dao层方法中传的subject对象。this.sqlSessionProxy的类型为DefaultSqlSession
,其中update(...)方法return executor.update(ms, wrapCollection(parameter));
,wrapCollection(parameter)对参数进行包装,该方法的实现如下:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
此方法首先定义一个StrictMap,
如果参数类型是Collection的子类,则在map中加入一条entry:("collection", object),返回map
如果参数类型是List的子类,则在map中加入一条entry:("list", object),返回map
如果参数是数组,则在map中加入一条entry:("array", object),返回map
否则返回参数object
随后会执行以下方法:
SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Configuration
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandl er resultHandler, BoundSql boundSql){
...
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
...
}
PreparedStatementHandler
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBound s, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds row Bounds, ResultHandler resultHandler, BoundSql boundSql) {
...
boundSql = mappedStatement.getBoundSql(parameterObject);
...
}
MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
...
}
再跟几步会进入DynamicSqlSource类中的getBoundSql()方法
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
这个方法是生成sql语句的方法
sqlSourceParser中是设置和一些注册的类型[处理机的]别名,如下图
再跟到SqlSourceBuilder中的parse()方法,
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalPara meters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
该方法中有个 originalSql参数,这是sql映射文件中的原始sql语句,
update t_forum_subject SET title=#{title}, brief=#{brief}, is_top=#{isTop}, is_open=#{isOpen}, last_modify_time=#{lastModifyTime}, last_modify_userid=#{lastModifyUserId} where id=#{id} and enable=1
GenericTockenParser类中的public String parse(String text)方法将originalSql中的#{}
转为了?
,并且在handler的属性parameterMappings中添加相应的映射
public String parse(String text) {
...
builder.append(handler.handleToken(content));
...
}
SqlSourceBuilder
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
再回到DynamicSqlSource类中getBoundSql方法中,
现在sqlSource中sql属性值变为
update t_forum_subject SET title=?, brief=?, is_top=?, is_open=?, last_modify_time=?, last_modify_userid=?
where id=? and enable=1
parmeterMappings属性的值如上图所示
boundSql的值如上图
SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
最后进入DefaultParameterhandler类中的
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
在这个方法中遍历parameterMappings,将parameterMapping中property的值赋给propertyName,再通过
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
将subject对象中的值赋给value,然后typeHandler.setParameter(ps, i + 1, value, jdbcType);
将value值加入到ps的ColumnMap, ColumnNames, ColumnValues,再执行SimpleExecutor的doUpdate()中的return handler.update(stmt);
然后,就没有然后了
这次更新操作就完成了。
(三)其他类型的参数
我懒,不想写了
---------------------------------------EOF--------------------------------------
我的心愿是世界和平!~( ゜▽゜)つロ
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。