?
1. 问题背景
使用mybatis + mapper配置的方式,在业务代码中
1.1 定义,自动导入Dao类
@Resource
private ViewpointPkgDao viewpointPkgDao;
1.2 使用,直接使用ViewpointPkgdao.listViewpointPkg()
List<ViewpointPkgFullInfo> noRelated = viewpointPkgDao.listViewpointPkg(qryForm).stream().
filter(vp -> StringUtil.isEmpty(vp.getCombiCode())).collect(Collectors.toList());
viewpointPkgList = CommonTool.genCopyBeanList(noRelated,
该listViewpointPkg 方法在ViewpointPkgdaoMapper.xml 中有定义,即sql实现了该方法,指定了出参和入参,
<select id="listViewpointPkg"
parameterType="xxxx.ViewpointPkgDataBaseQryForm"
resultType="xxxxx.ViewpointPkgFullInfo">
SELECT
a.viewpoint_pkg_id AS viewpointPkgId,
a.name,
a.broker_id AS brokerId,
a.broker_manager_id AS brokerManagerId,
a.status,
...
FROM
<include refid="filterViewpointPkgFullInfoSql"/>
<if test="orderBySql != null">
ORDER BY ${orderBySql}
</if>
<if test="limitSql != null">
LIMIT ${limitSql}
</if>
</select>
为啥,通过MyBatis对ViewpointPkgDaomapper.xml 自动生成工具代码后,本质MyBatis和我们都没有实现ViewpointPkgDao 接口,业务代码中能直接调用ViewpointPkgDao 接口的相关方法?
答案是: spring容器bean注入+动态代理
从调试结果也证明了这一动态代理猜想,org.apache.ibatis.binding.MapperProxy@6304a864
2. spring 注入mapperBean
https://zhuanlan.zhihu.com/p/196744982
3. 动态代理
3.1 定义
org.apache.ibatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
3.2 被创建
org.apache.ibatis.binding.MapperProxyFactory#newInstance 通过mapper代理工厂创建mapper代理
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
3.2.1 getMapper
接着newInstance 被org.apache.ibatis.binding.MapperRegistry#getMapper 调用
public class MapperRegistry {
private Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
紧接着mapperRegistry.getMapper 被 org.apache.ibatis.session.Configuration#getMapper 调用
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
而configuration.getMapper 最终可被这3处调用 其中sqlSessionManager,DefaultSqlsession 是原生mybatis 的调用,而sqlSessionTemplate 是mybatis-spring 框架的调用
这也是为什么我们可以这样使用:
public static void main(String[] args) {
InputStream inputStream =
Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.getUserByName("tom");
}
org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
MapperFactoryBean 对应我们的xxxMapper接口,本质上是mapper代理。
org.mybatis.spring.mapper.MapperFactoryBean#getObject
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
org.mybatis.spring.SqlSessionTemplate#getMapper
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
3.2.2 addMapper
这就引申出 何处添加了 mapper, org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
org.apache.ibatis.binding.MapperRegistry#addMappers org.apache.ibatis.binding.MapperRegistry#addMappers org.apache.ibatis.session.Configuration#addMappers org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration org.apache.ibatis.builder.xml.XMLConfigBuilder#parse 最终parse 被一下2处调用,用于构建mapper,生成mapper代理
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 给spring-mybatis框架用
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
...
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (logger.isDebugEnabled()) {
logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
org.apache.ibatis.session.SqlSessionFactoryBuilder#build 纯mybatis框架用
3.2.3 MappedStatement
如上 addMapper 所述,mapper.xml 中method 转化为MappedStatement 存储在configuration 的mappedStatements 中
那mappedStateMent 何时使用呢? 先说结论: 执行时使用
先看另原始调用
Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
List<User> list =
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList 就涉及到了mappedStateMent
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
至于执行器底层如何绑定参数,执行sql,详见下一章。
|