Mybatis 是支持普通的sql查询,存储过程和高级映射的持久层框架,Mybats消除了几乎所有的jdbc 代码和参数的手动设置以及结果集的检索,mybatis使用简单的xml或注解用于配置和原始映射,将接口和java的pojo 映射为数据库中的记录。
xml 配置
<mapper namespace="mapper.UserMapper">
<select id="getUser" resultType="user" parameterType="_int">
select *
from user where id=#{id ,
typeHandler=springMybatis.typeHandler.LongTypehandler}
</select>
<select id="getUserMap" resultType="map" parameterType="_int">
select *
from user where id=#{id ,
typeHandler=springMybatis.typeHandler.LongTypehandler}
</select>
</mapper>
typeHandler
public class LongTypehandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
// TODO Auto-generated method stub
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
// TODO Auto-generated method stub
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return cs.getString(columnIndex);
}
}
测试方法
@Test
public void getUserServiceTest() throws IOException {
try(SqlSession session = sessionFactory.openSession()){
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUser("18");
System.out.println(user);
}
}
@Test
public void objectFactoryTest() {
try(SqlSession session = SessionFactory.openSession()){
UserMapper mapper = session.getMapper(UserMapper.class);
Map<String,String> userMap = mapper.getUserMap("18");
System.out.println(userMap);
}
以上两个测试方法分别将结果集映射为实体类和map集合 , typeHandler将String类型的参数转换为了int类型。mybatis是怎样替我们进行转换的呢,我们从查询的入口开始,逐步分析一下。
根据mapped.xml标签类型以及返回值类型分别使用不同的策略执行
//mapper代理对象判断执行哪种类型的sql
//根据xml中的标签判断,如果是select,执行以下逻辑
if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果方法返回void,并且设置了Resulthandler
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果返回多个值, -- 通过判断返回类型是否是Collection或者数组
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回单个值
result = executeForMap(sqlSession, args);
} else {
//其他情况,所以即使mapper接口方法设置返回值为void,
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
}
取得相应的参数,rowBounds,resultHandler等回调sqlSession的select方法
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
//根据id也是方法名取得MappedStatement对象
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
//判断是否配置了resultMap或者resultType,针对注解
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
//取得sql语句用的参数,会过滤掉resultHandler,rowBounds等一些属性
Object param = method.convertArgsToSqlCommandParam(args);
//是否配置了分页
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
未配置分页使用默认RowBounds对象
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
根据statement取得mappedStatement,statement就是类名+方法名,mappedStatement对象封装了xml中某个节点对象比如:< select >,
具体的执行逻辑交给了执行器Executor
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//根据statement取得mappedStatement,statement就是类名+方法名
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
执行器会先尝试从一级缓存中取出,如果配置了resulthandler是不会走缓存的,因为你可能在resultHandler对结果集做其他操作
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//尝试从缓存中取出,配置了resultHandler不走缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//是否是callable,添加到callable缓存
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//通过数据库查询取值
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
取得Statement对象
configuration.newStatementHandler取得一个RoutingStatementHandler,RoutingStatementHandler起一个路由的作用,可以根据不同的statement type取得相应的StatementHandler
prepareStatement方法取得一个Statement对象,并且会根据用户设置对Statement做一些配置,比如查询超时时间 stmt.setQueryTimeout(timeout);
FetchSize(调用rs.next时就取得fetchSize个结果,这样下次就可以直接从缓存中获取了) ,stmt.setFetchSize(fetchSize);并且会对prepareStatement进行参数配置,如果自定义了typeHandler则会使用自定义的设置,如果未配置则使用默认设置
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//取得全局配置类
Configuration configuration = ms.getConfiguration();
//取得路由statementHandler,会根据不同的statement type取得相应的 StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//取得Statement 对象,并会设置执行超时时间,FetchSize,配置参数等
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
使用TypeHandler来设置参数,TypeHandler继承了BaseTypeHandler,BaseTypeHandler会先对参数值做null判断,如果为null,再判断是配置了jdbcType,如果配置了那么就设置属性值为null ps.setNull(i,jdbcType.TYPE_CODE); 未设置抛出异常,所以我们再可能为null的属性值必须加上{ id,jdbcType = varchar}
public void setParameters(PreparedStatement ps) throws SQLException {
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);
}
//取得BaseTypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
//配置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
配置参数
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
//如果参数为null,并且没有配置jdbcType抛出异常,否则设置为空参数
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
"Cause: " + e, e);
}
} else {
//由子类实现的方法
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
setNonNullParameter是由子类实现的方法,我们可以自定义自己的typehandler进行属性值配置
自定义typeHandler
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
// TODO Auto-generated method stub
ps.setString(i, parameter);
}
好了,以上我们取得了statement对象,并且对其进行了配置,下面可以进行sql执行了
调用了PreparedStatement的execute方法执行了sql,execute返回一个boolean值,如果是查询的话返回true,如果是更新或插入的话就返回false了;我们可以用getResultSet()取得是结果集,getUpdateCount()取得更新的记数
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行sql
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
处理结果集,做最终的映射操作了,getFirstResultSet取出第一个结果集封装在ResultSetWrapper中,取得所有的resultMap配置的java类型,调用handleResultSet循环映射赋值
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//多结果集
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//取得第一个结果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
//取得配置得resultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
//循环为配置的resultMap赋值
while (rsw != null && resultMapCount > resultSetCount) {
//取得ResultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
//映射赋值
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
handleResultSet 判断是否配置了自定义的ResultHandler
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
//是否配置了resultHandler
if (resultHandler == null) {
//未配置使用默认Resulthandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
//使用自定义Resulthandler
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)
}
}
private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//是否嵌套了resultMap
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
//处理嵌套的resultMap
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
//处理普通的resultMap
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
handleRowValuesForSimpleResultMap 中使用skipRows方法处理了分页,
getRowValue取出结果集所映射的对象,使用的方式其实就是用的反射,先使用无参构造实例化对象,再用反射为每个查询表字段所对应的属性赋值,所以我们写的实体类必须要有无参构造,而get,set方法并不需要
storeObject方法使用resultType对所映射处理的java对象做处理
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext resultContext = new DefaultResultContext();
//处理分页,根据rowBounds配置跳过相应行
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//鉴别reusltmap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
//取得结果集映射的对象
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
resultHandler对象调用handleResult方法处理结果集,我们自定义resultHandler是必须重写handleResult对象了
private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
resultHandler.handleResult(resultContext);
}
如果没有使用自定义的resulthandler,那么就会根据mapperd的放回值来判断了,当返回值是集合时使用DefaultResultHandler,用list存储结果集
public void handleResult(ResultContext context) {
list.add(context.getResultObject());
}
返回值是map时,使用map集合存储
public void handleResult(ResultContext context) {
// TODO is that assignment always true?
final V value = (V) context.getResultObject();
final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);
// TODO is that assignment always true?
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
}