温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

MyBatis中如何执行Sql语句

发布时间:2021-06-11 16:25:39 来源:亿速云 阅读:262 作者:Leah 栏目:编程语言

这篇文章给大家介绍MyBatis中如何执行Sql语句,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

public class UserDaoTest {   private SqlSessionFactory sqlSessionFactory;   @Before   public void setUp() throws Exception{     ClassPathResource resource = new ClassPathResource("mybatis-config.xml");     InputStream inputStream = resource.getInputStream();     sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);   }   @Test   public void selectUserTest(){     String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";     SqlSession sqlSession = sqlSessionFactory.openSession();     CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);     Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);     System.out.println(cbondissuer);     sqlSession.close();   } }

之前提到拿到sqlSession之后就能进行各种CRUD操作了,所以我们就从sqlSession.getMapper这个方法开始分析,看下整个Sql的执行流程是怎么样的。

获取Mapper

进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {   //mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml   //mapperRegistry的key是接口的全限定名,比如com.csx.demo.spring.boot.dao.SysUserMapper   //mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类)   return mapperRegistry.getMapper(type, sqlSession); }

进入getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);   //如果配置文件中没有配置相关Mapper,直接抛异常   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);   }  }

进入MapperProxyFactory的newInstance方法:

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;  }  //生成Mapper接口的动态代理类MapperProxy  @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);  } }

下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对象中没有委托类,也就是说MapperProxy干了代理类和委托类的事情)。好了下面重点看下invoke方法。

//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 {    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);   }   //获取MapperMethod,并调用MapperMethod   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;  }  @UsesJava7  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)    throws Throwable {   final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class     .getDeclaredConstructor(Class.class, int.class);   if (!constructor.isAccessible()) {    constructor.setAccessible(true);   }   final Class<?> declaringClass = method.getDeclaringClass();   return constructor     .newInstance(declaringClass,       MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED         | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)     .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);  }  /**   * Backport of java.lang.reflect.Method#isDefault()   */  private boolean isDefaultMethod(Method method) {   return ((method.getModifiers()     & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)     && method.getDeclaringClass().isInterface();  } }

所以这边需要进入MapperMethod的execute方法:

public Object execute(SqlSession sqlSession, Object[] args) {   Object result;   //判断是CRUD那种方法   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;  }

然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

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();    //内部封装了ParameterHandler和ResultSetHandler    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);    stmt = prepareStatement(handler, ms.getStatementLog());    //StatementHandler封装了Statement, 让 StatementHandler 去处理    return handler.<E>query(stmt, resultHandler);   } finally {    closeStatement(stmt);   }  }

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {    //到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧   PreparedStatement ps = (PreparedStatement) statement;   ps.execute();   //结果交给了ResultSetHandler 去处理,处理完之后返回给客户端   return resultSetHandler.<E> handleResultSets(ps);  }

到此,整个调用流程结束。

简单总结

这边结合获取SqlSession的流程,做下简单的总结:

  • SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

  • 拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。

  • 获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;

  • 获得SqlSession对象后就能执行各种CRUD方法了。

以上是获得SqlSession的流程,下面总结下本博客中介绍的Sql的执行流程:

  • 调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制);

  • MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;

  • 往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。

Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。

  • 调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。

Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。

重要类

  • MapperProxyFactory

  • MapperProxy

  • MapperMethod

  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;

  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;

  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

  • ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,

  • ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

  • TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换

  • MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,

  • SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

  • BoundSql 表示动态生成的SQL语句以及相应的参数信息

Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

关于MyBatis中如何执行Sql语句就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI