1、mybatis扩展点plugins
mybatis的扩展是通过拦截器Interceptor来实现的,本质上就是JDK的动态代理,所以它只能对接口进行拦截,mybatis可以对以下四个接口类型进行拦截,也就是说会对这4种对象进行代理,所有的代理拦截都是通过 InterceptorChain.pluginAll(Object target) 来实现的。
Executor: 执行器,执行SQL语句(所有的sql都通过它来执行),并且对事务、缓存等提供统一接口。(在这一层上做拦截的权限会更大)
StatementHandler: 对statement进行预处理,并且提供统一的原子的增、删、改、查接口。(如果要在SQL执行前进行拦截的话,拦截这里就可以了)
ResultSetHandler:对返回结果ResultSet进行处理。
PameterHandler:对参数进行赋值。
SqlSession的创建过程:
mybatis中的SQL都是通过DefaultSqlSession去执行的,其创建过程如下:
// SqlSessionFactoryBuilder => DefaultSqlSessionFactory => DefaultSqlSession
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession();
2、源码解读具体实现(以Executor接口为例)
2.1、创建SqlSession时,SqlSessionFactroy会解析mybatis.xml配置文件中的plugins标签,并将Interceptor属性定义的Interceptor放到interceptorChain中;
// SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析mybatis.xml配置文件,并创建DefaultSqlSessionFactory
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.
}
}
}
// XMLConfigBuilder.java
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 解析mybatis.xml中的各个标签
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 解析plugins标签,并把Interceptor放到interceptorChain中
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).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
// Configuration,mybatis文件的抽象类
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
2.2、DefaultSqlSessionFactory.openSession()时使用JDK动态代理生成@Signature注解指定的被代理类(包含代理的方法以及方法参数)
// DefaultSqlSessionFactory.java
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 使用Configuration创建Executor
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// Configuration.java
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
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, autoCommit);
}
// 使用JDK动态代理生成Executor的interceptorChain
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.3、InterceptorChain生成的具体过程
// InterceptorChain.java
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
// Interceptor的具体实现类(即我们业务上要实现的功能)
@Override
public Object plugin(Object arg0) {
return Plugin.wrap(arg0, this);
}
// Plugin.java
public static Object wrap(Object target, Interceptor interceptor) {
// getSignatureMap获取Interceptor类上的@Intercepts(@Signature)内容
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 生成目标类target(Executor.class)的代理类,实现我们需要的plugin功能
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
// 解析实现Interceptor接口的类上定义的@Intercepts(@Signature)内容,获取需要拦截的类和方法。
// 例如:@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) { // issue #251
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
// sig.type()即Executor.class
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
3、demo关键步骤
3.1、实现自定义的Interceptor
// 自定义拦截器
@Intercepts({@Signature(type = Executor.class, method = "query",
args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyTestInterceptor implements Interceptor {
private static final String MSG = "octopus route table info is not exit!";
@Override
public Object intercept(Invocation arg0) throws Throwable {
Object obj = null;
try {
obj = arg0.proceed();
} catch (Throwable e) {
if (e.getCause() instanceof MySQLSyntaxErrorException) {
MySQLSyntaxErrorException ex = (MySQLSyntaxErrorException) e.getCause();
System.out.println("====" + ex.getErrorCode());
System.out.println("====" + ex.getSQLState());
System.out.println("====" + ex.getMessage());
System.out.println("====" + ex.getCause());
if (MSG.equals(ex.getMessage())) {
throw new RouteTableNoExistException();
}
}
}
return obj;
}
@Override
public Object plugin(Object arg0) {
return Plugin.wrap(arg0, this);
}
@Override
public void setProperties(Properties arg0) {
System.out.println("env value: " + arg0.getProperty("names"));
}
}
3.2、在mybatis.xml中配置plugins
<configuration>
<plugins>
<plugin interceptor="com.pinganfu.interceptor.MyTestInterceptor" />
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="MANAGED">
<property name="closeConnection" value="false" />
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${jdbcUrl}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/TBATMapper.xml" />
</mappers>
</configuration>
3.3、获取SqlSession
Properties pro = new Properties();
try {
pro.load(Resources.getResourceAsStream("jdbc.properties"));
// 加载mybatis.xml中的plugins
InputStream in = Resources.getResourceAsStream("mybatis.xml");
sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession();
} catch (IOException e) {
e.printStackTrace();
}
4、mybatis针对各种异常的处理
mybatis通过DefaultSqlSession执行时,会将发生的所有异常统一包装成PersistenceException再抛出,我们可以通过PersistenceException.getCause()获取具体的异常。
// DefaultSqlSession.java
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
// 对执行发生的所有Exception进行wrap之后再抛出
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// ExceptionFactory.java
public static RuntimeException wrapException(String message, Exception e) {
// 将Exception进行统一包装成PersistenceException
return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
}