共计 14767 个字符,预计需要花费 37 分钟才能阅读完成。
springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)
java 开源商城零碎
代码版本
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
既然是 springboot 那么要想晓得怎么运行的,有从三个中央动手
xxxAutoConfiguration
- yml 对应的配置文件(
xxxProperties
) - 配置的注解
咱们先来看三个类
1. MybatisAutoConfiguration 结构 SqlSession
这个类通过 dataSource
的配置,结构出 SqlSessionFactory
与 SqlSessionTemplate
。如果以前应用 spring 相干的 api 的话,应该会比拟相熟 jdbcTemplate
与 redisTemplate
之类的。SqlSessionTemplate
这个命名,就会让人联想到这个也是相似的性能。而 session
这个词很显著就是与服务之间交互保留连贯状态的货色。Factory 是工厂模式。从而能够得出:SqlSessionFactory
是用来创立 SqlSession
的,SqlSession
能够关上或敞开与数据库的连贯。SqlSessionTemplate
就是操作这些开关的要害。
@EnableConfigurationProperties(MybatisProperties.class)
public class MybatisAutoConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 省略...}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {// 省略...}
}
2. MybatisProperties 读取配置信息
这个类次要是将配置文件外面的配置转成 bean 映射,配置文件相似这个样子
#mybatis 的相干配置
mybatis:
#mapper 配置文件
mapper-locations: classpath:mapper/*Mapper.xml
type-aliases-package: com.frozen-watermelon.**.model
#开启驼峰命名
configuration:
map-underscore-to-camel-case: true
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {public static final String MYBATIS_PREFIX = "mybatis";}
3. @MapperScan 扫描 Mapper 接口
咱们通常为了确定被扫描的 mapper 所属的包,都会有这样一个配置
@Configuration
@MapperScan({"com.frozen-watermelon.**.mapper"})
public class MybatisConfig {}
这里就有这个注解@MapperScan
,那么这个注解是如何在源码中使用的呢?
咱们先看下这个注解类
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {String[] value() default {};
String[] basePackages() default {};}
这外面 @Import
了 MapperScannerRegistrar.class
,也就是说这个类被实例化了。这个类同时实现了ImportBeanDefinitionRegistrar
接口,也就是说 registerBeanDefinitions()
这个办法会被调用
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取 @MapperScan 的配置信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// 筹备构建 MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// 省略...
List<String> basePackages = new ArrayList<>();
// 获取注解的配置的信息,basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 构建对象
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
从下面的代码能够看出最终构建了一个 MapperScannerConfigurer
对象。那么 MapperScannerConfigurer
到底是干嘛用的呢?MapperScannerConfigurer
实现了 BeanDefinitionRegistryPostProcessor
接口,也就是说postProcessBeanDefinitionRegistry()
这个办法创立完 bean 之后会被调用
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 省略...
// 扫描 basePackage
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
4. 创立 mapper 代理对象
咱们来看下扫描干了啥
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 真正的执行扫描 这里的 doScan 办法,调用的应该是 ClassPathMapperScanner 外面的扫描办法,下面 new 进去的
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 将 beanDefininition 进行 register 操作,前面就能够创立这个 bean 了
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的构造方法 `ClassPathBeanDefinitionScanner` 这个是 spring 的办法,次要是用来结构一个 bean,此时是结构 basePackage 外面的各种 mapper 的定义信息
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 {
// 解决 bean 定义信息,将下面创立的 bean 定义信息传入
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {definition = (AbstractBeanDefinition) holder.getBeanDefinition();
// 这里的 mapperFactoryBeanClass 是 MapperFactoryBean,而原来的 definition 是 basePackage 外面的各种 mapper 的定义信息,神奇的操作
// 能够看下 debugger 的截图就能够看到这微妙的信息,MapperFactoryBean 这个货色很重要
definition.setBeanClass(this.mapperFactoryBeanClass);
// 上面增加了一堆 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;
}
}
}
}
将一般的 mapper 变成MapperFactoryBean
下面曾经将 bean 的定义信息进行了 registerBeanDefinition
,而此时 register 的是MapperFactoryBean
这个 bean。而这个 bean 实现了 FactoryBean
的接口,那么咱们就来看下 MapperFactoryBean
这个信息,顺便看下 getObject()
办法
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);
}
}
这个 getSqlSession()
就是用 SqlSessionTemple
啦,这里的 getMapper
又是什么呢?一路找上来就能发现,是 mybatis
在 MapperProxyFactory
应用 jdk 动静代理生成的代理 mapper
// SqlSessionTemple
public <T> T getMapper(Class<T> type) {return getConfiguration().getMapper(type, this);
}
// 往下找
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
// 往下找
// MapperRegistry
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);
}
}
// 往下找
// MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
}
下面的一堆操作,通过 FactoryBean
的getObject()
只是将代理过后的 mapper 交给了 spring 去治理,那么 mybatis
是怎么治理的呢?咱们持续回到 MapperFactoryBean
,发现他也实现了InitializingBean
接口。所以还有 afterPropertiesSet()
会被调用。
public abstract class DaoSupport implements InitializingBean {
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {initDao();
}
catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
@Override
protected void checkDaoConfig() {notNull(this.sqlSessionTemplate, "Property'sqlSessionFactory'or'sqlSessionTemplate'are required");
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
protected void checkDaoConfig() {super.checkDaoConfig();
notNull(this.mapperInterface, "Property'mapperInterface'is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 代理的 mapper 放到 mybatis 的 Configuration 中
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {logger.error("Error while adding the mapper'" + this.mapperInterface + "'to configuration.", e);
throw new IllegalArgumentException(e);
} finally {ErrorContext.instance().reset();}
}
}
}
下面通过configuration.addMapper(this.mapperInterface);
将代理的 mapper 放到 mybatis 的 Configuration 中。下面的所有,都是对@MapperScan
扫描进去的接口创立动静代理的操作。
持续看下 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 {
// 缓存 mapper 代理
knownMappers.put(type, new MapperProxyFactory<>(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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析
parser.parse();
loadCompleted = true;
} finally {if (!loadCompleted) {knownMappers.remove(type);
}
}
}
}
最初看下解析的办法
public void parse() {String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 这外面就会去解析与 Mapper 接口雷同包名上面的 xml 文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {if (!canHaveStatement(method)) {continue;}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {parseResultMap(method);
}
try {
// 这里回去解析接口办法上的 SQL 注解
parseStatement(method);
} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();}
5. MapperProxy 代理 mapper 接口的办法
咱们曾经晓得 mapper 的所有的接口都会被代理,这个代理类是谁呢?不言而喻的是MapperProxy
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
}
假如咱们有一个这样的 mapper:
public interface UserMapper {int getById(String userId);
}
这个时候咱们调用 userMapper.getById("1")
会产生什么呢?
因为被 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 {
// 缓存办法,并执行办法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// MapUtil.computeIfAbsent 如果存在就返回缓存对象,如果不存在再结构
return MapUtil.computeIfAbsent(methodCache, method, m -> {if (m.isDefault()) {
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 {
// 缓存的办法
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
咱们持续看 PlainMethodInvoker
这个类的 invoke()
办法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);
}
通过办法是增删改查(比方 <select>
标签判断出是查问),调用 SqlSession
增删改查对应的办法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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;
}
springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)
java 开源商城零碎