目录

MyBatis----03

1. MyBatis原理

MyBatis底层使用的是动态代理帮我们创建实现接口的实现类。

测试代码,获得接口实现:

//获得UserMapper接口的实现类 UserMapper mapper = session.getMapper(UserMapper.class); //调用UserMapper方法 List<User> list = mapper.findAll(); 

调用DefaultSqlSession中的getMapper方法:

@Override public <T> T getMapper(Class<T> type) { return this.configuration.getMapper(type, this); } 

调用Configuration中的getMapperfangfa:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); } 

调用MapperRegister总的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { //创建代理对象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } } 

调用MapperProxyFactory中的newInstance方法:

@Override protected T newInstance(MapperProxy<T> mapperProxy) { //清晰的看到,使用动态代理技术,创建了接口的实现类 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } 

Proxy.newProxyInstance方法的第三个参数:

//实现了InvocationHandler接口 public class MapperProxy<T> implements InvocationHandler, Serializable{} 

查看invoke方法:

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { //调用的是Object中继承方法(例如:toString) -> 直接执行 return method.invoke(this, args); } else { //调用Mapper接口中声明的方法,需要进一步处理 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } 

调用MapperPtoxy中的invoke方法:

@Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } 

调用MapperMethod的execute方法:

public Object execute(SqlSession sqlSession, Object[] args) { Object result; //分析判断要执行的sql类型 switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } //查询 case SELECT: 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 if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: 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; } 

执行MapperMethod中的executuForMany方法:

@Override private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); //查询 result = sqlSession.selectList(command.getName(), param, rowBounds); } else { //查询 result = sqlSession.selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } 

调用DefaultSqlSession的selectList方法:

@Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } 
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds
rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); //调用执行器对象的query方法,进行查询 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } 

如果没有缓存存在,会调用CachingExecutor中的query方法查询:

@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException { //从XML中加载sql语句 BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //执行查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } 

调用CachingExecuter中的query方法:

@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") //获得缓存中是否存在查询结果 List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { //如果缓存中没有,就去jdbc中查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //并放入缓存 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //未开启缓存,直接查询 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 

进入到baseExecutor中执行query方法:

@Override 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++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { 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(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } 

BaseExecutor中的queryDataBase方法:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql
boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } 

调用SimpleExecutor中的doQuery方法:

@Override 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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); //执行查询 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } 

进入RountingStatementHandler中的query方法:

@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.query(statement, resultHandler); } 

进入PreparedStatementHandler中的query方法:

@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //jdbc中的PrepareStatement的execute方法 -> 执行sql ps.execute(); //封装并返回结果 return resultSetHandler.handleResultSets(ps); } 

进入DefaultResultSetHandler中的handlerResultSets封装查询结果:

@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling
results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); 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); } 

至此,MyBatis源码观看完毕,结论:

  • MyBatis底层就是使用了动态代理技术帮我们创建Mapper接口的实现类;
  • 查询数据库,MyBatis底层用的是JDBC;
  • 封装查询结果使用反射技术。

2. 如何获得保存后的主键值

2.1 使用useGenerateKeys属性

<!-- useGenerateKeys:声明需要返回生成的主键值 keyProperty:返回主键值到对象的那个属性中 --> <insert id="save2" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into t_user(u_name,u_password) values(#{name},#{password}) </insert> 

2.2 使用selectKey元素

<insert id="save3" parameterType="User" > <!-- selectKey元素:需要返回指定sql语句的查询结果 keyColumn:指定主键列列名 keyProperty:指定主键对应的属性名 order:指定select LAST_INSERT_ID();语句在insert语句之前还是之后执行 BEFORE:之前 AFTER:之后 resultType:主键值类型 --> <selectKey keyColumn="u_id" keyProperty="id" order="AFTER" resultType="int" > select LAST_INSERT_ID() ; </selectKey> insert into t_user(u_name,u_password) values(#{name},#{password}) </insert> 

3. 动态SQL

动态SQL使用场景:SQL语句内容不固定,而是根据运行实际传入的参数发生改变,
例如:做一个带有多个查询条件的列表页面,如果一个条件都没有:执行无条件的查询;如果使用了n个条件:执行带有n个条件的sql查询。

# 用户输入的条件是id: SELECT * FROM t_user WHERE u_id = ? #用户输入的条件是name: SELECT * FROM t_user WHERE u_name LIKE ? #用户输入的条件是什么都没有: SELECT * FROM t_user #用户输入的条件是id和name: SELECT * FROM t_user WHERE u_id = ? AND u_name LIKE ?

3.1 使用where元素配合if,可拼接多个条件

<select id="findByIdAndName" resultMap="userMapper" parameterType="User"> select * from t_user <where> <!-- 如果id属性不为空,那么标签体中的条件 --> <if test="id!=null and id!=''"> and u_id = #{id} </if> <if test="name!=null and name!=''"> and u_name like #{name} </if> </where> </select> 

3.2 使用where元素配合choose,只拼接一个条件

<select id="findByIdAndName2" resultMap="userMapper" parameterType="User"> select * from t_user <where> <choose> <!--判断条件是否成立 --> <when test="id!=null and id!=''"> and u_id = #{id} </when> <when test="name!=null and name!=''"> and u_name like #{name} </when> <!-- 上面的when都不成立,只拼接该元素(选填) --> <otherwise> and 1=1 </otherwise> </choose> </where> </select> 

3.3 使用where元素配合foreach实现in|not in查询

3.3.1 针对数组

UserMapper;

//根据多个id查找User对象 List<User> findByIdArray(@Param("ids") int[] id); 

UserMapper.xml:

<select id="findByIdArray" resultMap="userMapper"> select * from t_user <where> <!-- collection:指定要遍历的(数组|集合)对象 item:为当前遍历的元素指定一个索引 open:条件左半部分 close:条件右半部分 separator:每个元素的分隔符 id in (1,2,3) --> <foreach collection="ids" item="id" open="u_id in(" separator="," close=")"> #{id} </foreach> </where> </select> 

3.3.2 针对集合(set与list同理)

UserMapper:

List<User> findByIdList(@Param("ids") List<Integer> ids); 

UserMapper.xml:

<select id="findByIdList" resultMap="userMapper"> select * from t_user <where> <!-- collection:指定要遍历的(数组|集合)对象 item:为当前遍历的元素指定一个索引 open:条件左半部分 close:条件右半部分 separator:每个元素的分隔符 id in (1,2,3) --> <foreach collection="ids" item="id" open="u_id in(" separator="," close=")"> #{id} </foreach> </where> </select> 

3.4 SQL片段

抽取经常重复出现的sql:

<!-- 抽取xml中出现频率高的sql --> <sql id="baseQuery"> select * from t_user </sql> 

在其它配置中引用该sql:

<select id="findByIdList" resultMap="userMapper"> <!-- 引用抽取出来的sql --> <include refid="baseQuery"></include> <where> <!-- collection:指定要遍历的(数组|集合)对象 item:为当前遍历的元素指定一个索引 open:条件左半部分 close:条件右半部分 separator:每个元素的分隔符 id in (1,2,3) --> <foreach collection="ids" item="id" open="u_id in(" separator="," close=")"> #{id} </foreach> </where> </select>