深入理解Mybatis(一)

为什么去学习MyBatis
优点:

  1. MyBatis简单易学,只需要简单的几个xml文件配置,即可
  2. 灵活:因为mybatis是sql是放在xml文件中代码和sql分离,可以做统一优化和管理,提供动态标签更灵活编写sql

缺点:

  1. 移植性差,以为sql是依赖数据库的

  2. 编写sql量大

代码

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User findUserById(Integer id) {
        //为什么接口却可以直接执行?
        User user = userMapper.selectOne(id);
        return user;
    }
}

我们并没有写userMapper的实现类,那Mybatis怎么调用的?想知道这个我们首先要了解下动态代理,动态代理有cglib和JDK实现的动态代理

JDK动态代理
首先代理类要实现 InvocationHandler 这个接口

public class ProxyExample implements InvocationHandler{
    /**
     * 
     * @param proxy     代理类
     * @param method    执行的方法
     * @param args      方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return "Hello Proxy ";
    }

}

我们要代理的接口创建如下:

public interface TargetProxy {
    
    public String getProxyHello();
    
}

下面补全代理的代码,让代理类代理TargetProxy接口

public class ProxyExample implements InvocationHandler{
    /**
     *
     * @param proxy     代理类
     * @param method    执行的方法
     * @param args      方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return "Hello Proxy ";
    }

    /**
     * 拿到需要代理的类
     * @return
     */
    public Object setTargetProxy(){
        /**
         * TargetProxy.class.getClassLoader() 目标类的类加载器
         * new Class[]{TargetProxy.class} 目标类的接口class对象
         * this 代理类 ,因为代理类是本类, 调用目标类时会执行代理类的 invoke 方法
         */
        return Proxy.newProxyInstance(TargetProxy.class.getClassLoader() ,new Class[]{TargetProxy.class} ,this);
    }

    public static void main(String[] args) {
        //创建代理类
        ProxyExample proxyExample = new ProxyExample();
        //获取目标对象
        TargetProxy targetProxy = (TargetProxy)proxyExample.setTargetProxy();
        //执行目标对象方法
        String proxyHello = targetProxy.getProxyHello();
        System.out.println(proxyHello);
    }

}

执行该main方法:

Hello Proxy

那我们来看代理模式加深下印象:

在这里插入图片描述

到此我们看到了,接口直接执行方法却得到了我们想要的结果, 同样MyBatis也是这样做的,下面我们看看mybatis的调用。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    SqlSessionFactory sqlSessionFactory;

    @Override
    public void removeById(Integer id) {
        //获取sqlsession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通过sqlsession获取mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //执行删除
        mapper.deleteOne(id);
    }
}

我们可以看到获取的顺序 : sqlSessionFactory->SqlSession->Mapper

那我们先看SqlSessionFactory它是一个接口:

在这里插入图片描述

有两个实现类:

defaultSqlSessionFactory:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
......
  }

可以看到获取sqlSession的方法有很多我们看其中一个:

  //通过执行类型和事务获取执行器 ,然后创建默认defaultsqlsession
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //获取环境变量
      final Environment environment = configuration.getEnvironment();
      // 事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

可以看到defaultSqlSessionFactory中的SqlSession是直接new DefaultSqlSession创建的接下来看下 SqlSessionManage

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
    
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[]{SqlSession.class},
            new SqlSessionInterceptor());
  }
}

看他构造函数,是不是很眼熟,使用了我们上面说的代理模式 ,被代理的对象是SqlSessionFactory,代理对象是 SqlSessionInterceptor,等执行SqlsessionFactory的时候会执行代理类,看下代理类的实现:

  private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //从当前线程去拿sqlsession
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        //否则就从新打开一个session连接
        final SqlSession autoSqlSession = openSession();
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }
  }

这个执行的代理方法是在ThreadLocal中存放一个sqlsession,如果同一个线程,他会直接去ThreadLocal中去拿取SqlSession,如果没有get到,则直接重新重SqlSessionFactory中创建一个新的连接。可以避免连接的频繁创建销毁带来的性能损耗。到此为止我们基本上可以看到创建SqlSession的流程了 :
在这里插入图片描述

下面就要看怎么获取的Mapper接口了

public interface SqlSession extends Closeable {

......
  <T> T getMapper(Class<T> type);
......
}

最终调用的是DefaultSqlSession->Configuration->MapperRegister中的getMapper方法,如果是idea可以一直点Ctrl+左键到最终执行的实现

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //mapper的class对象
    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);
    }
  }
  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
......
}

可以仔细看下getMapper方法他是从一个Map中去出来的,那我们就要去看看这个map是怎么添加进去的,Key和Value都是什么,下面也是这个mapperRegister类中的方法addMapper

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {//校验有重复的mapper
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));//创建一个对应的mapper 的mapperproxyfactory
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
 }

可以看出添加的时候是Key是接口class对象,value是MapperProxyFactory对象,那我们去看看这个对象

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;
  }
  ......
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //返回代理的mapper接口,会执行invoke
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //初始化mapperproxy类
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

看newInstance(SqlSession sqlSession)这个方法,前面特别提醒的代码执行的是这个方法,可以看到首先创建了一个MapperProxy对象,我们点进去会发现又是我们前面学习的动态代理模式。之后调用newInstance(MapperProxy mapperProxy)方法里面是一个绑定代理对象和目标对象的方法,仔细看下不难发现,被代理类mapperInterface是我们创建MapperProxyFactory的时候传入进来的,mapperProxy是我们刚刚创建的。那现在我们知道了执行mapper的方法,调用的是代理类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())) {//如果是object类的方法直接执行
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {//如果是java编译器使用下面方法调用
        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;
  }
......
}

重点看invoke方法,最后执行的是executor方法。分析到现在我们大概对我们为什么mapper接口可以直接执行方法有了个大致了解。但是我们还是不知道,为什么执行executor方法就能执行我们所写的sql语句,下面章介绍这个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值