请大家先看demo代码:
Main主类
Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/Configuration1.xml"));
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = build.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(1);
System.out.println(user.getId());
System.out.println(user.getName());
System.out.println(user.getPassword());
UserMapper类
int insert(User user);
UserMapper.xml
<insert id="insert" parameterType="com.mybatis.study.entity.User" >
insert into user(name,password) values(#{name},#{password})
</insert>
Configuration.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/dev?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
这是一个简单的demo,使用传统的mybatis,配置文件中配置了数据源、mapper文件地址,事务管理器之类的信息,mybatis启动的逻辑为先读取配置文件,将读取到的流封装成Reader传入到大家都很熟悉的SqlSessionFactoryBuilder类中构建得到SqlSessionFactory工厂对象,通过工厂对象获得SqlSession用于数据访问的session对象,用我们自己定义的UserMapper类接收到Mapper对象,在通过我们自己定义的方法实现数据访问这里开始,我们就可能有一点奇怪,明明我们定义的是一个没有具体实现的接口,为什么可以直接调用方法,而且还可以获得我们想要的结果呢?这里可以先简单告诉大家,从SqlSession.getMapper中获取的对象,是一个实现了我们定义的UserMapper接口的一个动态代理类,mybatis帮助我们构建了这么一个动态代理类,其中的逻辑还帮助我们封装了对数据库的访问。下面我们来具体看看,mybatis是怎么实现数据查询的。
我们通过调试进入sqlSession.getMapper方法,这个方法传入一个Class<?>对象,也就是我们的接口UserMapper.class,SqlSession是一个接口,它有两个实现类,DefaultSqlSession和SqlSessionManager,在这里是走入到DefaultSqlsession中的getMapper方法(具体逻辑在openSession中,本文不做详述),我们进入会经过Configuration类和MapperRegistry类,我们直接看到MapperRegistry类的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);
}
可以看到,会先尝试从knownMappers(这个属性会在构建Configuration类的时候通过读取配置文件和映射文件,自动解析填充的)这个Map中获取MapperProxyFactory对象,如果没有获取到则抛出异常,获得mapperProxyFactory工厂对象后在传入sqlsession对象来构建实例
newInstance的主要逻辑:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
在这里就是重点了,MapperProxy类是一个实现了InvocationHandler接口的类,懂得Jdk动态代理的同学应该很熟悉,创建jdk动态代理时,也就是后面那个方法Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);是需要传入InvocationHandler的一个实例,这个时候生成的动态代理类逻辑的主要处理是在InvocationHandler接口的invoke方法中,也就是说我们获取的Mapper对象,调用他的方法时,都会去执行这个invoke方法。如下
@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 (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
invoke方法有三个参数,第一个参数proxy是代理对象,第二个是我们调用的方法的反射表示Method,第三个参数则是方法调用的参数,用数组表示。我们来分析一下这个方法。
前面的判断是为了过滤调用Object的方法,真正执行数据库查询的是在最后两行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
cacheMapperMethod(method)方式是查询和更新缓存,具体的实现如下
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
method是一个map缓存,其中method作为key,MapperMethod作为value,这个方法表示如果存在method则直接返回,不存在则new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())创建,并保存到map中,返回创建的对象。获得对象调用execute(sqlSession, args);进入到execute方法,如下:
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
这个方法有点长,但是其实逻辑很简单,主要是判断传入当前对象需要执行的sql操作类型,我们可以看到,有五种sql类型分别是INSERT、UPDATE、DELETE、SELECT和FLUSH(刷新)操作,其中SELECT操作是相对比较负责的操作,涉及到一对多,一对一之类,相信大家也很熟悉。
再分析这段代码之前,我们先看一下,method和command两个字段的含义,这个字段是在构造函数中被创建,两个字段的类型分别是SqlCommand和MethodSignature,这两个类都是MapperMethod类中的静态内部类,先看一下 SqlCommand类
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);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
SqlCommand类用于封装sql语句,但是其中又并不存在真正的sql语句,其中只是存储了对应sql的id和类型,在sqlCommand的构造函数中,是通过resolveMappedStatement函数获取到封装有sql的对象,这个对象是从Configuration中获取的(这里可以看出,mybatis把很多运行要用到的东西都放进了Configuration类中,在build过程中做了很多事情),构造的前面逻辑判断方法上是否标识了Flush注解,这个我们也很少用,主要累积在else中,将封装有sql的mappedStatement对象的id和type保存下来了,其中的type也是用于后面分析sql操作类型的字段。
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public boolean hasRowBounds() {
return rowBoundsIndex != null;
}
public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}
public boolean hasResultHandler() {
return resultHandlerIndex != null;
}
public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}
public String getMapKey() {
return mapKey;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
public boolean returnsCursor() {
return returnsCursor;
}
/**
* return whether return type is {@code java.util.Optional}.
* @return return {@code true}, if return type is {@code java.util.Optional}
* @since 3.5.0
*/
public boolean returnsOptional() {
return returnsOptional;
}
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
MethodSignature类是对方法的封装,其中封装了方法一对多、参数解析等一系列对象,这里不作过多分析。我们再回过头来看数据的查询逻辑
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
先使用method方法将参数转化,在调用sqlSession.selectOne()方法,进入调试查看
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
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;
}
}
查询一个返回值时,还是会调用查询列表的方法,只是只取第一个元素,当返回的list大小超过一条时,抛出异常TooManyResultsException,这个异常我们也可能经常可以看到。
我们进入到selecList方法
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里通过之前存的sql的id查询到MappedStatement对象,再讲查询的逻辑委托给执行器executor来查询和处理返回集。
进入到executor.query()方法,这个executor模式会使用CachingExecutor执行器(拥有缓存功能的执行器,也及时我们说的一级缓存),进入到CachingExecutor.query方法
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
获得绑定的sql,这里使用BoundSql封装,通过boundSql创建key,用于下次查询可以从缓存中获取。查询逻辑流转到query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
先从缓存中获取,如果有,则返回缓存中的数据,没有回通过delegate代理查询。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
逻辑会流转到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 {
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;
}
具体逻辑又走到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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
这里到从Configuration(又是这个玩意儿,万能啊)获取StatementHandler,这个对象可以帮助我们处理jdbc中的Statement对象,使用prepareStatement完成参数占位,最后通过StatementHandler的query方法,这个handler的类型RoutingStatementHandler,但是他真正的处理逻辑又委托给了另外一个Handler,这个handler是根据构造传入的mappedStatement的type分别创建的,如下
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
这里是使用PreparedStatementHandler,所以RoutingStatementHandler的query方法又委托给了PreparedStatementHandler的query方法,进入到PreparedStatementHandler的query方法,如下
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
获得可以PrepareStatement对象,完成了参数绑定之后,可以直接执行(这一部分的内容涉及到JDBC的编程 ,大家应该都很清楚了,不作过多的详述)ps.execute()就是通过jdbc真正执行了sql,后面就是对sql执行后的返回集的处理,这个下次再分析吧。