Mybatis

提出几个问题:
1.mybatis通过接口即可以实现对数据库的访问,是不是很神奇?
2.什么时候解析  mybatis-config.xml 呢?
3.什么时候解析  xxxmapper.xml 呢?
4.什么时候生成代理类呢?
5.生成代理类的具体方法?
 

核心概念

     SqlSessionFactoryBuilder(构造器): 它可以从XML、注解或者手动配置Java代码来创建SqlSessionFactory。
    SqlSessionFactory: 用于创建SqlSession (会话) 的工厂
    SqlSession: SqlSession是Mybatis最核心的类,可以用于执行语句、提交或回滚事务以及获取映射器Mapper的接口
    SQL Mapper: 它是由一个Java接口和XML文件(或注解)构成的,需要给出对应的SQL和映射规则,它负责发送SQL去执行,并返回结果
 

举例

//sql语句
CREATE TABLE  user(
  id int,
  name VARCHAR(255) not NULL ,
  age int ,
  PRIMARY KEY (id)
)ENGINE =INNODB DEFAULT CHARSET=utf8;

//java实体类
@Data
public class User {
    private int id;
    private int age;
    private String name;
    @Override
    public String toString() {
        return "User{" + "id=" + id +", age=" + age +", name='" + name + '\'' +'}';
    }
}
 

//配置mybatis-config.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>
    <!--enviroments表示环境配置,可以配置成开发环境(development)、测试环境(test)、生产环境(production)等-->
    <environments default="development">
        <environment id="development">
            <!--transactionManager: 事务管理器,属性type只有两个取值:JDBC和MANAGED-->
            <transactionManager type="MANAGED" />
            <!--dataSource: 数据源配置-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <!--mappers文件路径配置-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

//mapper接口
public interface UserMapper {
    User selectById(int id);
}
 
 
//xml文件
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性表示命令空间,不同xml映射文件namespace必须不同-->
<mapper namespace="com.pjmike.mybatis.UserMapper">
    <select id="selectById" parameterType="int"
            resultType="com.pjmike.mybatis.User">
             SELECT id,name,age FROM user where id= #{id}
       </select>
</mapper>

 

//测试类
public class MybatisTest {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.selectById(1);
            System.out.println("User : " + user);
        }
    }
}
// 结果:
User : User{id=1, age=21, name='pjmike'}

 

解答第一步的问题:

2.什么时候解析 mybatis-config.xml 呢?

回忆一下示例代码
 
    
private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder()
                    .build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
使用 new SqlSessionFactoryBuilder().build方法创建sqlSessionFactory工厂
//inputStream = Resources.getResourceAsStream("mybatis-config.xml")
public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
    }
  }
}
 
这里是真正解析 mybatis-config.xml的位置
//对于mybatis的全局配置文件的解析,相关解析代码位于XMLConfigBuilder的parse()方法中

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration")); //configuration是mybatis-config.xml的根节点
  return configuration; 
}
//解析mybatis-config.xml类
private void parseConfiguration(XNode root) {
  try {
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //该方法是对全局配置文件中mappers属性的解析  
    //mybatis-config.xml-> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

 

3.什么时候解析 xxxMapper.xml 呢?


/**
 * <mappers>
 *   <mapper resource="mapper/UserMapper.xml"/>
 * </mappers>
*/

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        //mybatis-config.xml-> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);   //这里是mapper/UserMapper.xml的resource
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //方法就是XMLMapperBuilder对Mapper映射器文件进行解析,可与XMLConfigBuilder进行类比
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
 
解析xxxmapper.xml: mapperElement(root.evalNode("mappers"));
//XMLMapperBuilder.parse() 方法就是XMLMapperBuilder对Mapper映射器文件进行解析,可与XMLConfigBuilder进行类比


/**
*<mapper namespace="com.pjmike.mybatis.UserMapper">
*    <select id="selectById" parameterType="int"
*            resultType="com.pjmike.mybatis.User">
*            SELECT id,name,age FROM user where id= #{id}
*    </select>
*</mapper>
*/


public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper")); //解析映射文件的根节点mapper元素
    configuration.addLoadedResource(resource);
    bindMapperForNamespace(); //重点方法,这个方法内部会根据namespace属性值,生成动态代理类
  }
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

/**
 * context 里是xml的所有内容
 * 该方法主要用于将mapper文件中的元素信息,比如insert、select这等信息解析到MappedStatement对象,并保存到Configuration类中的     
 * mappedStatements属性中,以便于后续动态代理类执行CRUD操作时能够获取真正的Sql语句信息
*/
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    //用于解析insert、select这类元素信息,并将其封装成MappedStatement对象
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

 

4.什么时候生成代理类呢?

动态代理类的生成
/*
* 该方法是核心方法,它会根据mapper文件中的namespace属性值,为接口生成动态代理类
* 将代理类放入mapper里
*/
private void bindMapperForNamespace() {
    //获取mapper元素的namespace属性值
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 获取namespace属性值对应的Class对象,即dao接口class
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //如果没有这个类,则直接忽略,这是因为namespace属性值只需要唯一即可,并不一定对应一个XXXMapper接口
        //没有XXXMapper接口的时候,我们可以直接使用SqlSession来进行增删改查
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          //如果namespace属性值有对应的Java类,调用Configuration的addMapper方法,将其添加到MapperRegistry中
          configuration.addMapper(boundType);
        }
      }
    }
  }

 

这里提到了Configuration的addMapper方法,实际上Configuration类里面通过MapperRegistry对象维护了所有要生成动态代理类的XxxMapper接口信息,可见Configuration类确实是相当重要一类
public class Configuration {
    ...
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    ...
    public <T> void addMapper(Class<T> type) {
      mapperRegistry.addMapper(type);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    ...
}

 

其中两个重要的方法:getMapper()和addMapper()
getMapper(): 用于创建接口的动态类
addMapper(): mybatis在解析配置文件时,会将需要生成动态代理类的接口注册到其中
 
1. Configuration#addMappper()
Configuration将addMapper方法委托给MapperRegistry的addMapper进行的,源码如下
public <T> void addMapper(Class<T> type) {
    // 这个class必须是一个接口,因为是使用JDK动态代理,所以需要是接口,否则不会针对其生成动态代理
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 生成一个MapperProxyFactory,用于之后生成动态代理类
        knownMappers.put(type, new MapperProxyFactory<>(type));
        //以下代码片段用于解析我们定义的XxxMapper接口里面使用的注解,这主要是处理不使用xml映射文件的情况
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 

MapperRegistry内部维护一个映射关系,每个接口对应一个MapperProxyFactory(生成动态代理工厂类)
 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
这样便于在后面调用MapperRegistry的getMapper()时,直接从Map中获取某个接口对应的动态代理工厂类,然后再利用工厂类针对其接口生成真正的动态代理类。
 
2. Configuration#getMapper()

Configuration的getMapper()方法内部就是调用MapperRegistry的getMapper()方法,源代码如下:


  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //根据Class对象获取创建动态代理的工厂对象MapperProxyFactory
    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);
    }
  }
 
5.生成代理类的具体方法 ?- - MapperProxyFactory.newInstance

从上面可以看出,创建动态代理类的核心代码就是在MapperProxyFactory.newInstance方法中,源码如下:


  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  } 
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //这里使用JDK动态代理,通过Proxy.newProxyInstance生成动态代理类
    // newProxyInstance的参数:类加载器、接口类、InvocationHandler接口实现类
    // 动态代理可以将所有接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
 
这里的InvocationHandler接口的实现类是MapperProxy,其源码如下
 
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 {
      //如果调用的是Object类中定义的方法,直接通过反射调用即可
      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);
    }
    //调用XxxMapper接口自定义的方法,进行代理
    //首先将当前被调用的方法Method构造成一个MapperMethod对象,然后掉用其execute方法真正的开始执行。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
  ...
}

 

最终的执行逻辑在于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()) {
      //insert语句的处理逻辑
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //update语句的处理逻辑
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //delete语句的处理逻辑
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //select语句的处理逻辑
      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);
          //调用sqlSession的selectOne方法
          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;
  }
  ...
}
 
在MapperMethod中还有两个内部类,SqlCommand和MethodSignature类,在execute方法中首先用switch case语句根据SqlCommand的getType()方法,判断要执行的sql类型,比如INSET、UPDATE、DELETE、SELECT和FLUSH,然后分别调用SqlSession的增删改查等方法
 

1.mybatis通过接口即可以实现对数据库的访问,是不是很神奇?

调用接口,其实是访问mybatis生成的代理类,具体调用getMapper方法的地方:


UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

public class DefaultSqlSession implements SqlSession {
  private final Configuration configuration;
  private final Executor executor;
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  ...
}

 

所以getMapper方法的大致调用逻辑链是:
SqlSession#getMapper() —> Configuration#getMapper() —>   MapperRegistry#getMapper() —> MapperProxyFactory#newInstance() —> Proxy#newProxyInstance()
我们通过SqlSession的getMapper方法获得接口代理来进行CRUD操作,其底层还是依靠的是SqlSession的使用方法。
 
原理图:
 
参考资料:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值