public class App1 {
public static void main(String[] args) {
// 載入 MyBatis 配置檔案
InputStream is = App1.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 獲取 SqlSession, 代表和資料庫的一次會話, 用完需要關閉
// SqlSession 和 Connection, 都是非執行緒安全的, 每次使用都應該去獲取新的物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 獲取實現介面的代理物件
// UserMapper 並沒有實現類, 但是mybatis會為這個介面生成一個代理物件(將介面和xml繫結)
// 這裡返回的是一個 MapperProxy 代理物件
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
sqlSession.close();
}
}
從上面可以看出,透過SqlSession
來獲取代理類,SqlSession
物件表示MyBaits框架與資料庫建立的會話,我們可以透過SqlSession
例項完成對資料庫的增刪改查操作。SqlSession
是一個介面
// SqlSession.java
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
// 根據介面型別獲取介面對應的代理
<T> T getMapper(Class<T> type);
Connection getConnection();
}
SqlSession
的預設實現為DefaultSqlSession
// DefaultSqlSession.java
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
@Override
public <T> T getMapper(Class<T> type) {
// MyBatis初始化時已經把Mapper介面對應的代理資訊儲存到了 Configuration 中
// 這裡直接從 Configuration 中獲取代理類
// 這裡返回的是一個 MapperProxy
// 找到Configuration類的getMapper方法,該方法比較簡單
// 主要發生如下幾個步驟
// Map<Class<?>, MapperProxyFactory<?>> knownMappers
// 1. 從 Configuration 獲取 Mapper 介面對應的 MapperProxyFactory
// 2. MapperProxyFactory 為 Mapper 介面生成 MapperProxy 代理物件並返回
return configuration.<T>getMapper(type, this);
}
MyBatis中透過MapperProxy
類實現動態代理。下面是MapperProxy類的關鍵程式碼:
// MapperProxy.java
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
// Mapper 介面
private final Class<T> mapperInterface;
// Mapper 介面中的每個方法都會生成一個MapperMethod物件, methodCache維護著他們的對應關係
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 {
// 如果是Object中定義的方法,直接執行。如toString(),hashCode()等
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);
}
// 將 Method 轉換成 MapperMethod 並儲存到 methodCache 中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 這裡面將會執行 Mapper介面對應的 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
// 透過 Configuration 獲取對應的 MappedStatement
// 透過 MappedStatement 獲取 MapperMethod 需要的各種資訊
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxy
使用的是JDK內建的動態代理,實現了InvocationHandler
介面,invoke()方法中為通用的攔截邏輯。當我們呼叫自己Mapper介面中的方法時,其實就是在呼叫MapperProxy
的invoke()方法。
由上面的分析可以知道,透過MapperMethod
來執行代理方法。
public class MapperMethod {
// 裡面有兩個屬性
// name: 要執行的方法名,如com.example.demo.UserMapper.getUserByUserName
// type: SQL標籤的型別 insert update delete select
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;
// 根據insert、update、delete、select呼叫不同的方法
switch (command.getType()) {
case INSERT: {
/**
* args是使用者 Mapper 所傳遞的方法引數列表
* 如果方法只包含一個引數並且不包含命名引數, 則返回傳遞的引數值。
* 如果包含多個引數或包含 @Param 註解修飾的引數,則返回包含名字和對應值的Map物件
*/
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;
}
}
從上面可以看出,資料的增刪改查都是透過SqlSession
來實現的。
MethodSignature
MethodSignature
是MapperMethod
的內部類。MethodSignature
為MapperMethod
類提供了三個作用。
- 獲取待執行方法中的引數和
@Param
註解標註的引數名 - 獲取標註有
@MapKey
的值 - 獲取SELECT操作時必要的標誌位。
public static class MethodSignature {
// 是否多值查詢
private final boolean returnsMany;
// 是否map查詢
private final boolean returnsMap;
// 是否void查詢
private final boolean returnsVoid;
// 是否遊標查詢
private final boolean returnsCursor;
// 返回型別
private final Class<?> returnType;
// 獲取mapKey的值
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
// 引數解析器
private final ParamNameResolver paramNameResolver;
/**
* args是使用者 Mapper 所傳遞的方法引數列表
* 如果方法只包含一個引數並且不包含命名引數, 則返回傳遞的引數值。
* 如果包含多個引數或包含命名引數,則返回包含名字和對應值的Map物件
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
}
ParamNameResolver
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
// 儲存方法引數列表的引數名
// 例如方法:getUser(String userName, Integer age)
// names.get(0) = arg0,names.get(1) = arg1
// 例如方法:getUser(@Param(value = "userName") String userName, Integer age)
// names.get(0) = 'userName',names.get(1) = arg1
// key儲存引數所在位置的下標
// value為引數名,如果存在@Param,則使用@Param註解裡面的值,否則 value = 'arg' + 引數下標索引值
private final SortedMap<Integer, String> names;
// 方法引數列表是否存在 @Param 註解的引數
private boolean hasParamAnnotation;
/**
* args是使用者 Mapper 所傳遞的方法引數列表
* 如果方法只包含一個引數並且不包含命名引數, 則返回傳遞的引數值。
* 如果包含多個引數或包含 @Param 註解修飾的引數,則返回包含名字和對應值的Map物件
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 如果方法引數為空,則直接放回 null
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 如果方法引數沒有使用 @Param 註解修飾的引數,並且只有一個引數
// 這裡直接返回引數值,相當於是返回args[0]
return args[names.firstKey()];
} else {
// 建立一個Map物件
// key 為引數名,value為引數值
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結