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源碼中涉及了很多工廠類和建造類,可以借鑑。