使用mybatis + mapper配置的方式,在业务代码中
@Resource
private ViewpointPkgDao viewpointPkgDao;
// 单独在粉丝端过滤出没有关联组合code的观点包
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
org.apache.ibatis.binding.MapperProxy
// 实现了 InvocationHandler接口??有何用
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
// 构造,传入了 SqlSession,说明每个session中的代理对象的不同的!
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
org.apache.ibatis.binding.MapperProxyFactory#newInstance
通过mapper代理工厂创建mapper代理
// jdk动态代理
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);
}
接着newInstance
被org.apache.ibatis.binding.MapperRegistry#getMapper
调用
public class MapperRegistry {
private Configuration config;
// 保存了每个接口类,对应mapper代理工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
// 通过动态代理工厂生成maper代理实例。
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");
// 这一步很关键,用于加载配置添加mapper,所以后面我们才能直接获取到mapper
SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
//这里不再调用SqlSession的api,而是获得了接口对象,调用接口中的方法。
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) {
// 这里就调用了configuration的getmapper
return getConfiguration().getMapper(type, this);
}
这就引申出 何处添加了 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));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 解析mapper.xml文件,构建出每个method的statment,添加到configuration的mappedStatements(Map)
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 {
// 这一句调用了添加addMapper操作
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
// 进一步在spring容器创建SqlSessionFactoryBean时,便构建好了mapper工厂bean所依赖的xxxmapper
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框架用如上 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);
// 执行器接受mapStatement,参数,执行sql,获得结果
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,详见下一章。