Mybatis插件原理和整合Spring
2021-04-22 21:34
标签:会话 功能 ebean 分表 com rap contains resolve handle 1)实现Interceptor接口 2)实现对应的方法。最关键的是intercept()方法里面是拦截的逻辑,需要增强的代码写在此处。 3)在拦截器类上加上注解。注解签名制定了需要拦截的对象、拦截的方法、参数(因为方法有不同的重载,所以要指定具体的参数)。 插件配置 mybatis-config.xml中中注册插件,配置属性。 插件解析注册 Mybatis启动时扫描 XMLConfigBuilder.pluginElement(); 启动解析的时候,把所有的插件全部存到Configuration的InterceptorChain中,它是一个List。 QA:不修改代码怎么增强功能?多插件怎么拦截? QA:什么对象可以被拦截?那些方法可以被拦截? 这里注意的是,因为Executor有可能被二级缓存装饰,那么是先代理还是装饰,还是先装饰后代理呢? Executor会被拦截到CachingExecutor或者BaseExecutor。 DefaultSqlSessionFactory.openSessionFromDataSource(): 先创建基本类型,在创建二级缓存装饰,最后插件拦截。所以这里拦截的是CachingExecutor。 代理类什么时候创建? 对Executor拦截的代理类是openSession()的时候创建的。 StatementHandler是SimpleExecutor.doQuery()创建的;里面包含了ParameterHandler和ResultSetHandler的创建和代理。 调用interceptorChain的pluginAll()方法。 遍历interceptorChain,使用Interceptor实现类的plugin()方法,对目标核心对象进行代理。 这个plugin返回一个代理对象。JDK动态代理,我们需要写一个实现InvocationHandler接口的触发管理类。然后使用Proxy.newProxyInstance()创建一个代理对象。 这里Mybatis的插件机制提供一个触发管理类Plugin,实现了InvocationHandler。 创建代理对象的newProxyInstance()在这个类进行封装,就是wrap()方法。 在wrap的时候创建了一个Plugin对象,Plugin是被代理对象、Interceptor的一个封装对象: 持有了被代理对象和interceptor的实例: 因为这里是for循环代理,所以某个核心对象有多个插件,会返回被代理多次的代理对象。 被代理之后,调用的流程? ? 在四大核心对象的一次执行过程中(可能被多次代理),因为已经被代理了,所以会触发管理类Plugin的invoke()方法。 如果被拦截的方法不为空,进入Plugin的invoke()方法,调用interceptor的intercept()方法: 到了intercept()方法,也就走到了我们自己实现的拦截逻辑(例如PageInterceptor的intercept()方法)。 其中Invocation,它是对被拦截对象、方法、参数的一个封装。 当然,在执行逻辑完成后,继续执行被代理对象(四大核心对象)的原方法,需要使用method的invoke方法。 拿到被代理的核心对象,继续执行它的方法(例如executor.query())。我们如何拿到被代理对象和参数呢? 这个采用了上面创建的Invocation对象,简化了参数的传递,直接提供了一个proceed()方法。原方法也可写成如下方法: 总结: DefaultSqlSession类select()方法流程 如果对象被代理多次,这里会继续调用下一个插件的逻辑,再走一次Plugin的invoke()方法。这里需要注意多个插件的运行顺序。 配置的顺序和执行的顺序是相反的。interceptorChain的List是按照插件从上往下的顺序解析、添加的。 创建的时候按照list的顺序代理。执行的时候也需要从最后代理的对象开始。 总结: 引入pageHelper的依赖,配置插件,如果需要分页,需要用到相关的工具类: 插件的优点就是不用修改Mybatis本身的代码。 QA:SQL改写的实现?PageHelper实现分页的原理? 首先看一下拦截器,PageInterceptor类。 首先判断是否需要count获取总数,默认是true。获得count之后,判断是否需要分页,如果pageSize > 0,就分页。 这里通过getPageSql()方法生成了一个新的BoundSql: getPageSql()对于不同的数据库有不同的实现: 以MYSQL为例,实际上是添加了LIMIT语句,加上起始位置和结束位置。 那么插件是怎么获取到页码和每页数量的,是怎么传递给插件的? ? 这个在PageHelper.startPage()方法可以找到答案。startPage()调用了PageMethod的setLocalPage()方法,包装了一个Page对象,并把这个对象放到ThreadLocal变量中。 而在AbstractHelperDialect中,Page对象中的翻页信息使用过getLocalPage()取出来的: 它调用的正式PageHelper的getLocalPage(),从ThreadLocal中获取到了翻页信息。 所以每次查询都会有一个线程私有的Page对象,它里面有页码和每页数量。 关键类: pom依赖 SqlSessionFactoryBean MapperScannerConfigurer 第一种配置一个MapperSacnnerConfigurer. 第二种配置一个 采用注解的方式@MapperScan注解,比如在Spring Boot的启动类上加上一个注解: 这三种效果都是一样的。 经过这两步(SqlSessionFactoryBean + MapperScannerConfigurer)配置以后,Mapper就可以注入到Service层了,Mybatis其他的代码和配置不需要进行任何的改动。 它是如何实现的呢? 只要我们理解了SqlSessionFactory、sqlSession、MapperProxy这三个对象怎么创建的,就理解了Spring继承Mybatis的原因。 1)SqlSessionFactory在哪里创建的。 2)SqlSession在哪里创建的。 3)代理类在哪里创建的。 在springboot需要自己实现: sqlSessionFactoryBean的内容: 他实现了三个接口:FactoryBean、InitializingBean、ApplicationListener InitializingBean 实现了InitializingBean接口,所以要实现afterPRopertiesSet()方法,这个方法会在bean的属性值设置完的时候被调用。 在afterPropertiesSet()方法里面,通过一些检查之后,调用buildSqlSessionFactory()方法。 这里创建了一个Configuration对象,叫做targetConfiguration。还创建了一个用来解析全局配置文件的XMLConfigBuilder。 判断configuration对象是否已经存在,也就是判断是否解析过。如果已经有对象,就覆盖一下属性。 如果Configuration不存在,但配置了configLocation属性,就根据mybatis-config.xml的文件路径,构建了一个xmlConfigBuilder对象。 如果Configuration不存在,configLocation路径也没有,只能使用默认属性去构建去给configurationProperties赋值。 一是把增删改查标签注册成MapperStatement对象。第二个是把接口和对应的MapperProxyFactoty工厂类注册到MapperRegistry中。 最后返回一个DefaultSqlSessionFactory。 总结 ? 通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面的有个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用。Spring在启动初始化这个bean的时候,完成了解析和工厂类的创建工作。 FactoryBean 这个类作用是让用户可以自定义实例化bean的逻辑。如果从BeanFactory中根据Bean的ID获取一个bean,它获取的其实是FactoryBean的getObject()返回的对象。 ApplicationListener让SqlSessionFactoryBean有能力监控应用发出的一些事件通知。 比如这里监听了ContextRefreshListener(上下文刷新事件),会在Spring容器加载完之后执行。 这里是检查ms是否加载完毕。 SqlSessionFactoryBean用到的Spring扩展点总结: 为什么不直接使用DefaultSqlSession? 因为它是线程不安全的。 又会比较麻烦。 SqlSessionTemplate虽然和DefaultSqlSession一样定义了数据操作的接口,但是没有自己的实现,全部调用了一个代理对象的方法。 那么,这个代理独享怎么来的?在构造方法里面通过JDK动态代理创建: 它是对SqlSession实现类DefaultSqlSession的代理。既然是JDK动态代理,那对代理类任意方法的调用都会走到(第三个参数)实现了InvocationHandler接口的触发管理类SqlSessionInterceptor的invoke()方法。 SqlSessionInterceptor是一个内部类 ? 这里会用getSqlSession()方法创建一个SqlSession对象,把SqlSessionFactory、执行器类型、异常解析器传进去。 获取到sqlSession实例(DefaultSqlSession)后,在调用它的增删改查方法。 总结 ? 怎么拿到一个SqlSessionTemplate是线程安全的,可以替换DefaultSqlSession,那么在Dao层是怎么拿到SqlSessionTemplate呢? ? 可以使用new一个创建的方式,但它有三个重载的构造函数。而且这个单例的SqlSessionTemplate必须存起来放在一个地方,可以在任何需要代替DefaultSqlSession的地方都可以拿到,不能重复创建,否则就不是单例了。 因为需要存在一个地方,所以,我们是不是可以提供一个工具类来获取单例的SqlSessionTemplate呢? ? Mybatis里面和Hibernate也是一样的,它提供了一个抽象的支持类SqlSessionDaoSupport(这里Hibernate使用HibernateDaoSupport)。 SqlSessionDaoSupport类中持有一个SqlSessionTemplate对象,并且提供了一个getSqlSession()方法,让我们获得一个SqlSessionTemplate。 ? 也就是说让我们Dao层继承抽象类SqlSessionDaoSupport,就自动拥有了getSqlSession()方法。调用getSqlSession()就能拿到共享的SqlSessionTemplate。 但Dao执行SQL格式还是不够简洁。 ? 所以我们需要先创建一个BaseDao继承SqlSessionDaoSupport。在BaseDao里面封装对数据库的操作,包括selectOne()、 ? 然后让我们Dao层继承BaseDao实现Mapper接口。在实现类加上@Repository注解就可以了。但这样还是比较麻烦的还需要实现DaoImpl。 那有没有更好的方式呢? ? 我们通过上面的方式操作数据库,繁琐,而且还会出现Statement ID的硬编码问题。没有使用到JDK动态代理。那么如何解决呢? 当我们使用Spring来调用Mybatis的时候。只需要注入一个Mapper就可以使用,那么是怎么实现的? 首先Spring可以通过配置或者是注解来扫描Mapper的接口。 其中当使用xml的时候,需要配置: 其中MapperScannerConfigurer是来做mapper的扫描的,由上面类图可以看出: MapperScannerConfigurer重写了postProcessBeanDefinitionRegistry(),那他实现了什么功能呢? ? 在这个方法里面:创建了一个scanner对象,然后设置属性。 ClassPathBeanDefinitionScanner的scan(); 这里会调用它的子类方法ClassPathMapperScanner的doScan()方法: ? 子类ClassPathMapperScanner又调用了父类ClassPathBeanDefinitionScanner的doScan()扫描所有的接口,把接口全部添加到beanDefinitions中。 ? processBeanDefinitions()方法里面,在注册beanDefinitions的时候。BeanClass被改为MapperFactoryBean。(这里有注释讲解) ? 这就是说,所有的Mapper接口,在容器里面都被注册成一个支持泛型的MapperFactoryBean了。 ? 为什么要注册成它呢?那注入使用的时候,也是这个对象,这个对象有什么作用呢? MapperFactoryBean这个类: ? 这个类继承了抽象类SqlSessionDaoSupport,这就解决了我们的第一个问题,现在每一个注入Mapper的地方,都可以拿到SqlSessionTemplate。 ? 那有没有使用到MapperProxy呢?如果注册的是MapperFactoryBean,难道注入使用的也是MapperFactoryBean吗?但这个类并不是代理类。 ? 所以注入的是一个什么对象呢?这里MapperFactoryBean也实现了FactoryBean。它可以在getObject()中获取Bean实例的行为。 ? 它并没有直接返回一个MapperFactoryBean。而是调用了SqlSessionTemplate的getMapper()方法。SqlSessionTemplate的本质是一个代理,所以它最终会调用DefaultSqlSession的getMapper()方法,最后返回的还是一个JDK的动态代理。 Mybatis插件原理和整合Spring 标签:会话 功能 ebean 分表 com rap contains resolve handle 原文地址:https://www.cnblogs.com/snail-gao/p/13275400.html插件编写要求(分页插件PageHelper)
自定义插件需要做到三点
public class PageInterceptor implements Interceptor{}
@Override
public Object intercept(Invocation invocation) throws Throwable {
return null;
}
@Override
public Object plugin(Object o) {
return null;
}
@Override
public void setProperties(Properties properties) {
}
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
1.采用的是代理模式,这个也是MyBatis插件的实现原理。
2.插件是层层拦截,我们用到另一种设计模式--责任链模式。
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
插件实现原理
Executor executor = configuration.newExecutor(tx, execType);
代理怎么创建?
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
default Object plugin(Object target) {
//实现代理对象
}
public static Object wrap(Object target, Interceptor interceptor) {
Map
new Plugin(target, interceptor, signatureMap)
private Plugin(Object target, Interceptor interceptor, Map
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set
return interceptor.intercept(new Invocation(target, method, args));
method.invoke(target, args);
return invocation.proceed();
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
配置的顺序和执行的顺序?
PageHelper原理
PageHelper.startPage(pn, 10); //pageNumber, pageSize,第几页,每页几条
List
String pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getPageSize());
} else {
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getStartRow());
sqlBuilder.append(",");
sqlBuilder.append(page.getPageSize());
pageKey.update(page.getStartRow());
}
pageKey.update(page.getPageSize());
return sqlBuilder.toString();
}
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
Page page = this.getLocalPage();
return this.getPageSql(sql, page, pageKey);
}
public static
使用场景
作用
描述
实现方式
水平分表
可以进行水平分表的查询
对query update进行拦截,在接口上添加注解,通过反射获取接口注解,根据主键上的配置进行分表,修改原SQL
数据脱敏
手机号和身份证在数据库完整存储,屏蔽手机号的中间四位。
query--对结果集脱敏
菜单权限控制
不同的用户登录,查询菜单权限表时获得不同的结果,在签单展示不同的菜单
对query方法进行拦截,在方法上添加注解,根据权限配置,以及用户登录信息,在SQL上加上权限过滤条件
整合Spring
关键配置
@MapperScan("com.xx.dao")
创建会话工厂SqlSessionFactory
@Configuration
public class SqlSessionConfig {
private Logger logger = LoggerFactory.getLogger(SqlSessionConfig.class);
@Value("${spring.datasource.jndi-name}")
private String dataSourceJndiName;
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Bean
public SqlSessionFactoryBean createSqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = null;
try {
// 加载JNDI配置
Context context = new InitialContext();
DataSource dataSource = (DataSource)context.lookup(dataSourceJndiName);
// 实例SessionFactory
sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 配置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
// 加载MyBatis配置文件
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
// 能加载多个,所以可以配置通配符(如:classpath*:mapper/**/*.xml)
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources(mapperLocations));
// 配置mybatis的config文件
sqlSessionFactoryBean.setConfigLocation("mybatis-config.xml");
} catch (Exception e) {
logger.error("创建SqlSession连接工厂错误:{}", e);
}
return sqlSessionFactoryBean;
}
}
spring:
# db
datasource:
jndi-name: ‘java:comp/env/jdbc/spring_db‘
# mybatis config
mybatis:
mapper-locations: classpath*:mapper/**/*.xml
public class SqlSessionFactoryBean implements FactoryBean
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property ‘dataSource‘ is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property ‘sqlSessionFactoryBuilder‘ is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null
|| this.configLocation == null, "Property ‘configuration‘ and ‘configLocation‘ can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
XMLConfigBuilder xmlConfigBuilder = null;
Configuration targetConfiguration;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
}
else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
}
else {
LOGGER.debug(
() -> "Property ‘configuration‘ or ‘configLocation‘ not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
创建一个用来解析Mapper.xml的XMLMapperBuilder,调用了它的parse()方法。这个步骤我们主要是做了两件事情,
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(),
targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
创建会话SqlSession
Note that this class is not Thread-Safe.
所以,在Spring里面,我们要保证SqlSession实例的线程安全,必须为每一次请求创建一个sqlSession。但是每一次请求用openSession()自己去创建,
在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来代替SqlSession,这个类就是SqlSessionTemplate。
因为它是线程安全的,所以可以在所有的Dao层共享一个实例(默认是单例的)。Thread safe, Spring managed,
public
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },
new SqlSessionInterceptor());
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
...
}
}
因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理类,也实现SqlSession,提供了跟DefaultSqlSession实例,
在调用被代理对象的相应方法。
和JdbcTemplate、RedisTemplate一样,SqlSessionTemplate可以简化Mybatis在Spring中的使用,也是Spring和Mybatis整合的最关键的一个类。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
//其他代码省略
}
getSqlSession.selectOne(statement, parameter);
selectList()、insert()、delete()这些方法,子类就可以直接调用。public class BaseDao extends SqlSessionDaoSupport {
//使用sqlSessionFactory
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
/**
* 获取Object对象
*
* @param statement
* @return
*/
public Object selectOne(String statement) {
return getSqlSession().selectOne(statement);
}
public Object selectOne(String statement, Object parameter) {
return getSqlSession().selectOne(statement, parameter);
}
//其他代码省略
}
接口的扫描注册
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。
BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子类,里面有一个postProcessBeanDefinitionRegistry()方法。
实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义。Spring创建Bean之前会调用这个方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
public Set
private void processBeanDefinitions(Set
public class MapperFactoryBean
接口注入使用
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
总结
1.提供了SqlSession的代替品SqlSessionTemplate,里面有一个实现了InvocationHandler的内部SqlSessionIntercepter,本质是对SqlSession的代理。
2.提供了获取SqlSessionTempldate的抽象类SqlSessionDaoSupport。
3.扫描Mapper接口,注册到容器中的是MapperFactoryBean,它继承了SqlSessionDaoSupport,可以获得SqlSessionTempldate。
4.把Mapper注入使用的时候,调用的是getObject()方法,它实际上是调用了SqlSessionTemplate的getMapper()方法,注入了一个人JDK动态代理对象。
5.执行Mapper接口的任意操作,会走到触发管理类MapperProxy,进入SQL处理流程。
Mybatis调用的设计模式总结
上一篇:java 生成指定范围内随机数
下一篇:C++内存泄露及常见情况总结
文章标题:Mybatis插件原理和整合Spring
文章链接:http://soscw.com/index.php/essay/78251.html