PageHelper插件相关注解说明
| 注解名称 | 注解说明 |
|---|---|
@Configuration | 相当于<beans></beans>标签 |
@ConditionalOnBean(SqlSessionFactory.class) | 只有在上下文中存在SqlSessionFactory的bean是才会运行PageHelperAutoConfiguration |
@EnableConfigurationProperties(PageHelperProperties.class) | @ConfigurationProperties注解主要用来把properties配置文件转化为bean来使用的,而@EnableConfigurationProperties注解的作用是@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的。 |
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX) | PageHelperProperties是PageHelper的配置类,将properties中的配置文件转换成PageHelperProperties对象,PageHelperProperties上有@ConfigurationProperties(prefix = "PageHelperProperties.PAGEHELPER_PREFIX")注解。 |
@AutoConfigureAfter(MybatisAutoConfiguration.class) | 当MybatisAutoConfiguration这个类加载完成后再加载本类 |
@PostConstruct | @PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法) |
| @Intercepts({@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) | type:表示拦截的类,这里是Excutor的实现类method:表示拦截的方法,这里拦截的是Executor的query方法args:表示方法参数 |
Mybatis拦截器原理分析
Mybatis提供的插件(Plugin)功能其实就是拦截器功能,默认情况下,Mybatis允许使用插件来拦截方法的调用包括一下几种:
1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) //拦截执行器的方法
2. ParameterHandler (getParameterObject, setParameters) //拦截参数的方法
3. ResultSetHandler (handleResultSets, handleOutputParameters) //拦截结果集的方法
4. StatementHandler (prepare, parameterize, batch, update, query) //拦截Sql语法构建的处理
PageHelper自动配置
pageHelper的自动配置类名:PageHelperAutoConfiguration
@Configuration
@ConditionalOnBean(SqlSessionFactory.class)
@EnableConfigurationProperties(PageHelperProperties.class)
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class PageHelperAutoConfiguration {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Autowired
private PageHelperProperties properties;
/**
* 接受分页插件额外的属性
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
public Properties pageHelperProperties() {
return new Properties();
}
@PostConstruct
public void addPageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
//先把一般方式配置的属性放进去
properties.putAll(pageHelperProperties());
//在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
properties.putAll(this.properties.getProperties());
interceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
}
在执行完自动配置的构造方法后,会执行addPageInterceptor方法,配置属性的注入,并将PageInterceptor实例添加到Configuration的实例的interceptorChain中
Mybatis构件相互关系
| 核心构件 | 构件说明 |
|---|---|
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement 所需要的参数, |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条<select|update|delete|insert>节点的封装 |
SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
Configuration | MyBatis所有的配置信息都维持在Configuration对象之中。 |
Mybatis执行流程说明
public static void main(String[] args) throws Exception {
/*
* 1.加载mybatis的配置文件,初始化mybatis,创建出SqlSessionFactory,是创建SqlSession的工厂
* 这里只是为了演示的需要,SqlSessionFactory临时创建出来,在实际的使用中,SqlSessionFactory只需要创建一次,当作单例来使用
*/
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
//2. 从SqlSession工厂 SqlSessionFactory中创建一个SqlSession,进行数据库操作
SqlSession sqlSession = factory.openSession();
}
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
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);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
通过代码可以看出SqlSession是由SqlSessionFactory的oppenSession()方法来生成的,生成会话的过程会通过Configuration类的newExecutor方法创建一个执行器。
Mybatis执行器Executor根据SqlSession传递的参数执行query方法,经过一系列的转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集
StatementHandler主要完成以下两个工作:
- 对于
JDBC的PreparedStatement类型的对象,创建过程中,我们使用的是SQL语句字符串会包含若干个?占位符,我们其后再对占位符进行设值。 StatementHandler通过List<E> query(Statement statement, ResultHandler resultHandler)方法来完成执行Statement,和将Statement对象返回的resultSet封装成List;
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
从Mybatis的SQL执行流程图中可以Mybatis的四大对象Executor、ParameterHandler、ResultSetHandler、StatementHandler,由他们一起合作完成SQL的执行,那么这四大对象是由谁创建的呢?没错,就是Mybatis的配置中心:Configuration,创建源代码如上:
这些方法最终都会调用interceptorChain.pluginAll方法,这个方法引用的Plugin的wrap方法
public static Object wrap(Object target, Interceptor interceptor) {
// 获取PageInterceptor的Intercepts注解中@Signature的method,存放到Plugin的signatureMap中
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标对象实现的全部接口;四大对象是接口,都有默认的子类实现
// JDK的动态代理只支持接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
pluginAll其实就是给四大对象创建代理,一个Interceptor就会创建一层代理,而我们的PageInterceptor只是其中一层代理;我们接着往下看,Plugin继承了InvocationHandler,JDK的动态代理,那么它的invoke方法肯定会被调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取PageInterceptor的Intercepts注解中@Signature的method
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 当methods包含目标方法时,调用PageInterceptor的intercept方法完成SQL的分页处理
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
从代码中可以看出,最后调用了PageInterceptor拦截器的intercept方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if(args.length == 4){
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
// 此处会从当前线程取Page信息,还记得什么时候在哪将Page信息放进当前线程的吗?
if (!dialect.skip(ms, parameter, rowBounds)) {
//反射获取动态参数
String msId = ms.getId();
Configuration configuration = ms.getConfiguration();
Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
String countMsId = msId + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
if(countMs != null){
count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
//处理查询总数
//返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
}
其中会读取当前线程中的Page信息,根据Page信息来断定是否需要分页;而Page信息就是从我们的业务代码中存放到当前线程的.