public static void select() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 根据类型得到接口,接口直接调用
TransactionMapper transactionMapper = sqlSession.getMapper(TransactionMapper.class);
// 代理类调用,得到返回值与sqlSession对比
// String flowKey = sqlSession.selectOne("com.dzq.mapper.TransactionMapper.getFlowKey", 10);
// SqlSession调用知道namespace和参数,那么接口调用如何知道呢,通过动态代理的object和method方法拼接,在根据方法对应的标签找到是insert还是update
// 然后再得到参数
String flowKey = transactionMapper.getFlowKey(10);
System.out.println(flowKey);
}
上面已经讲到InvoceHandler接口的方法,那什么时候加入到配置中的呢
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 找到对应的namespace的接口进行解析
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
// 得到namespace
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
// 转换到class类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 加入到config
configuration.addMapper(boundType);
}
}
}
}
publicvoid addMapper(Class type) { // 校验工作 if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 放入到mappers,new MapperProxyFactory (type)创建动态代理工厂,如上面复习的一样 knownMappers.put(type, new MapperProxyFactory (type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
下面讲如何获取
TransactionMapper transactionMapper = sqlSession.getMapper(TransactionMapper.class);
publicT getMapper(Class type, SqlSession sqlSession) { // 得到对应的动态代理工厂 final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 创建代理,如最开始的复习,执行主要看MapperProxy的invoke方法 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用了Object方法和接口的默认方法,不是重点
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 根据方法生成MapperMethod方法,后面比较简单,就是创建MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
MapperMethod关键属性和方法
// command:sql命令,记录着命令类型:insert,update等,还有namespace // method:记录返回值,参数等 private final SqlCommand command; private final MethodSignature method;
// 执行,在创建之后就执行了
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据不同的类型调用不同的sqlSession方法
switch (command.getType()) {
case INSERT: {
//根据method组装参数,因为sqlSession只有一个参数
//command.getName()=namespace
Object param = method.convertArgsToSqlCommandParam(args);
// rowCountResult 根据sqlSession执行的结果转换返回值,返回值类型在method记录
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);
}
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;
}
与Spring结合
spring复习为了能够结合Spring,mybatis特意扩展开发mybatis-spring.
mybatis能够实现SqlSession获取到Mapper类,那么就看看如何注入到springbean中。
- spring bean存在的2种形式:普通的bean和工厂bean
普通的bean:直接获取对应类的示例,比如以下,dataSource直接获取DriverManagerDataSource属性
工厂bean:获取对应bean的工厂,通过getObject()获取,比如以下,sqlSessionFactory获取SqlSessionFactoryBean通过getObject()获得。
为什么需要FactoryBean,有一些配置不仅仅是一个属性那么简单,需要大量的解析操作(代码量),通过上面的源码知道SqlSessionFactory是持有Config的,config是通过解析文件来获得的。那么什么时候解析呢?通常和InitializingBean配合,实现afterPropertiesSet,当属性赋值完成后,可以根据属性值完成解析工作。解析完成后创建SqlSessionFactory,然后通过FactoryBean接口就可以获取了。
// 属性赋值完成后,通过属性进行下一步操作
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 构建sqlSessionFactory,buildSqlSessionFactory不在介绍
this.sqlSessionFactory = buildSqlSessionFactory();
}
// 通过上面的构建,这里就直接获取了
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
- spring对BeanDefinition处理扩展,继承BeanDefinitionRegistryPostProcessor,实现postProcessBeanDefinitionRegistry
BeanDefinition是对bean注解的解析,还没有生成bean对象,可以通过postProcessBeanDefinitionRegistry进行处理,获取进行其他处理逻辑,相当于spring的一个监听器,做一些额外的事情。比如:org.mybatis.spring.mapper.MapperScannerConfigurer,其实并不是为了使用这个类对象,只是为了把对应的Mapper接口注册到spring。任何类都可以,只要符合业务逻辑的类名即可。
回忆下mybatis,Mapper代理是可以通过SqlSeesion对象通过类获取到的,那么把这个对象注入到bean就可以了,bean的2种形式已经介绍过了,mybatis是通过工厂bean的方式注入的,MapperFactoryBean,那看看源码是如何通过postProcessBeanDefinitionRegistry注入的吧?
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 通过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);
// 赋值MapperFactoryBeanClass类
//public void setMapperFactoryBeanClass(Class extends MapperFactoryBean> mapperFactoryBeanClass) {
// 赋值的时候为空,默认值MapperFactoryBean
// this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
//}
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
// 开始解析,那么就看看生成什么样子的BeanDefinition
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
开始解析
// 开始解析
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// ClassPathMapperScanner重新了这个方法,一定要看重写的方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
子类重写的方法
@Override public SetdoScan(String... basePackages) { // 还是调用父类获取到解析的beanDefinitions Set beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 开始操作BeanDefinition processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
对BeanDefinition进行二次加工
private void processBeanDefinitions(SetbeanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); boolean scopedProxy = false; if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { definition = (AbstractBeanDefinition) Optional .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]")); scopedProxy = true; } String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); // MapperFactoryBean构造函数唯一的参数就是Mapper本身 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // BeanClass替换成mapperFactoryBeanClass:MapperFactoryBean definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); // Attribute for MockitoPostProcessor // https://github.com/mybatis/spring-boot-starter/issues/475 definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName); 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; } // 上面的代码把属性值放入到bean定义中,通过byType方式赋值属性 // public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { // if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { // this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); // } // } if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); if (scopedProxy) { continue; } if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) { definition.setScope(defaultScope); } if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } }
至此Mybatis如何与spring结合讲解完成。
设计模式总结 工厂模式- SqlSessionFactory:负责创建SqlSession
public interface SqlSessionFactory {
// 创建openSession无参,关键参数默认
SqlSession openSession();
// 创建是否自动提交的SqlSession,参数属于Connection的重要属性,以下方法类似
// 关键属性由创建者决定
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
构造器模式
- SqlSessionFactoryBuilder负责构建SqlSessionFactory
使用心得:SqlSessionFactory实现类的构造方法public DefaultSqlSessionFactory(Configuration configuration),目前已有的资源(xml文件)需要转换,那么直接使用构造器,拿到xml,然后再build中进行转换来构造SqlSessionFactory
// 通过额外的参数转换成SqlSessionFactory真正需要的参数
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 最终是拿到Config创建SqlSessionFactory,使得SqlSessionFactory代码内的构造器更加简洁
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}



