文末附有源码地址
博客为代码完成后才开始整理,展示的代码都是最终代码
介绍:
个人学习,代码写的不够好,有点乱
通过学习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;
注意其中的realConnection和proxyConnection,一个为原始的一个为代理的,那为什么要这么做呢?
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类中我们已经贴出popConnection和pushConnection方法。
项目地址
github:https://github.com/Alice-175/Mybaits
gitee:https://gitee.com/alice-175/Mybaits