目录
1. Spring整合Mybatis底层源码
2. SqlSessionTemplate类的作用
3. Mybatis一级缓存失效问题
1. Spring整合Mybatis底层源码 Mybatis框架可以单独使用,需要用到Mybatis所提供的一些类构造出对应的Mapper对象,然后就能使用Mybatis框架提供的功能,我们先看一个Demo:
@Test
public void testMybatis() throws IOException {
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
// 解析配置文件,得到SqlSession工厂类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 从工厂中获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 然后从SqlSession中获取Mapper的代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
String result = mapper.selectById();
sqlSession.commit();
sqlSession.flushStatements();
sqlSession.close();
}
这是一段很普通的Mybatis的代码,我们点进去看一下getMapper()方法,看一下Mybatis如何生成UserMapper代理对象,这个方法是SqlSession接口的方法,这个接口里面有很多我们平常使用的CRUD方法:
public interface SqlSession extends Closeable {
T selectOne(String var1);
T selectOne(String var1, Object var2);
List selectList(String var1);
List selectList(String var1, Object var2);
List selectList(String var1, Object var2, RowBounds var3);
Map selectMap(String var1, String var2);
Map selectMap(String var1, Object var2, String var3);
Map selectMap(String var1, Object var2, String var3, RowBounds var4);
Cursor selectCursor(String var1);
Cursor selectCursor(String var1, Object var2);
Cursor selectCursor(String var1, Object var2, RowBounds var3);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
T getMapper(Class var1);
Connection getConnection();
}
主要看它默认的实现类DefaultSqlSession的方法:
所属类:org.apache.ibatis.session.defaults.DefaultSqlSession
public T getMapper(Class type) {
return this.configuration.getMapper(type, this);
}
所属类:org.apache.ibatis.session.Configuration
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
所属类:org.apache.ibatis.binding.MapperRegistry
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
mapperProxyFactory.newInstance(sqlSession)就会返回代理对象:
所属类:org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}public Class getMapperInterface() {
return this.mapperInterface;
}public Map getMethodCache() {
return this.methodCache;
}protected T newInstance(MapperProxy mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
把mapper传进去,通过JDK的动态代理生成代理对象。我们可以看一下MapperProxy类,它实现了InvocationHandler接口。
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}return mapperMethod;
}
当我们得到代理对象,去执行Mapper接口里面的方法时,Mybatis就会进入MapperProxy类的invoke()方法,得到方法上面的注解(如:@Select("select 'user'")),然后去执行目标方法。
所有MapperProxy类很重要,上面这些生成代理逻辑,都是Mybatis提供的。
回到Spring,如果Mybatis要和Spring整合,除了Mybatis自带的jar包之外,还需整合的jar包:
org.mybatis
mybatis-spring
1.3.1
为什么呢?举个例子:
@Component
public class UserService { @Autowired
private UserMapper userMapper;
public void test() {
System.out.println(userMapper.selectById());
}}
上面的代码貌似没问题,但是如果没有整合jar包,就会报找不到userMapper这个Bean的错误。
我们知道,Spring在Bean的实例化中进行属性注入时,找不到userMapper,因为在过程中需要把Mybatis生成的UserMapper代理对象注入到这里,赋值该属性,才能进行后续的操作,Spring和其他框架整合亦如此。会把Mybatis创建的类(或代理对象)注册到Spring容器,Spring在用的时候才能找到。
这个章节要说的就是如何把Mybatis生成userMapper变成Spring所需的Bean,但是UserMapper是接口,所以不能用固有的思路通过Spring上下文去getBean(),接口是无法实例化的。
答案就是利用FactoryBean接口,例如:
/**
* @author Kieasar
*/
@Component
public class BaecFactoryBean implements FactoryBean { @Override
public Object getObject() {
// 参数中把类加载器传进去,然后new一个UserMapper,生成代理对象
Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("执行代理对象的方法");
return null;
}
});
return newInstance;
} // 把生成的代理对象的类型返回
@Override
public Class> getObjectType() {
return UserMapper.class;
}
}
此时,这个Bean对象所对应的Bean就是getObject()方法返回的代理对象(newInstance),接下来,单元测试一下:
@Test
public void main() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
执行结果没有报错,说明属性被赋值了,很明显,因为通过属性去找,能找到上面BaecFactoryBean生成的代理对象。
但是test()方法打印结果却为null,因为此时执行的是代理对象的invoke()方法。
上面的例子,貌似我们实现了Mybatis的功能,但是有个问题,此时只有一个UserMapper,如果再有OrderMapper或其他很多的类需要注入,难道我们要写很多个FactoryBean吗?所以,当当前的思路是不行滴~
于是,又想到,代码不要写死,这样不就可以了嘛:
/**
* @author Kieasar
*/
@Component
public class BaecFactoryBean implements FactoryBean { private Class mapperInterface;
public BaecFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
} @Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
this.sqlSession = sqlSessionFactory.openSession();
} @Override
public Object getObject() {
Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("执行代理对象的方法");
return null;
}
});
return newInstance;
} // 把生成的代理对象的类型返回
@Override
public Class> getObjectType() {
return mapperInterface;
}
}
但是,@Component注解修饰的类,注入的时候是单例的,支持生成一个代理对象,不能生成我们需要的若干个Bean,但是,这样可以呀~
@Test
public void main() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
AbstractBeanDefinition userMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
userMapperBeanDefinition.setBeanClass(BaecFactoryBean.class);
userMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 得到一个userMapper代理对象
context.registerBeanDefinition("userMapper",userMapperBeanDefinition);
AbstractBeanDefinition orederMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
orederMapperBeanDefinition.setBeanClass(BaecFactoryBean.class);
orederMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
// 得到一个orderMapper代理对象
context.registerBeanDefinition("orderMapper",orederMapperBeanDefinition);
UserService userServcie = (UserService) context.getBean("userServcie");
userServcie.test();
}
确实可以,但是如果有很多的类需要注入呢……
用扫描的方式,则需要一个扫描器,继承ClassPathBeanDefinitionScanner类,就有了扫描器的功能。
public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
} @Override
protected Set doScan(String... basePackages) {
Set beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
// 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(BaecFactoryBean.class.getName());
}
return beanDefinitionHolders;
} @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
但是Spring不关心接口,Mybatis只关心接口,所以,还需要加工一下,重写isCandidateComponent()方法,如果是接口,返回true,只拦截接口,不拦截类,如上。
扫描哪儿呢?还是注解方便,自定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(BaecBeanDefinitionRegistrar.class)
public @interface BaecMapperScan { String value();
}
并且需要把BaecBeanDefinitionRegistrar类导入进来,这个类用来注册BeanDefinition:
public class BaecBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { // 参数AnnotationMetadata就是注解的元数据信息
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
// 获取注解信息
Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(BaecMapperScan.class.getName());
// 获取扫描路径
String path = (String) annotationAttributes.get("value");
// 引入扫描器
BaecBeanDefinitionScanner scanner = new BaecBeanDefinitionScanner(registry);
// ClassPathBeanDefinitionScanner扫描器默认只扫描@Component注解的类
// 所以,重写IncludeFilter()方法,让它扫描所有的类
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){
return true;
}
});
// 扫描路径
scanner.scan(path);
}
}
我们定义的BaecBeanDefinitionScanner继承自ClassPathBeanDefinitionScanner扫描器,默认只扫描@Component注解的类,可以看一下ClassPathBeanDefinitionScanner的扫描过程,doScan()方法主要会调到这里:
所属类:springframework.context.annotation.ClassPathScanningCandidateComponentProvider
private Set scanCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet<>();
try {
// 获取basePackage下所以的文件资源
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 根据路径获取资源,class文件的file对象
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
// 元数据读取器
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// excludeFilters includeFilters过滤器判断
if (isCandidateComponent(metadataReader)) {
// 构造BeanDefiniiton
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 又判断一次,符合条件才加入
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}----省略无关代码----
这个方法isCandidateComponent()就是用来判断有没有@Component注解:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// 排除过滤器,返回false
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 符合includeFilters的才会进行条件匹配,通过了才是Bean,也就是先看有没有@Component,再看是否符合@Conditianal
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
所以,UserMapper和OrderMapper要想被扫描到,就重写IncludeFilter()方法,让它扫描所有的类。
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){
return true;
}
});
但是自定义的BaecFactoryBean是自己实现代理对象,要整合Mybatis,就需要Mybatis生成得代理对象,顺着这个思路,换成MyBatis的。
@Component
public class BaecFactoryBean implements FactoryBean {private Class mapperInterface;
private SqlSession sqlSession;
public BaecFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}// 通过注入SqlSessionFactory拿到SqlSession
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
this.sqlSession = sqlSessionFactory.openSession();
}@Override
public Object getObject() {
return sqlSession.getMapper(mapperInterface);
}// 把生成的代理对象的类型返回
@Override
public Class> getObjectType() {
return mapperInterface;
}
}
那么,只要Spring能拿到SqlSessionFactory这个Bean,就能取到SqlSession对象,如何拿呢?这就需要程序员自己实现了:
@ComponentScan("com.baec")
@BaecMapperScan("com.baec.mapper")
public class AppConfig {@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}}
当然,也需要提前把Mybatis的配置文件mybatis.xml准备好。
另外,如果不使用Spring的注解,有什么办法注入SqlSession呢?
public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
} @Override
protected Set doScan(String... basePackages) {
Set beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
// 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(BaecFactoryBean.class.getName());
// 自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
return beanDefinitionHolders;
} @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
把BeanDefinition强转成GenericBeanDefinition,位置AutowiredMode属性为按类型注入,Spring会自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean。
然后,上面的BaecFactoryBean就可以修改为:
public class BaecFactoryBean implements FactoryBean {private Class mapperInterface;
private SqlSession sqlSession;
public BaecFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
this.sqlSession = sqlSessionFactory.openSession();
}@Override
public Object getObject() {
return sqlSession.getMapper(mapperInterface);
}@Override
public Class> getObjectType() {
return mapperInterface;
}
}
没有使用Spring的注解,但是实现了注入。
接下来,我们看一下Mybatis的原理,从@MapperScan注解入手:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class>[] basePackageClasses() default {};
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class extends Annotation> annotationClass() default Annotation.class;
Class> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
它也是导入了MapperScannerRegistrar类,该类也是实现了ImportBeanDefinitionRegistrar接口:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
public MapperScannerRegistrar() {
}public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
}Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
String[] var10 = annoAttrs.getStringArray("value");
int var11 = var10.length;
int var12;
String pkg;
for(var12 = 0;
var12 < var11;
++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}var10 = annoAttrs.getStringArray("basePackages");
var11 = var10.length;
for(var12 = 0;
var12 < var11;
++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
var11 = var14.length;
for(var12 = 0;
var12 < var11;
++var12) {
Class> clazz = var14[var12];
basePackages.add(ClassUtils.getPackageName(clazz));
}scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
在这里面,Mybatis同样创建了一个扫描器ClassPathMapperScanner,扫描@MapperScan注解,毋庸置疑,该类也是继承自Spring的扫描器——ClassPathBeanDefinitionScanner类,在它的doScan方法中的processBeanDefinitions()中,同样也设置了AutowireMode属性为:
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
接下来,再看一下ClassPathMapperScanner中创建的MapperFactoryBean,是把Mybatis生成代理对象转化为Bean最关键的一环,这个类也实现了FactoryBean接口:
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
}public MapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}}public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}public Class getObjectType() {
return this.mapperInterface;
}public boolean isSingleton() {
return true;
}public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}public Class getMapperInterface() {
return this.mapperInterface;
}public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}public boolean isAddToConfig() {
return this.addToConfig;
}
}
getObject()方法中也是通过getSqlSession().getMapper(this.mapperInterface)拿到的,属性mapperInterface也是通过MapperFactoryBean类的构造方法注入的。
而getSqlSession()方法在它的父类SqlSessionDaoSupport中定义了,和上面的例子一样。
我们回到文章一开头的代码,这句代码就相当于开了一个事务。
// 从工厂中获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
它与Mybatis的一级缓存和二级缓存有关,但是,如果一级缓存打开了,第二次去执行同样的sql时就不会去请求数据库了,因为SqlSession会缓存第一次执行sql的结果,会执行MapperProxy的invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}MapperMethod mapperMethod = this.cachedMapperMethod(method);
// 主要执行这里
return mapperMethod.execute(this.sqlSession, args);
}
当执行某个方法时,会判断方法是@Select、@Insert等注解,以@Select为例,最终会调用
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
// 最终会调用到这里
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
执行到result = sqlSession.selectOne(this.command.getName(), param); 拿到了sql和方法的参数。
2. SqlSessionTemplate类的作用 在MapperFactoryBean的父类SqlSessionDaoSupport中的SqlSession其实是SqlSessionTemplate类:
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
我们用到的UserMapper是通过SqlSessionTemplate.getMapper()返回的代理对象,那这个SqlSessionTemplate是什么东东呢?
先看它的构造函数:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
传入一个SqlSessionFactory ,最终通过JDK的动态代理生成一个SqlSession的代理对象,并赋值给sqlSessionProxy属性。
再看它的getMapper()方法:
public T getMapper(Class type) {
return this.getConfiguration().getMapper(type, this);
}
参数传的是类型和this,去生成代理对象。mapper.selectById()调用的就是SqlSessionTemplate.selectOne()方法:
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
就是调用的sqlSessionProxy就是构造函数生成的代理对象的selectOne()方法。
此时还没有真正去执行sql,执行sql是DefaultSqlSession对象,执行sql最终会进入SqlSessionInterceptor类,它是构造方法中生成动态代理导进去的,调用到它的invoke():
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
// 执行sql就是在这
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}}return unwrapped;
}
SqlSessionUtils.getSqlSession()方法就是从Session工厂中得到DefaultSqlSession:
所属类:org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
// 通过Spring的事务管理器,从ThreadLocal中是否有SqlSession
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
// 有则返回
if (session != null) {
return session;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}// 没有,则从DefaultSqlSessionFactory工厂类中创建一个新的DefaultSqlSession
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
总之,调用流程就是SqlSessionTemplate.selectOne()---->SqlSessionProxy.selectOne()--->DefaultSqlSession.selectOne()。这儿抛出两个问题:
SqlSessionTemplate的作用是什么?
因为DefaultSqlSession是线程不安全的,没有锁和同步方法,当不同线程同时调用DefaultSqlSession时,DefaultSqlSession确是同一个,存在并发安全问题。
SqlSessionTemplate就是用来解决线程安全这个问题,但该类里面的方法却没有加锁,所以只要保证每个线程有单独的DefaultSqlSession,一个线程执行sql时是同一个DefaultSqlSession就可以了,如何做的?
通过ThreadLocal,每个线程执行之前,先看ThreadLoca中是否有DefaultSqlSession,有直接拿来用,没有则创建。
上面SqlSessionUtils.getSqlSession()方法中的TransactionSynchronizationManager.getResource(),就是Spring的事务,底层就是ThreadLocal:
private static Object doGetResource(Object actualKey) {
// resources是ThreadLocal包装的Map,用来缓存资源的,比如缓存当前线程中由某个DataSource所创建的数据库连接
Map
检查ThreadLocal中是否有SqlSession,返回的是SqlSession的包装类SqlSessionHolder,有则从SqlSessionHolder中取出,没有,则从DefultSqlSession工厂类中创建一个新的:
SqlSessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
然后执行registerSessionHolder()方法,把DefultSqlSession包装成SqlSessionHolder,存到ThreadLocal。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 通过事务管理器,存到ThreadLocal
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
3. Mybatis一级缓存失效问题 为什么Spring和Mybatis整合之后一级缓存会失效?
在上面的源码中,注意有个条件TransactionSynchronizationManager.isSynchronizationActive(),是否开启了Spring事务,如果为true才去执行后面的逻辑。
不符合带来的结果就是,一个线程去执行不同的sql时,发现ThreadLocal中没有SqlSession,于是每次都去创建,导致一级缓存失效,因为一级缓存的运行机制就是同一个SqlSession执行同一个sql时,返回之前缓存的结果。
如何解决?就是方法上加@Transactional注解,就会使用Spring的事务管理器建立的数据库连接,Mybatis如何拿到事务管理器的连接呢?就是prepareStatement()方法:
所属类:org.apache.ibatis.executor.SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 获取数据库连接
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
进而调用到org.apache.ibatis.executor.BaseExecutor.getConnection()方法:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
这个Transaction是Mybatis的事务管理接口,包括事务的提交、回滚,及数据库连接的获取。
会先在DefaultSqlSessionFactory.openSessionFromDataSource()方法中通过TransactionFactory创建的,进而创建了Executor执行器。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
return new DefaultSqlSession(this.configuration, executor, autoCommit);
----省略无关代码----
}
Spring整合Mybatis后得到的是SpringManagedTransaction类,它继承自Transaction接口:
public class SpringManagedTransaction implements Transaction {
private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
private boolean isConnectionTransactional;
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
Assert.notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}public Connection getConnection() throws SQLException {
if (this.connection == null) {
this.openConnection();
}
return this.connection;
}private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
this.connection.commit();
}
}public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
this.connection.rollback();
}
}public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}public Integer getTimeout() throws SQLException {
ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
}
}
openConnection()中的DataSourceUtils.getConnection(this.dataSource)就可以拿到由Spring事务管理器创建的数据库连接。
如果方法没有开启事务,那么在执行sql时候,每个sql有自己的SqlSession对象来执行。
如果开启了Spring事务,就是多个sql属于同一个事务,那应该用一个SqlSession来执行多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession)。
Spring为什么要这样设计?解决线程安全问题,一般情况下不建议使用Mybatis的一级缓存,如果使用,就会涉及到事务的隔离级别,假如设置的隔离级别是读未提交,那么同一条sql查到的结果可能是一样的(从缓存中拿的),把隔离界别忽略了。那数据库的隔离级别重要还是Mybatis的一级缓存重要?肯定是数据库的隔离级别重要。
如果非要使用Mybatis的一级缓存,提供另外一种方法,用@Bean创建一个SqlSession的Bean:
@ComponentScan("com.baec")
@BaecMapperScan("com.baec.mapper")
public class AppConfig {@Autowired
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}@Bean
public SqlSession sqlSession() throws IOException {
return sqlSessionFactory().openSession();
}
这样,在Service中就可以直接使用了:
@Component
public class UserService { @Autowired
private UserMapper userMapper;
@Autowired
private SqlSession sqlSession;
public void test() {
sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
}
}
这样一来,这三个Sql执行使用的SqlSession 就是同一个了,而且是同一个线程。
如果加了@MapperScan注解,会扫描、注入BeanDefinition,Mapper的代理对象Bean,即MapperFactoryBean
public abstract class DaoSupport implements InitializingBean { /** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@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);
}
}
初始化的时候首先会调用checkDaoConfig(),检查SqlSession是否为空,会调到这里:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public SqlSessionDaoSupport() {
}public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}public SqlSession getSqlSession() {
return this.sqlSession;
}protected void checkDaoConfig() {
Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
接下来,会进入到子类MapperFactoryBean.checkDaoConfig():
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 把泛型Class的mapperInterface添加到Mapper中
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}}
把泛型Class
另外,如果不想写@MapperScan注解,还有一种方式可以扫描注入Mybatis生成的代理对象Bean,整合Mybatis:
@ComponentScan("com.baec")
public class AppConfig {@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
// 指定扫描路径
configurer.setBasePackage("com.baec.mapper");
return configurer;
} @Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
} @Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/tuling?characterEncoding=utf-8&
useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("Zhouyu123456***");
return dataSource;
} @Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
return sessionFactoryBean.getObject();
}}
SqlSessionFactory类实现了InitializingBean和FactoryBean接口,它的getObject()方法调用了afterPropertiesSet()方法:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
buildSqlSessionFactory()方法中会构建出Mybatis的核心配置类Configuration,在这里面创建了Spring的事务管理器SpringManagedTransactionFactory工厂,通过该类可以拿到SpringManagedTransaction。
MapperScannerConfigurer实现了BenDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry()方法中创建了扫描器,实现了扫描BeanDefiniiton的功能:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",;
\t\n"));
}
最后,我们总结一下Spring整合Mybatis底层源码执行流程:
- 通过@MapperScan导入了MapperScannerRegistrar类;
- MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions()方法;
- registerBeanDefinitions()方法中定义了ClassPathMapperScanner对象,用来扫描Mapper;
- 设置ClassPathMapperScanner对象可以扫描到接口,因为在Spring中不会扫描接口(因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会扫描@Component注解的类);
- 扫描接口并且得到对应的BeanDefinition;
- 把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType;
- 扫描完成后,Spring就会基于BeanDefinition去创建Bean,相当于每个Mapper对应一个FactoryBean;
- 在MapperFactoryBean中的getObject()方法中,调用了getSqlSession()去得到一个sqlSession对象,然后根据对应的Mapper接口生成一个Mapper接口代理对象,这个代理对象就成为Spring容器中的Bean;
- SqlSession对象属于Mybatis,SqlSession对象需要SqlSessionFactory来产生;
- MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory(),一个setSqlSessionTemplate(),而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要存在SqlSessionFactory类型的Bean或者SqlSessionTemplate类型的Bean;
- 如果你定义的是一个SqlSessionFactory类型的Bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性;
- 而在SqlSessionTemplate类中就存在一个getMapper()方法,这个方法会产生一个Mapper接口代理对象;
- 当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程,执行流程看下图:
文章图片
【Spring源码|Spring源码之整合Mybatis底层实现】
推荐阅读
- #|Mybatis源码分析——插件详解
- mybatis源码学习|Mybatis 源码学习(十二) —— binding 包
- 源码系列|Mybatis源码初探——优雅精良的骨架
- MyBatis|MyBatis官方教程及源码解析——入门
- 数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析
- 前后端分离的好处有哪些()
- JAVA基础|JDK动态代理
- springboot|SpringBoot集成JWT实现token验证
- 全文检索|springboot整合