本章疑問:
// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public interface UserMapper {
public UserEntity getUser(int id);
}
爲什麼UserMapper是接口,沒用實現類,那麼他是怎麼初始化的?getMapper()方法爲什麼可以調用?
mapper接口是怎麼初始化的?是反射?不是的,接口是不能反射初始化。揭祕:其實是代理設計模式【動態代理】,底層使用AOP實現。
另外MyBayis中最重要的是SqlSession:操縱SQL語句。
思考問題:動態代理分爲:jdk動態代理和CGLIB動態代理,那麼Mybatis使用了那種代理設計模式?
答案:MyBatis採用的jdk動態代理,因爲代理的是接口。
回顧jdk動態代理
JDK動態代理的一般步驟如下:
1.創建被代理的接口和類;
2.實現InvocationHandler接口,對目標接口中聲明的所有方法進行統一處理;
3.調用Proxy的靜態方法,創建代理類並生成相應的代理對象;
代碼實現jdk動態代理:
/**
* 1.創建被代理的接口和類;
*/
public interface OrderService {
public String add();
}
public class OrderServiceImpl implements OrderService {
public String add() {
System.out.println("OrderServiceImpl add。。。");
return "success";
}
}
/**
* 2.實現InvocationHandler接口,對目標接口中聲明的所有方法進行統一處理;
*/
public class JdkMapperProxy implements InvocationHandler {
//目標對象,被代理對象
private Object targect;
public JdkMapperProxy(Object targect){
this.targect=targect;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置通知...在代理方法之前處理");
//目標方法,目標方法參數
Object result = method.invoke(targect, args);//被執行目標方法,被代理的方法
System.out.println("後置通知...在代理方法之後處理");
return null;
}
}
/**
* 3.調用Proxy的靜態方法,創建代理類並生成相應的代理對象;
*/
public class TestMybatis02 {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()
, OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl()));
orderService.add();
}
}
運行TestMybatis02 結果如下:
前置通知...在代理方法之前處理
OrderServiceImpl add。。。
後置通知...在代理方法之後處理
生成的代理類
通過反編譯工具查看生成的代理類,可知,代理類實現了OrderService被代理類接口,add()方法中,調用h.invoke()方法,其中this.h指的是InvocationHandler,本質就是調用下面的這個方法
回顧了下jdk動態代理,下面我們開始源碼分析
思考問題:會不會把下面這段配置轉爲實體類
<select id="getUser" parameterType="int"
resultType="com.mayikt.entity.UserEntity">
select * from user where id=#{id}
</select>
答案是肯定的,在那裏進行解析的呢?下面開始分析源碼:下面就是解析的地方
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
....
//進入這裏
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
}
}
重點這段代碼:
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
//會進入到這裏
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
//進入到這裏
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
....
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
//最終到這裏了
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
if (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
.....
//進入這裏
this.configuration.addMappedStatement(statement);
return statement;
}
}
public void addMappedStatement(MappedStatement ms) {
//最終結果
this.mappedStatements.put(ms.getId(), ms);
}
protected final Map<String, MappedStatement> mappedStatements;
this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
protected static class StrictMap<V> extends HashMap<String, V> {
通過上面的代碼執行流程,最終我們知道,mapper.xml中的配置文件裏的每條sql語句是如何轉化爲對象保存起來的。最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。
通過源碼可知:HadhMap被put了兩次
後面我們來分析getMapper()方法:默認走的是DefaultSqlSession
// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
由上面代碼可知:通過configuration.getMapper()去查下我們之前有沒有註冊過mapper接口,沒有則會報:沒用綁定接口錯誤。
再看看上篇文章中介紹的mapperRegistery裏面的東西:存放的是mapper接口,key爲:接口,value爲:MapperProxyFactory
這裏我們mapper接口註冊過,會進入else分支的這段代碼:使用mapperProxyFactory創建代理類:
return mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
對比:mybatis的jdk動態代理和我們自己實現的jdk動態代理:
public class MapperProxy<T> implements InvocationHandler, Serializable {//mybatis的實現
public class JdkMapperProxy implements InvocationHandler {//我們的實現
protected T newInstance(MapperProxy<T> mapperProxy) {//mybatis的實現
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()//我們的實現
, OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl()));
最後返回mapper信息如下:mapper爲:我們通過:mapperProxyFactory創建的代理類MapperProxy
所以當我們調用mapper的getUser()方法時候,就會執行MapperProxy代理類的invoke()方法
UserEntity user = mapper.getUser(2);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //判斷mapper接口有沒有實現類,顯然我們mapper沒用實現類
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else { //會執行這個分支
MapperMethod mapperMethod = this.cachedMapperMethod(method); //緩存中獲取method
return mapperMethod.execute(this.sqlSession, args); //執行sql語句
}
}
思考問題:Mybatis裏面,mapper接口中有多個方法,每次調用會走同一個invoke()方法嗎?
答案:不會的,因爲你的每個MapperRegistry裏面的class爲mapper接口,都有獨立的MapperProxyFactory,因爲MapperRegistry中key存放的是mapper接口,value爲MapperProxyFactory。
我們使用MapperProxyFactory創建MapperProxy去創建的代理,所以每次調用getMapper()方法取到同一個mapper則會走同一個invoke()方法,反過來每次調用mapper時候,就會走不同invoke()方法。
一般我們把Mapper接口定義爲全局,則會走同一個invoke()方法,除非設=設置爲多例,就每次都會new 不同,走不同invoke()方法。
Mybatis是基於多個不同的mapper接口生產的代理類,不同的mapper接口走不同的invoke方法,如果是相同的mapper接口,不同的方法,肯定是走同一個invoke方法。
那麼就有問題了,多個不同mapper接口會產生多個代理類( new MapperProxy()),佔太多的內存,後面會詳解。
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
上面我們把mapper接口看完了,執行 mapper.getUser(2) 會走invoke(),下面看invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
//進入這裏
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); //去緩存中查看是否有method,我們這裏是沒用的
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); //會走到這裏
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}
先看下這塊
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
public enum SqlCommandType {
UNKNOWN,
INSERT,
UPDATE,
DELETE,
SELECT,
FLUSH;
SqlCommandType 是和sql語句相關的
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {//進入這裏
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) {
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
this.name = null;
this.type = SqlCommandType.FLUSH;
} else { //ms不爲null,則執行到這裏
this.name = ms.getId();
this.type = ms.getSqlCommandType();
if (this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}
configuration.hasStatement(statementName)
public boolean hasStatement(String statementName) {
return this.hasStatement(statementName, true);
}
getId()爲namespace+id
將mapper.xml裏面配置的sql語句和對應的mapper接口方法進行關聯並放入map緩存中,後期直接走緩存了。最後執行execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else if (SqlCommandType.SELECT == this.command.getType()) { //select類型走這裏
if (this.method.returnsVoid() && this.method.hasResultHandler()) { //判斷方法是否沒用返回結果的,不是
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) { //判斷返回結果是不是返回多個結果集,不是
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) { //是否返回map集合?不是
result = this.executeForMap(sqlSession, args);
} else { //所以走這裏
param = this.method.convertArgsToSqlCommandParam(args); //轉換參數
result = sqlSession.selectOne(this.command.getName(), param); //重點在這:selectOne()
}
} else {
if (SqlCommandType.FLUSH != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
result = sqlSession.flushStatements();
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
通過源碼我們可以改成下面這樣:selectOne(),後面我們針對selectOne()進行源碼分析
//UserEntity user = mapper.getUser(2);
sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser",2);
總結:
MybatisMapper接口綁定原理分析流程
1、mapper.xml中的配置文件裏的每條sql語句,最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。
2、執行getMapper()方法,判斷是否註冊過mapper接口,註冊了就會使用mapperProxyFactory去生成代理類MapperProxy
3、執行目標方法時,會調用MapperProxy代理類的invoke()方法
4、將mapper.xml裏面配置的sql語句和對應的mapper接口方法進行關聯並放入map緩存中,後期直接走緩存了。最後執行execute()方法
5、執行execute()方法最終調用selectOne()方法,執行結果。
本文參考