Mybatis原理分析

mybatis源码分析

首先简单回顾一下mybatis的用法。mybatis最主要的就是mybatis配置文件、SqlSessionFactory SqlSession

private void test(){
    //mybatis配置文件
    String resource = "mybatisConfig.xml";
    InputStream inputStream;
    SqlSessionFactory sqlSessionFactory;
    SqlSession sqlSession = null;
    try {
        //根据配置文件获取输入流
        inputStream = Resources.getResourceAsStream(resource);
        //根据配置文件输入流创建会话工厂
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //根据会话工厂得到sqlsession
        sqlSession = sqlSessionFactory.openSession();
        //通过id=1查询用户信息
        //方式一:直接通过sqlsession的select方法查询
        User user = sqlSession.selectOne("com.learn.mybatis.mapper.UserMapper.findUserById",1);
        //方式二:通过mapper接口的方法查询(推荐使用第二种,该方式底层实际上依然是调用sqlsession的方法)
        //UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //User user = userMapper.findUserById(1);
        //提交事务,增删改需要commit,查询无需commit
        //sqlSession.commit();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //关闭会话
        if (sqlSession != null){
            sqlSession.close();
        }
    }
}

1、创建SqlSessionFactory

调用SqlSessionFactoryBuilder类的build方法:

public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        //通过XMLConfigBuilder解析配置文件,解析的配置信息都会封装为一个Configuration对象
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //创建SqlSessionFactory,实际是DefaultSqlSessionFactory。parser.parse()得到的是Configuration对象
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
        }

    }

    return var5;
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

2、获取SqlSession

调用SqlSessionFactory接口的openSession方法获取SqlSession,实际是调用具体实现类DefaultSqlSessionFactory的openSession方法。

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

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        //通过Confuguration对象获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
        Environment environment = this.configuration.getEnvironment();
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //表面是通过SqlSession执行SQL语句,实际底层是通过Executor执行的
        Executor executor = this.configuration.newExecutor(tx, execType);
        //创建DefaultSqlSession,得到SqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

SqlSession接口中的方法:

public interface SqlSession extends Closeable {
    <T> T selectOne(String var1);

    <T> T selectOne(String var1, Object var2);

    <E> List<E> selectList(String var1);

    <E> List<E> selectList(String var1, Object var2);

    <E> List<E> selectList(String var1, Object var2, RowBounds var3);

    <K, V> Map<K, V> selectMap(String var1, String var2);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

    <T> Cursor<T> selectCursor(String var1);

    <T> Cursor<T> selectCursor(String var1, Object var2);

    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);

    void select(String var1, Object var2, ResultHandler var3);

    void select(String var1, ResultHandler var2);

    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

    int insert(String var1);

    int insert(String var1, Object var2);

    int update(String var1);

    int update(String var1, Object var2);

    int delete(String var1);

    int delete(String var1, Object var2);

    void commit();

    void commit(boolean var1);

    void rollback();

    void rollback(boolean var1);

    List<BatchResult> flushStatements();

    void close();

    void clearCache();

    Configuration getConfiguration();

    // 获取mapper接口代理对象
    <T> T getMapper(Class<T> var1);

    Connection getConnection();
}

3、获取Mapper接口

此处不使用SqlSession中的方法直接操作数据库,而是使用SqlSession获取Mapper接口,调用Mapper接口中的方法操作数据库。实际上是通过JDK动态代理获取Mapper接口的代理类,通过代理类调用方法来操作数据库。

在使用SqlSession获取Mapper接口的动态代理时,必须满足下面四个条件:

1. 映射文件的命名空间(namespace)必须是mapper接口的全路径;

2. 映射文件的statement的id必须和mapper接口的方法名保持一致;

3. statement的resultType必须和mapper接口方法的返回类型一致;

4. statement的parameterType与mapper接口方法的参数类型一致(不一定)。

SqlSession调用getMapper方法获取Mapper接口,实际是调用实现类DefaultSqlSession的getMapper方法。

public <T> T getMapper(Class<T> type) {
    return this.configuration.getMapper(type, this);
}

调用Configuration类的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}

Configuration类中同时提供了新增Mapper的方法。可想而知,既然可以通过Configuration获取到Mapper,必然需要先新增Mapper。

public void addMappers(String packageName, Class<?> superType) {
    this.mapperRegistry.addMappers(packageName, superType);
}

public void addMappers(String packageName) {
    this.mapperRegistry.addMappers(packageName);
}

public <T> void addMapper(Class<T> type) {
    // 调用MapperRegistry类的addMapper方法
    this.mapperRegistry.addMapper(type);
}

此处先分析Configuration类的addMapper方法,然后再回过头来分析getMapper方法。

下面是MapperRegistry类的源码:

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

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

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //根据接口的Class获取到MapperProxyFactory对象
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    //调用MapperRegistry类的addMapper方法
    public <T> void addMapper(Class<T> type) {
        //只添加接口
        if (type.isInterface()) {
            //如果已经添加,不允许重复添加
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                //以接口的Class为键,以MapperProxyFactory对象为值保存到Map中。	
                //MapperProxyFactory对象又持有接口的引用
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        Iterator var5 = mapperSet.iterator();

        while(var5.hasNext()) {
            Class<?> mapperClass = (Class)var5.next();
            this.addMapper(mapperClass);
        }

    }

    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
}

下面看一下MapperProxyFactory类的源码:

public class MapperProxyFactory<T> {
    //MapperProxyFactory类中持有Mapper接口的引用,封装了Mapper接口作为属性
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }
    //通过JDK动态代理生成接口的代理对象
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

现在再回过头来分析Configuration类的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 调用MapperRegistry类的getMapper方法
    return this.mapperRegistry.getMapper(type, sqlSession);
}

调用MapperRegistry类的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

调用MapperProxyFactory类的newInstance方法:

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}

通过JDK动态代理生成接口的代理对象:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

因此,通过SqlSessiongetMapper方法最终得到的是Mapper接口的代理对象。并且根据JDK动态代理的原理可知,MapperProxy必然实现了InvocationHandler接口,重写了invoke方法。在代理对象重写的目标方法中会调用重写的invoke方法。

MapperProxy类的源码如下所示:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final 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;
    }

    public Object invoke(Object proxy, Method, Object[] args) throws Throwable {
        try {
            // 如果要调用的方法属于类,而不是属于接口,则直接通过反射调用(接口没有继承Object)
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            // 若是默认方法,则直接调用接口的默认方法
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        // 调用execute方法
        MapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod = (MapperMethod)this.methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }

        return mapperMethod;
    }

    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method, Object[] args) throws Throwable {
        Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        Class<?> declaringClass = method.getDeclaringClass();
        return ((Lookup)constructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
    }
}

4、调用接口方法操作数据库

本例中通过User user = userMapper.findUserById(1);调用UserMapper接口的findUserById方法查询用户信息。根据JDK动态代理的原理可知,实际是代理对象调用自己重写的目标方法,在重写的目标方法中调用了InvocationHandlerinvoke方法。因此,userMapper.findUserById方法在底层调用了InvocationHandlerinvoke方法。由上面可知,MapperProxy类实现了InvocationHandler接口,重写了invoke方法,根据多态性可知,实际调用了MapperProxy类的invoke方法。

调用MapperProxy类的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //如果要调用的方法属于类,而不是属于接口,则直接通过反射调用(接口没有继承Object)
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }

        if (this.isDefaultMethod(method)) {
            return this.invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    //如果要调用Mapper接口中对数据库增删改查的方法,则调用execute方法
    return mapperMethod.execute(this.sqlSession, args);
}
接口没有继承Object。如果一个接口定义是最顶级的(没有super interfaces),那么这个接口会自动声明一个abstract member method结构体来代表所有来自Object类(一切类的superclass)中的public方法(包括这些方法的签名、返回类型以及抛出的异常)。再换种说法——意思是接口隐含定义了一套与Object类中的方法签名完全相同的方法,所以,我们在程序中调用接口的那些与Object中具有相同签名的方法时,编译器不会报错!
由于Mapper接口没有实现类,所以在MapperProxy类的invoke方法中会调用MapperMethodexecute方法。

调用MapperMethodexecute方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    switch(this.command.getType()) {
    case INSERT:
        // sqlSession.insert方法
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        // sqlSession.update方法
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        // sqlSession.delete方法
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        // sqlSession.selectOne方法
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

最终都会执行SqlSession对应的增删改查方法。因此,通过Mapper接口的代理对象调用Mapper接口的代理方法,与直接通过SqlSession调用方法是一样的。此处以SqlSession接口的selectOne方法为例分析,调用实现类DefaultSqlSessionselectOne方法:

public <T> T selectOne(String statement, Object parameter) {
    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;
    }
}

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var5;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // 调用抽象实现类BaseExecutor的query方法
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception var9) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
    } finally {
        ErrorContext.instance().reset();
    }

    return var5;
}

交给Executor接口去执行,调用Executor接口的query方法,实际是调用抽象实现类BaseExecutorquery方法:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //通过MappedStatement还有查询参数来生成BoundSql。BoundSql就是对sql的包装,包含sql语句,参数,List(参数的mapping)。
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
    return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

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 (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
            this.clearLocalCache();
        }

        List list;
        try {
            ++this.queryStack;
            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
            if (list != null) {
                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
		        // 调用抽象类BaseExecutor的queryFromDatabase方法
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

        if (this.queryStack == 0) {
            Iterator var8 = this.deferredLoads.iterator();

            while(var8.hasNext()) {
                BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                deferredLoad.load();
            }

            this.deferredLoads.clear();
            if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                this.clearLocalCache();
            }
        }

        return list;
    }
}

调用抽象类BaseExecutorqueryFromDatabase方法:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        //调用抽象类BaseExecutor的doQuery方法,实际调用实现类的doQuery方法
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        this.localCache.removeObject(key);
    }

    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}

继续看一下实现类SimpleExecutordoQuery方法:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = this.prepareStatement(handler, ms.getStatementLog());
	    // 调用实现类PreparedStatementHandler的query方法
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}

调用StatementHandler接口的query方法,调用实现类PreparedStatementHandlerquery方法:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    //通过PreparedStatement执行SQL,将结果交给ResultSetHandler处理
    ps.execute();
    //调用接口ResultSetHandler的handleResultSets方法
    return this.resultSetHandler.handleResultSets(ps);
}

调用实现类DefaultResultSetHandlerhandleResultSets方法:

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
    List<Object> multipleResults = new ArrayList();
    int resultSetCount = 0;
    ResultSetWrapper rsw = this.getFirstResultSet(stmt);
    List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    this.validateResultMapsCount(rsw, resultMapCount);

    while(rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
        this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
        rsw = this.getNextResultSet(stmt);
        this.cleanUpAfterHandlingResultSet();
        ++resultSetCount;
    }

    String[] resultSets = this.mappedStatement.getResultSets();
    if (resultSets != null) {
        while(rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
            }

            rsw = this.getNextResultSet(stmt);
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }
    }

    return this.collapseSingleResultList(multipleResults);
}

至此,mybatis源码分析完毕,由此可知,mybatis底层是对JDBC的封装,是通过JDK动态代理与JDBC实现的。mybatis通过sqlsession操作数据库,实际上是通过JDBC操作数据库。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值