MyBatis Mapper in principle to use interface
MyBatis 3 Mapper interface recommended way to perform xml configuration of SQL, it is easy to use, and very flexible. In the remainder of convenience, I would like to know how this is achieved, before also generally know by the JDK dynamic proxy to do, but this time wanted to know the details.
The more things more complex, so with a simple rely only MyBatis CRUD 3.4.0 to develop an understanding of call Mapper interface.
Typically xml configuration file created by SqlSessionFactory
the object, and then obtain SqlSession
the object, and then obtain an interface proxy object Mapper custom last method call interface, the following example:
/**
*
* @author xi
* @date 2018/10/01 14:12
*/
public class Demo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
System.out.println(role);
}
}
How to parse the configuration file, create a factory, get this session are not the focus, you can look directly at the following line:
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Custom acquisition method Mapper proxy object is located at: org.apache.ibatis.session.SqlSession#getMapper
, or a generic method
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
Subclasses implement this method are: DefaultSqlSession
and SqlSessionManager
, where attention to the default implementation:org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
There appeared Configuration object, the object is simply contains the xml configuration parsing content, it is not now the same focus, continue to follow up down:org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Here appeared MapperRegistry
objects, it parses the content of Mapper.xml ( mapper
tag namespace
contains the fully qualified name of the interface Mapper) come, HashMap comprising a member variable org.apache.ibatis.binding.MapperRegistry#knownMappers
, key is Mapper interface Class
objects, value is org.apache.ibatis.binding.MapperProxyFactory
, can be seen from the name is used to create a proxy object factory Mapper interface will be used later.
In particular this knownMappers
is how to fill, as detailed org.apache.ibatis.binding.MapperRegistry#addMapper
method, regardless of being first to go down:org.apache.ibatis.binding.MapperRegistry#getMapper
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
Depending on the type Mapper interface, from the knownMappers
get corresponding plant, and then create a proxy object, continue to follow up:org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
Here again there is a MapperProxy
target, it should be understood that a proxy object, opened it implements the java.lang.reflect.InvocationHandler
interface, which is trying to sell wow.
Do not look at the dog, continue to follow up:org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
In this, I see the familiar java.lang.reflect.Proxy
, here mapperInterface
is passed when creating the factory Mapper interface. The real proxy object Mapper interface this time to produce, is really a sheep's head.
This is not both familiar and unfamiliar JDK dynamic proxy What, are familiar with it, because in front of the dog mapperProxy
is an InvocationHandler
object that intercepts all calls to the proxy object interface methods. It is strange because the use of concrete subclasses will implement the interface when JDK dynamic proxies before, did not see here. Then take a look at org.apache.ibatis.binding.MapperProxy
:
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
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 {
// 判断 method 是不是 Object 类的方法,如 hashCode()、toString()
if (Object.class.equals(method.getDeclaringClass())) {
try {// 如果是,则调用当前 MapperProxy 对象的这些方法
// 跟 Mapper 接口的代理对象没关系
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 到这了,说明调用的是接口中的方法,具体的执行就不是本次关注的重点了
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
// 对 MapperMethod 做了缓存,这个 methodCache 是个 ConcurrentHashMap,在 MapperProxyFactory 中创建的
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;
}
}
Specific instructions are inside code comments, nothing to say.
to sum up
- By JDK dynamic proxy mode, create a proxy object Mapper interface, intercept calls to interface methods;
- Mapper interfaces can not be used in heavy-duty, see specific reasons
org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
, may depend on itorg.apache.ibatis.session.Configuration#mappedStatements
.
Finally, the last, good old saying: failing to decide, firstOpen big dragon(See source code) (escape