欢迎大家来到IT世界,在知识的湖畔探索吧!
大纲
- Mapper 接口的注册过程
- MapperStatement 对象创建过程
- Mapper 接口方法的调用过程(动态代理)
- SqlSession 执行 Mapper的过程
Mapper 接口的注册过程
Mapper 由两部分组成: Mapper.java 接口 和 Mapper.xml sql 配置文件
Mapper 接口用于定义执行 SQL 语句相关的方法,方法名一般和 Mapper.xml <select|update|insert|delete> 标签的 id 属性相同
- mapper 调用代码示例
// 提前编写好 mybatis-config.xml
String resource = "javabus-mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
// 1 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 2 创建会话 SqlSession
SqlSession session = sqlSessionFactory.openSession();
// 3 执行sql,并且接收返回结果
//User user = session.selectOne("cn.javabus.mapper.UserMapper.getUser", 1);
//SQL语句执行方式二 返回的是MapperProxy 代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//JVM会首先调用代理对象的 invoke() ;详情可了解 jdk 动态代理
User user = mapper.getUser("uid1");
欢迎大家来到IT世界,在知识的湖畔探索吧!
- UserMapper 是一个interface接口,所以SqlSession.getMapper()返回的是什么?
- 接口必须通过某个类实现,然后才能调用实现类的方法. getMapper()返回的是一个 jdk 动态代理技术创建的一个动态代理对象.
- java实现动态代理的两种方式-jdk 和 cglib
- 代理涉及概念: 1 目标对象; 2 代理对象; 3 创建代理对象的工具类(jdk 是 Proxy)
欢迎大家来到IT世界,在知识的湖畔探索吧!// 1 SqlSession.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//MapperProxyFactory首先为Mapper接口创建了一个实现了InvocationHandler方法调用处理器接口的代理类MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
}
// 2 mapperProxyFactory.newInstance(sqlSession)
// 最终返回的是 Proxy.newProxyInstance() 创建的代理对象
public T newInstance(SqlSession sqlSession) {
//创建MapperProxy代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
//3 使用 jdk字节码技术生成动态代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
/**
* @param loader 类加载器
* @param interfaces 目标对象接口,业务接口
* @param h 实现了 InvocationHandler接口的代理对象
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
//mapperProxyFactory.newInstance(sqlSession) 是非静态的,每次创建 mapper 动态代理对象需要先创建 mapperProxyFactory 对象
//configuara.mapperRegistry.knownMappers 保存了 Mapper class 对象与MapperProxyFactory对应关系
/**
* 1 Mapper接口代理类
(实现了InvocationHandler接口,使用 jdk 动态代理技术生成 Mapper动态代理对象)
*/
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;
/**
* 2 通过持有 mapperInterface 接口的引用 (和常见 jdk 动态代理示例相比: 没有真实对象,但是能通过接口找到调用的 sql语句)
* @param methodCache
*/
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
/**
* 通过 jdk 生成动态代理实现类,会生成业务接口的实现类和方法,会在方法内部先调用invoke()
* @param proxy 通过 jdk Proxy生成的代理对象 org.apache.ibatis.binding.MapperProxy@3bd40a57
* @param method 要执行被代理对象的方法 UserMapper.getUser(x)
* @param args 要执行方法的入参
*/
@Override//
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//1 如果执行的是 equals 等 Object 类的方法,就直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//2 对 Mapper接口中的方法进行封装,生成M apperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//3 调用mapperMethod 的 execute()方法
return mapperMethod.execute(sqlSession, args);
}
MapperStatement 对象创建过程
MapperStatement 是用于封装 Mapper 文件中的 sql 语句的.
当我们运行mapper文件时,每一个Mapper 接口中@Select|@Update等注解,或者 Mapper.xml select|update|insert|delete 对应的 Sql 语句,这些crud的标签都会被封装为一个MapperStatement对象
mybatis3 源码深度解析-Configuration和SqlSession创建代码解析
想了解MapperStatement 对象创建过程,就需要重点关注 Configuration 对象解析过程中<mappers> 标签的解析过程,主要是做了以下几件事情
1 获取<select|insert|update|delete> 标签所有属性信息
2 将<include> 标签引用的 sql 片段 替换为对应 <sql>标签对应的内容
3 通过 lang 属性创建 SqlSource (sql 语句 select * from user where id = ?)
4 获取 KeyGenerator (KeyGenerator 主键生成策略)
5 所有解析完成后,使用 MapperBuilderAssistant 创建MappedStatement对象,
添加到 Configuration#mappedStatements 保存起来
Mapper 接口方法的调用过程(动态代理)
欢迎大家来到IT世界,在知识的湖畔探索吧!SqlSession session = sqlSessionFactory.openSession();
//返回的是MapperProxy jdk 动态代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//JVM会首先调用代理对象的 invoke() 方法
User user = mapper.getUser("uid1");
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1 如果执行的是 equals 等 Object 类的方法,就直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//2 对 Mapper接口中的方法进行封装,生成M apperMethod 对象
//MethodSignature 构造器 做了方法参数解析等处理
final MapperMethod mapperMethod = cachedMapperMethod(method);
//3 调用mapperMethod 的 execute()方法
return mapperMethod.execute(sqlSession, args);
}
/**
* MapperMethod 对 Mapper 接口相关方法进行封装(可以方便获取 sql 语句类型,方法签名等)
*/
public class MapperMethod {
private final SqlCommand command;//用于获取 sql 语句类型,resolveMappedStatement()可以获取 MappedStatement对象
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
//获取目标方法参数信息
Object param = method.convertArgsToSqlCommandParam(args);
//调用 sqlSession insert()方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
}
}
SqlSession 执行 Mapper的过程
SqlSession只有一个默认实现 DefaultSqlSession
//1 DefaultSqlSession.selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
//statement cn.javabus.mapper.UserMapper.getUser
MappedStatement ms = configuration.getMappedStatement(statement);
//MappedStatement 作为参数,调用executor.query()方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
//2 BaseExecutor.query()
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同,具体参见SqlSource详解一节 TODO
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 委托给重载的query
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
//3 缓存中查询不到则调用 BaseExecutor.queryFromDatabase()
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// todo doQuery是个抽象方法,每个具体的执行器都要自己去实现,我们先看SIMPLE的
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//4 SimpleExecutor.doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 根据上下文参数和具体的执行器new一个StatementHandler, 其中包含了所有必要的信息,比如结果处理器、参数处理器、执行器等等,
// 主要有三种类型的语句处理器UNPREPARE、PREPARE、CALLABLE。默认是PREPARE类型,
// 通过mapper语句上的statementType属性进行设置,一般除了存储过程外不应该设置
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// todo 获取jdbc Statement对象,以及设置入参
stmt = prepareStatement(handler, ms.getStatementLog());
// todo 内部调用PreparedStatement完成具体查询后,将ps的结果集传递给对应的结果处理器进行处理。
// 查询结果的映射是mybatis作为ORM框架提供的最有价值的功能,同时也可以说是最复杂的逻辑之一。
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
//5 SimpleExecutor.prepareStatement()
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 1 jdbc 第一步 获取JDBC连接
// 通过DriverManager获取连接 Connection conn=DriverManager.getConnection(url,"root","hello");
Connection connection = getConnection(statementLog);
// 调用语句处理器的prepare方法
// 先走RoutingStatementHandler,又委派给了 BaseStatementHandler.prepare
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数 调用了 ps.setInt(1)等设置值
handler.parameterize(stmt);
return stmt;
}
//6 handler.query() 调用的是 PreparedStatementHandler.query()
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//调用resultSetHandler处理结果集
return resultSetHandler.handleResultSets(ps);
}
//7 resultSetHandler.handleResultSets(ps) ...
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/18177.html