mybaits原理详解一连接

        遥想在13年,还在上学的我,还在辛辛苦苦学习原始的JDBC,每次连接数据库都要自己手动连接数据库.当时就想为啥不写一个框架保持连接,可惜同年mybaits初版出来了,作者的梦想就破灭了。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
public class JdbcExample {
    private static final String URL = "jdbc:mysql://localhost:3306/mydatabase";
    private static final String USER = "myuser";
    private static final String PASSWORD = "mypassword";
 
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 加载并注册JDBC驱动类
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 建立数据库连接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 创建SQL查询语句
            String sql = "SELECT * FROM mytable WHERE id = ?";
            // 预编译SQL语句
            pstmt = conn.prepareStatement(sql);     
            // 设置参数
            pstmt.setInt(1, 1);
            // 执行查询
            rs = pstmt.executeQuery();       
            // 处理查询结果
            while (rs.next()) {
                System.out.println("ID: " + rs.getInt("id"));
                System.out.println("Name: " + rs.getString("name"));
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

        多年以后,在某次面试过程中被问到mybaits的原理,再次想起当年的梦想,决定好好研究一波,下面先将原理精简总结下,后续会细讲原理。

1.使用SqlSessionFactoryBuilder()构建一个sqlSessionFactory
//mybatis全局配置文件名定义
    String resource = "mybatis-config.xml";
//读取这个resource表示的文件并将其转化为输入流
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.使用sqlSessionFactory子类(DefaultSqlSessionFactory)工程的openSession()方法获取SqlSession(sqlSession里面都是一些select,update的方法)
    sqlSessionFactory.openSession();
3.调用DefaultSqlSessionFactory的openSessionFromDataSource,新建一个executor
Executor executor = this.configuration.newExecutor(tx, execType);//type默认是一个SIMPLE,REUSE,BATCH;
4.executor里面的方法会去调用StatementHandler
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = this.prepareStatement(handler, ms.getStatementLog());
    var9 = handler.query(stmt, resultHandler);
5.调用了prepareStatement方法赋值parameterHandler
    Connection connection = this.getConnection(statementLog);
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    handler.parameterize(stmt);
6.查询结果映射ResultHandler
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute();//执行sql
    return this.resultSetHandler.handleCursorResultSets(ps);
7.关闭stament
    stmt.closeOnCompletion();

下面来细讲每一步

        1、第一步读取配置和mapper,mybaits只是将JDBC隐藏起来了,不是没有,这里以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">
        <!-- 这一步为xml的标头,引用的dtd是用来检验xml的格式的,这里我们不做讲解-->
<configuration>
    <!-- -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydatabase?useSSL=true&amp;useUnicode=false&amp;
                characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="********"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在mybatis核心配置文件中注册-->
    <mappers>
        <!--resource要用 /-->
        <mapper resource="mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

  这个有个配置为<dataSource type="POOLED"> ,很显然这个是一个类型,这个其实就是为了解决我们jdbc里每次都要自己手动连接,使用完后要手动关闭的麻烦,mybaits提供了三种type,分别为“POOLED”,“UNPOOLED”,“JNID”(这里不做讨论),其中默认为“POOLED”,正如名字所示,这是一个连接池,相反“UNPOOLED”就是非连接池

  下面我们来看源码。源码位置在如下位置

        先看简单的unpooled

public class UnpooledDataSource implements DataSource {
    private ClassLoader driverClassLoader;
    private Properties driverProperties;
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
    //驱动名
    private String driver;
    //连接url
    private String url;
    //用户名
    private String username;
    //密码
    private String password;
    //是否自动提交
    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;
    private Integer defaultNetworkTimeout;
 }

  这里我们看到了jdbc里面的常客driver,username,password,实际UnpooledDataSource也是这样做的,接下来我们看下面四个方法,其中initializeDriver()这个方法是用来加载Driver驱动的,其中核心代码为driverType = Class.forName(this.driver, true, this.driverClassLoader);这一步和我们jdbs的Class.forName("com.mysql.cj.jdbc.Driver");的作用是一样的。另一个方法configureConnection(),这个方法最总返回了一个conn,这一步和老的jdbc里面的conn = DriverManager.getConnection(URL, USER, PASSWORD);是异曲同工的。

//根据用户名和密码创建连接
private Connection doGetConnection(String username, String password) throws SQLException {
        Properties props = new Properties();
        if (this.driverProperties != null) {
            props.putAll(this.driverProperties);
        }

        if (username != null) {
            props.setProperty("user", username);
        }

        if (password != null) {
            props.setProperty("password", password);
        }

        return this.doGetConnection(props);
    }

    private Connection doGetConnection(Properties properties) throws SQLException {
        this.initializeDriver();
        Connection connection = DriverManager.getConnection(this.url, properties);
        this.configureConnection(connection);
        return connection;
    }

    private synchronized void initializeDriver() throws SQLException {
        if (!registeredDrivers.containsKey(this.driver)) {
            try {
                Class driverType;
                if (this.driverClassLoader != null) {
                    driverType = Class.forName(this.driver, true, this.driverClassLoader);
                } else {
                    driverType = Resources.classForName(this.driver);
                }

                Driver driverInstance = (Driver)driverType.getDeclaredConstructor().newInstance();
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                registeredDrivers.put(this.driver, driverInstance);
            } catch (Exception var3) {
                Exception e = var3;
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }

    }

    private void configureConnection(Connection conn) throws SQLException {
        if (this.defaultNetworkTimeout != null) {
            conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), this.defaultNetworkTimeout);
        }

        if (this.autoCommit != null && this.autoCommit != conn.getAutoCommit()) {
            conn.setAutoCommit(this.autoCommit);
        }

        if (this.defaultTransactionIsolationLevel != null) {
            conn.setTransactionIsolation(this.defaultTransactionIsolationLevel);
        }

    }

        下面来看PooledDataSource这个类这里面我们看到了类似线程池的东西,这里借用下官方文档里面的定义 

图片

 

public class PooledDataSource implements DataSource {
    private static final Log log = LogFactory.getLog(PooledDataSource.class);
    private final PoolState state = new PoolState(this);
    private final UnpooledDataSource dataSource;
    //最大活跃连接数
    protected int poolMaximumActiveConnections = 10;
    // 在任意时间存在的空闲连接数
    protected int poolMaximumIdleConnections = 5;
    //
    protected int poolMaximumCheckoutTime = 20000;
    //
    protected int poolTimeToWait = 20000;
    //
    protected int poolMaximumLocalBadConnectionTolerance = 3;
    protected String poolPingQuery = "NO PING QUERY SET";
    protected boolean poolPingEnabled;
    protected int poolPingConnectionsNotUsedFor;
    private int expectedConnectionTypeCode;
}

        PooledDataSource类里面重要的方法有三个 ,分别是popConnection(获取连接),pushConnection(关闭连接),forceCloseAll(强制关闭所有)这个连接方法有点长,我们一点点来看。

public ConnectionpushConnection getConnection() throws SQLException {
        return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
 }

 public Connection getConnection(String username, String password) throws SQLException {
        return this.popConnection(username, password).getProxyConnection();
 }
 private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countedWait = false;
        PooledConnection conn = null;
        long t = System.currentTimeMillis();
        int localBadConnectionCount = 0;

        while(conn == null) {
            synchronized(this.state) {
                PoolState var10000;
                if (!this.state.idleConnections.isEmpty()) {
                    conn = (PooledConnection)this.state.idleConnections.remove(0);
                    if (log.isDebugEnabled()) {
                        log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                    conn = new PooledConnection(this.dataSource.getConnection(), this);
                    if (log.isDebugEnabled()) {
                        log.debug("Created connection " + conn.getRealHashCode() + ".");
                    }
                } else {
                    PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                        ++this.state.claimedOverdueConnectionCount;
                        var10000 = this.state;
                        var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        var10000 = this.state;
                        var10000.accumulatedCheckoutTime += longestCheckoutTime;
                        this.state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            try {
                                oldestActiveConnection.getRealConnection().rollback();
                            } catch (SQLException var15) {
                                log.debug("Bad connection. Could not roll back");
                            }
                        }

                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled()) {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    } else {
                        try {
                            if (!countedWait) {
                                ++this.state.hadToWaitCount;
                                countedWait = true;
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
                            }

                            long wt = System.currentTimeMillis();
                            this.state.wait((long)this.poolTimeToWait);
                            var10000 = this.state;
                            var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        } catch (InterruptedException var16) {
                            break;
                        }
                    }
                }

                if (conn != null) {
                    if (conn.isValid()) {
                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }

                        conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        this.state.activeConnections.add(conn);
                        ++this.state.requestCount;
                        var10000 = this.state;
                        var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                        }

                        ++this.state.badConnectionCount;
                        ++localBadConnectionCount;
                        conn = null;
                        if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
                            if (log.isDebugEnabled()) {
                                log.debug("PooledDataSource: Could not get a good connection to the database.");
                            }

                            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }
        }

        if (conn == null) {
            if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
            }

            throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        } else {
            return conn;
        }
    }

简单画一个新建连接流程图(新建其实不准确,准确应该叫获取连接)

图片

        归还连接的流程图如下。

protected void pushConnection(PooledConnection conn) throws SQLException {
        synchronized(this.state) {
            this.state.activeConnections.remove(conn);
            if (conn.isValid()) {
                PoolState var10000;
                if (this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {
                    var10000 = this.state;
                    var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }

                    PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                    this.state.idleConnections.add(newConn);
                    newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                    newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                    conn.invalidate();
                    if (log.isDebugEnabled()) {
                        log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                    }

                    this.state.notifyAll();
                } else {
                    var10000 = this.state;
                    var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }

                    conn.getRealConnection().close();
                    if (log.isDebugEnabled()) {
                        log.debug("Closed connection " + conn.getRealHashCode() + ".");
                    }

                    conn.invalidate();
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
                }

                ++this.state.badConnectionCount;
            }

        }
    }

  forceCloseAll(强制关闭所有),顾名思义,关闭所有的连接,这里就不细讲了,感兴趣的可以自己去看看源码。

  至此,我们获得了一个conn,其实就是下面两步,其实很多人刚开始学习的时候,会觉得源码羞涩难懂。其实不然,当我们首次使用JDBC连接数据库的时候,你可以没有感觉,但是当你写了第五遍,第十遍的时候,你在想,为什么我不能写一个ConnUtils来创建连接呢,那恭喜你,你就发明了UnpooledDataSource的基础版。还有在我们学习JDBC的时候,都会告诉你,连接使用完后要close掉,不close会导致内存泄露,但是在学完这个概念后,再来看下面这段代码,你会发现conn.prepareStatement是可以多次复制的,那我不关,重复使用是否可以。你想到我创建很多个连接,然后每次要用的时候,判断是否有可以空闲的连接,直接取不就可以了吗,那恭喜你,你就发明了PooledDataSource的基础版。

Class.forName("com.mysql.cj.jdbc.Driver");
 // 建立数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);

// 创建SQL查询语句
String sql = "SELECT * FROM mytable WHERE id = ?";
// 预编译SQL语句
pstmt = conn.prepareStatement(sql);  

        其实很多人觉得源码都是大神写的,其实很多框架的源码的代码量还没有你工作中的项目代码多,静下心来阅读才会学到很多东西。这里在插一句,很多面试会问的你工作中有没有使用过synchronized,很多时候,大部分打工人都用的很少(如果你用的很多,我只能说你厉害)。但是我们在阅读源码的时候,你就会发现PooledDataSource里面用了很多次,这里你可以考虑下使用的原因是什么。

   本文参考文档 "https://mybatis.net.cn/" mybaits中文文档(但貌似这个文档是盗版的)

   下一篇将细讲 session模块

图片

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值