手写mybatis(二):DataSource

博客介绍了作者在学习Mybatis源码后,模拟实现Mybatis的数据库连接池功能,包括POOLED和UNPOOLED两种类型。通过定义DataSourceFactory接口和实现类,实现了数据源的创建与管理。PooledDataSource类用于管理连接池,通过连接池的创建、获取和归还连接的方法,确保了数据库连接的复用和有效管理。博客还提供了项目源码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文末附有源码地址

博客为代码完成后才开始整理,展示的代码都是最终代码

介绍:

个人学习,代码写的不够好,有点乱
通过学习mybatis源码模拟实现Mybatis(在完善)
已实现:
crud(实现XML配置,注解配置写过一点,原理差不多不写了)、
事务、
数据库连接池、
动态sql(一部分,原理没问题了,以后有时间再完善)、
一级二级缓存

mybatis配置文件中数据库源可配置为POOLED、UNPOLLED、JNDI。
这里我们模仿mybatis实现POOLED、UNPOLLED.

一、配置

<dataSource type="POOLED">

首先贴一张图:写好的类的目录:
在这里插入图片描述
在 XMLConfigBuilder类中(上一篇介绍过)通过以下代码读取配置。

if (config.getDataSourceType().equals("POOLED")) {
            config.setEnvironment(new Environment(null, null, new PooledDataSourceFactory(config).getDataSource()));
        } else if (config.getDataSourceType().equals("UNPOOLED")) {
            config.setEnvironment(new Environment(null, null, new UnpooledDataSourceFactory(config).getDataSource()));
        }
        return config;

二、DataSourceFactory

我们首先定义DataSourceFactory接口:

public interface DataSourceFactory {
    DataSource getDataSource();
}

我们利用Factory来获取DataSource,其中UnpooledDataSourceFactory 实现DataSourceFactory接口,PooledDataSourceFactory继承UnpooledDataSourceFactory 。
然后我们需要构建两个DataSource:POOLED和UNPOOLED,他们都实现DataSource接口:javax.sql.DataSource。

下图为UnpooledDataSource,主要保存基本配置。

public class UnpooledDataSource implements DataSource {
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();

    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            String driverName = driver.getClass().getName();
            registeredDrivers.put(driverName, driver);
        }
    }

    private String driver;
    private String url;
    private String username;
    private String password;
    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;
    private Integer defaultNetworkTimeout;

三、最重要部分

而对于PooledDataSource,需要初始化创建Connection并保存,在需要Connection时将其取出,用完后放回。原理貌似十分的简单,但仔细看mybatis源码,会发现许多细节和巧妙的实现。

public class PooledDataSource implements DataSource {
    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 PoolState poolState;
    private int expectedConnectionTypeCode;

    public PooledDataSource() {
        this.dataSource = new UnpooledDataSource();
    }

    public PooledDataSource(UnpooledDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public PooledDataSource(Configuration config) {

        this.dataSource = new UnpooledDataSource(config);
        try {
            Connection conn = dataSource.getConnection();
            PooledConnection pooledConnection = new PooledConnection(conn, this);
            this.poolState = new PoolState();
            for (int i = 0; i < poolMaximumIdleConnections; i++) {
                poolState.idleConnections.add(pooledConnection);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public PooledConnection popConnection(String username, String password) throws SQLException {
        PooledConnection con = null;
        while (con == null) {
            synchronized (poolState) {
                if (poolState.idleConnections.size() > 0) {
                    con = poolState.idleConnections.remove(0);
                } else {
                    if (poolState.activeConnections.size() < poolMaximumActiveConnections) {
                        Connection conn = dataSource.getConnection();
                        con = new PooledConnection(conn, this);

                    } else {
                        PooledConnection oldestActiveConnection = poolState.activeConnections.get(0);
                        if (oldestActiveConnection.isValid()) {
                            poolState.activeConnections.remove(oldestActiveConnection);
                        }
                    }

                }
            }
        }
        poolState.activeConnections.add(con);
        return con;
    }

    protected void pushConnection(PooledConnection pooledConnection) {
        synchronized (poolState) {
            poolState.activeConnections.remove(pooledConnection);
            if (pooledConnection.isValid()) {
                if (poolState.idleConnections.size() < poolMaximumIdleConnections) {
                    PooledConnection newConn = new PooledConnection(pooledConnection.getRealConnection(), this);
                    poolState.idleConnections.add(newConn);

                } else {
                    try {
                        pooledConnection.getRealConnection().close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }


}

一些属性标明了数据库连接池的默认配置:

 protected int poolMaximumActiveConnections = 10;
    protected int poolMaximumIdleConnections = 5;
    protected int poolMaximumCheckoutTime = 20000;
    protected int poolTimeToWait = 20000;
    protected int poolMaximumLocalBadConnectionTolerance = 3;

有一个构造器标明构造时初始化连接,popConnection和pushConnection方法分别为拿出和放入连接。
对于Connection,用PooledConnection类代替:

class PooledConnection implements InvocationHandler {
    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    private final int hashCode;
    private final PooledDataSource dataSource;
    private final Connection realConnection;
    private final Connection proxyConnection;
    private long checkoutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valid;

注意其中的realConnectionproxyConnection,一个为原始的一个为代理的,那为什么要这么做呢?

private static final String CLOSE = "close";
@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (CLOSE.equals(methodName)) {
            dataSource.pushConnection(this);
            return null;
        }
        return method.invoke(realConnection, args);
    }

从以上代码可以看出,当调用proxyConnection的close方法时,实际是将连接放入池中。大家肯定注意到其中还有一个类PoolState:

public class PoolState {

    protected final List<PooledConnection> idleConnections = new ArrayList<>();
    protected final List<PooledConnection> activeConnections = new ArrayList<>();
    protected PooledDataSource dataSource;

    public PoolState(PooledDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public PoolState() {

    }
}

这个类用来存放PooledConnection并且分为空闲的和正在使用的连接。在PooledDataSource类中我们已经贴出popConnectionpushConnection方法。

项目地址

github:https://github.com/Alice-175/Mybaits

gitee:https://gitee.com/alice-175/Mybaits

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值