您的位置:首页 > 其它

【Mybatis学习】查询映射过程解析

2017-12-10 15:53 609 查看


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;
}
103dc
}

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信息注册到了mappedStatementspublic 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源码中涉及了很多工厂类和建造类,可以借鉴。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: