1.功能架构
Mybatis的功能架构分为三层:
(1)API接口层:提供给外部使用的接口API,比如dao层接口。
(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理、日志,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
2.核心原理:JAVA动态代理
代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。分为动态代理和静态代理。
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
实现方式:
- JDK动态代理实现
效率相对低,被代理类需实现对应的接口
- cglib动态代理实现
效率相对高,生成目标类的子类
public class UserProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
public UserProxy(SqlSession sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + ":" + Arrays.toString(args));
return null;
}
}
public interface UserMapper {
UserInfo getUserById(long id);
}
public class ProxyTest {
public static void main(String[] args) {
UserProxy userProxy = new UserProxy(null, null);
UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
new Class[]{UserMapper.class},
userProxy);
System.out.println(userMapper.getUserById(1l));
}
}
简单查询过程:
public class OrderInfoTest {
static Logger log = LogManager.getLogger(OrderInfoTest.class);
public static void main(String[] args) throws IOException {
// 加载配置文件,并获取session
String resource = "mybatis-config.xml";
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
SqlSession session = sessionFactory.openSession();
// 获取可操作的接口
OrderInfoMapper orderInfoMapper = session.getMapper(OrderInfoMapper.class); // 生成动态代理类
// 执行查询
// OrderInfo orderInfo = session.selectOne("mybatis.study.customer.mapper.OrderInfoMapper.getOrderInfoById", 1L);
OrderInfo orderInfo = orderInfoMapper.getOrderInfoById(1l);
System.out.println(orderInfo.getMoney());
}
}
通过调用DAO接口的方法,与直接使用statementId标识的方式,结果是一致的,中间肯定是做了映射关系。
这就说明了为什么mapper文件中的namespace必须要与dao层接口的全限定名一致。下面看下映射的过程。
3.mapper(dao)到session使用statementId查询的映射过程
3.1 sqlSession创建过程主要类的说明
SqlSessionFactoryBuilder:用于创建SqlSessionFactory的实例,build方法入参配置文件的数据流
SqlSessionFactory是创建SqlSession实例的工厂接口,实现类有两个默认的实现是DefaultSqlSessionFactory,调用openSession获取session对象,进行操作
/** * * 从配置文件获取环境、数据源、事务类型来创建 sqlSession * * @param execType 执行器类型 * @param level 事务级别 * @param autoCommit 是否自动提交 * @return session */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); // 获取配置的环境 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 环境的事务工厂, 默认事务管理ManagedTransactionFactory tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 新建事务对象 final Executor executor = configuration.newExecutor(tx, execType); // 建立执行器 return new DefaultSqlSession(configuration, executor, autoCommit); // 返回默认的sqlSession } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
而SqlSessionManager,实现了SqlSessionFactory和SqlSession接口,直接具有session的功能,内部封装 DefaultSqlSessionFactory,是DefaultSqlSessionFactory的加强版。
总之,通过上述的过程得到可操作的session,其中最重要的就是Configuration的构建,下面说明下Configuration的解析过程
3.2 配置文件的解析
从上面的代码可以看到,配置文件的解析是通过XMLConfigBuilder实现的。
public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); // 解析属性 Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析设置项 loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // 解析别名 pluginElement(root.evalNode("plugins")); // 解析插件 objectFactoryElement(root.evalNode("objectFactory")); // 解析对象工厂 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // objectWrapper工厂,可以对结果进行一些特殊的处理 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 反射工厂 settingsElement(settings); environmentsElement(root.evalNode("environments")); // 解析环境 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析数据库提供厂商 typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置的类型处理器 mapperElement(root.evalNode("mappers")); // 解析sql映射文件 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } }
通过mybatis配置文件和mapper文件的解析,
1.将mapper接口信息,注册到了mapperRegistry, // 以class<T> 和代理工厂注册到mapper库
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
这样,针对session.getMapper(OrderInfoMapper.class)生成动态代理类,就对应上了2.Statement信息注册到了mappedStatements
public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; } }
3.3 调用映射
session获取mapper,调用了Configuration中从mapper库中查询到MapperProxyFactory对象,调用方法,执行MapperProxy中invoke方法
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @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 if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
执行mapperMethod的execute方法
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } 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); } 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; } }
public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } }
4.总结
主要是梳理了下从DAO接口到Mybatis查询的过程,解释了Mybatis采用的动态代理模式,将DAO接口的方法映射到session使用statementId查询的过程,Mybatis源码中涉及了很多工厂类和建造类,可以借鉴。