前言
spring
和 mybatis
进行整合时,需要使用以下这个依赖包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
spring
容器启动的时候需要使用一个 xml
配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-spring.xml");
xml
文件中就涉及到整合的配置信息
<!-- dataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!-- spring 和 mybatis 整合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定 mybatis 要连接的数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- mybatis 配置文件的地址 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- mybatis 的 mapper 文件的地址 -->
<property name="mapperLocations" value="classpath:com/atguigu/mapper/*.xml"/>
</bean>
<!-- 配置 Mapper 接口扫描 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.atguigu.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
dao
层接口
public interface ProductInfoMapper {
int deleteByPrimaryKey(Integer productId);
int insert(ProductInfo record);
int insertSelective(ProductInfo record);
ProductInfo selectByPrimaryKey(Integer productId);
int updateByPrimaryKeySelective(ProductInfo record);
int updateByPrimaryKey(ProductInfo record);
}
对应的 mapper.xml
文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.joonwhee.open.mapper.UserPOMapper" >
<resultMap id="BaseResultMap" type="com.joonwhee.open.po.UserPO">
<result column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
</resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from product_info
where product_id = #{productId,jdbcType=INTEGER}
</select>
</mapper>
spring
整合 mybatis
原理
1. 类 MapperScannerConfigurer
MapperScannerConfigurer
这个类实现了接口 BeanDefinitionRegistryPostProcessor
,会在 spring
构建 IOC
容器的早期被调用重写的 postProcessBeanDefinitionRegistry
方法
参考:spring IOC:invokeBeanFactoryPostProcessors 详解
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 1.新建一个ClassPathMapperScanner并填充相应属性
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
// 2.设置mapper bean是否需要懒加载
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 3.注册Filter,因为上面构造函数我们没有使用默认的Filter,
// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
scanner.registerFilters();
// 4.扫描basePackage,basePackage可通过",; \t\n"来填写多个,
// ClassPathMapperScanner重写了 doScan 方法
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
ClassPathMapperScanner
这个类是 spring-mybatis.jar
中的的类,它 extends
于 ClassPathBeanDefinitionScanner
,重写了 doScan
方法
1.1. ClassPathMapperScanner
中的 doScan
方法
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1.找出指定包名下符合bean定义的BeanDefinition并注册到容器中
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 2.对扫描到的beanDefinitions进行处理,主要4件事:
// 1)将bean的真正接口类添加到通用构造函数参数中
// 2)将beanClass直接设置为MapperFactoryBean.class,
// 结合1,相当于要使用的构造函数是MapperFactoryBean(java.lang.Class<T>)
// 3)添加sqlSessionFactory属性,sqlSessionFactoryBeanName和
// sqlSessionFactory中,优先使用sqlSessionFactoryBeanName
// 4)添加sqlSessionTemplate属性,同样的,sqlSessionTemplateBeanName
// 优先于sqlSessionTemplate,
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
1.1.1. processBeanDefinitions
方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// 设置 definition 的构造函数参数值为:映射接口的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 改变 definition 的beanClass为MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
// 添加属性addToConfig为true
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// 分别为 definition 添加 sqlSessionFactory 和 sqlSessionTemplate属性
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
// 设置属性按类型注入
definition.setLazyInit(lazyInitialization);
}
}
类 MapperScannerConfigurer
小结
- 创建扫描器
ClassPathMapperScanner
- 使用
ClassPathMapperScanner
扫描注册basePackage
包下的所有的mapper
接口类,将mapper
接口类封装成为BeanDefinition
对象,注册到spring
容器中 - 同时会将
basePackage
包下的所有bean
进行一些特殊处理:beanClass
设置为MapperFactoryBean、bean
的真正接口类作为构造函数参数传入MapperFactoryBean
2. 类 SqlSessionFactoryBean
对于 SqlSessionFactoryBean
来说,实现了 InitializingBean
和 FactoryBean
接口
FactoryBean
可以自定义创建实例bean
的方法,只需要实现它的getObject()
方法InitializingBean
则是会在bean
初始化阶段被调用
2.1 实现了 FactoryBean
接口的 getObject()
方法
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
// 省略部分代码
// 构建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
2.2. buildSqlSessionFactory()
方法
主要做了几件事:
- 对我们配置的参数进行相应解析
- 使用配置的参数构建一个
Configuration
- 使用
Configuration
新建一个DefaultSqlSessionFactory
这边的核心内容是对于 mapperLocations
的解析,如下代码
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 省略部分代码......
// 5.mapper处理(最重要)
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 5.1 新建XMLMapperBuilder
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 5.2 解析mapper文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 6.使用 targetConfiguration 构建 DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
2.3. 解析 mapper
文件的 parse()
方法
public void parse() {
// 1.如果 resource 没被加载过才进行加载
if (!configuration.isResourceLoaded(resource)) {
// 1.1 解析 mapper 文件
configurationElement(parser.evalNode("/mapper"));
// 1.2 将 resource 添加到已加载列表
configuration.addLoadedResource(resource);
// 1.3 绑定 namespace 的 mapper 见 2.6 方法
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
2.4. 解析 mapper
文件的 configurationElement(parser.evalNode("/mapper"))
方法
private void configurationElement(XNode context) {
try {
// 1.获取namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 2.设置currentNamespace属性
builderAssistant.setCurrentNamespace(namespace);
// 3.解析parameterMap、resultMap、sql等节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 4.解析增删改查节点,封装成Statement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 解析增删改查节点,封装成Statement
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 1.构建XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 2.解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XNode
的 Debug
示意图如下
每个 XNode
都相当于如下的一个 SQL
,下面封装的每个 MappedStatement
可以理解就是每个 SQL
<select id="queryByPrimaryKey" resultMap="BaseResultMap"
parameterType="java.lang.Integer">
select id, name, password, age
from user
where id = #{id,jdbcType=INTEGER}
</select>
2.5. 解析节点 statementParser.parseStatementNode()
方法
public void parseStatementNode() {
// 省略所有的属性解析......
// 将解析出来的所有参数添加到 mappedStatements 缓存
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// MapperBuilderAssistant.java
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 1.将 id 填充上 namespace,例如:queryByPrimaryKey变成
// com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 2.使用参数构建 MappedStatement.Builder
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 3.使用 MappedStatement.Builder 构建 MappedStatement
MappedStatement statement = statementBuilder.build();
// 4.将 MappedStatement 添加到缓存
configuration.addMappedStatement(statement);
return statement;
}
该方法会将节点的属性解析后封装成 MappedStatement
,放到 mappedStatements
缓存中key
为 id
。例如:
key
为:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
value
为:MappedStatement
2.6. 绑定 namespace
的 mapper
的 bindMapperForNamespace
方法
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 1.解析 namespace 对应的绑定类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 2.boundType不为空,并且configuration还没有添加boundType,
// 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
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 {
// 将type和以该type为参数构建的MapperProxyFactory作为键值对,
// 放到knownMappers缓存中去
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
主要是将刚刚解析过的 mapper
文件的 namespace
放到 knownMappers
缓存中,key
为 namespace
对应的 class
,value
为 MapperProxyFactory
类 SqlSessionFactoryBean
小结
- 解析处理所有属性参数构建
Configuration
,使用Configuration
新建DefaultSqlSessionFactory
- 解析
mapperLocations
属性的mapper
文件,将mapper
文件中的每个SQL
封装成MappedStatement
,放到mappedStatements
缓存中,key
为id
,例如:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
,value
为MappedStatement
- 将解析过的
mapper
文件的namespace
放到knownMappers
缓存中,key
为namespace
对应的class
,value
为MapperProxyFactory
3. 解析 DAO
文件
DAO
文件,也就是 basePackage
指定的包下的文件,也就是上文的 ProductInfoMapper
上文 doScan
中说过,basePackage
包下所有 bean
定义的 beanClass
会被设置成 MapperFactoryBean.class
,而 MapperFactoryBean
也实现了 FactoryBean
接口,因此直接看 MapperFactoryBean
的 getObject
方法
@Override
public T getObject() throws Exception {
// 1.从父类中拿到sqlSessionTemplate,这边的sqlSessionTemplate也是doScan中添加的属性
// 2.通过mapperInterface获取mapper
return getSqlSession().getMapper(this.mapperInterface);
}
// SqlSessionTemplate
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
// Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 1.从knownMappers缓存中获取
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 2.新建实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory.java
public T newInstance(SqlSession sqlSession) {
// 1.构造一个MapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 2.使用MapperProxy来构建实例对象
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用 JDK 动态代理来代理要创建的实例对象,InvocationHandler为 mapperProxy,
// 因此当我们真正调用时,会走到 mapperProxy 的 invoke 方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
mapperInterface }, mapperProxy);
}
解析 DAO
文件小结
- 通过
mapperInterface
从knownMappers
缓存中获取到MapperProxyFactory
对象 - 通过
JDK
动态代理创建mappper
接口代理对象,InvocationHandler
为MapperProxy
4. DAO
接口被调用
当 DAO
中的接口被调用时,因为 MapperProxy
类 implements
了 InvocationHandler
接口,所以会调用到 MapperProxy
的 invoke
方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 1.创建MapperMethodInvoker
// 2.将method -> MapperMethodInvoker放到methodCache缓存
// 3.调用MapperMethodInvoker的invoke方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// MapperProxy.java
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// 1.放到methodCache缓存,key为method,value为MapperMethodInvoker
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 2.方法为默认方法,Java8之后,接口允许有默认方法
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 3.正常接口会走这边,使用mapperInterface、method、configuration
// 构建一个MapperMethod,封装成PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
4.1. method.invoke(this, args)
方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 1.根据命令类型执行来进行相应操作
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;
}
根据不同的操作类型执行相应的操作,最终将结果返回
4.2. 增删改查
// 1.insert
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
// 2.update
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
// 从mappedStatements缓存拿到对应的MappedStatement对象,执行更新操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 3.delete
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
// 4.select,以executeForMany为例
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 1.参数转换成sql命令参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 2.执行查询操作
result = sqlSession.selectList(command.getName(), param);
}
// 3.处理返回结果
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
@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 {
//从mappedStatements缓存中拿到对应的MappedStatement对象,执行查询操作
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
可以看出,最终都是从 mappedStatements
缓存中拿到对应的 MappedStatement
对象,执行相应的操作
spring
整合 mybatis
原理流程步骤
- 扫描注册
basePackage
包下的所有的mapper
接口类,将mapper
接口类封装成为BeanDefinition
对象,注册到spring
容器中,同时会将basePackage
包下的所有bean
进行一些特殊处理:beanClass
设置为MapperFactoryBean、bean
的真正接口类作为构造函数参数传入MapperFactoryBean
- 解析
mapperLocations
配置的mapper
文件,将mapper
文件中的每个SQL
封装成MappedStatement
,放到mappedStatements
缓存中,key
为id
,例如:id
为 :com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
,value
为MappedStatement
。并且将解析过的mapper
文件的namespace
放到knownMappers
缓存中,key
为namespace
对应的class
,value
为MapperProxyFactory
- 创建
DAO
的bean
时,通过mapperInterface
从knownMappers
缓存中获取到MapperProxyFactory
对象,通过JDK
动态代理创建mapper
接口代理对象,实现了InvocationHandler
接口的类为MapperProxy
DAO
中的接口被调用时,通过mapper
接口代理对象,调用MapperProxy
的invoke
方法,执行相应的增删改查操作