前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架。一直没有时间去补充相应的文档,现在正好抽时间去整理下。通过思路历程和代码注释,一方面重温下知识,另一方面准备后期去完善这个框架。
参照传统的JDBC连接数据库过程如下,框架所做的事情就是把这些步骤进行封装。
// 1\. 注册 JDBC 驱动Class.forName(JDBC_DRIVER);
// 2\. 打开链接conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 3\. 实例化statementstmt = conn.prepareStatement(sql);
// 4\. 填充数据stmt.setString(1,id);
// 5\. 执行Sql连接ResultSet rs = stmt.executeQuery();
// 6\. 结果查询while(rs.next()){
// 7\. 通过字段检索
int id = rs.getInt("id");
String name = rs.getString("name");
String url = rs.getString("url");}
上述是执行Java连接数据库通用语句,ORM框架就是对象关系映射框架。我们需要做的重点是步骤4和7,将对象字段填充至数据库,将数据库的内容取出作为对象返回。了解了基本的方法后,开始动手写一个框架了。
开始,需要构建一个驱动注册类,这个类主要用来对数据库驱动进行构造。详见代码块1
数据库驱动类(com.simple.ibatis.dirver)
/** * @Author xiabing * @Desc 驱动注册中心,对数据库驱动进行注册的地方 **/public class DriverRegister { /* * mysql的driver类 * */ private static final String MYSQLDRIVER = "com.mysql.jdbc.Driver"; /* * 构建driver缓存,存储已经注册了的driver的类型 * */ private static final Map registerDrivers = new ConcurrentHashMap<>(); /* * 初始化,此处是将DriverManager中已经注册了驱动放入自己缓存中。当然,你也可以在这个方法内注册 常见的数据库驱动,这样后续就可以直接使用,不用自己手动注册了。 * */ static { Enumeration driverEnumeration = DriverManager.getDrivers(); while (driverEnumeration.hasMoreElements()){ Driver driver = driverEnumeration.nextElement(); registerDrivers.put(driver.getClass().getName(),driver); } } /* * 加载mysql驱动,此个方法可以写在静态代码块内,代表项目启动时即注册mysql驱动 * */ public void loadMySql(){ if(! registerDrivers.containsKey(MYSQLDRIVER)){ loadDriver(MYSQLDRIVER); } } /* * 加载数据库驱动通用方法,并注册到registerDrivers缓存中,此处是注册数据库驱动的方法 * */ public void loadDriver(String driverName){ Class> driverType; try { // 注册驱动,返回驱动类 driverType = Class.forName(driverName); // 将驱动实例放入驱动缓存中 registerDrivers.put(driverType.getName(),(Driver)driverType.newInstance()); }catch (ClassNotFoundException e){ throw new RuntimeException(e); }catch (Exception e){ throw new RuntimeException(e); } }}
驱动注册后,需要建立数据库连接。但是框架如果一个请求建一个连接,那木势必耗费大量的资源。所以该框架引入池化的概念,对连接进行池化管理。详情见代码2
数据源类(com.simple.ibatis.datasource)
PoolConnection
/** * @Author xiabing * @Desc 连接代理类,是对Connection的一个封装。除了提供基本的连接外,还想记录 这个连接的连接时间,因为有的连接如果一直连接不释放,那木我可以通过查看 这个连接已连接的时间,如果超时了,我可以主动释放。 **/public class PoolConnection { // 真实的数据库连接 public Connection connection; // 数据开始连接时间 private Long CheckOutTime; // 连接的hashCode private int hashCode = 0; public PoolConnection(Connection connection) { this.connection = connection; this.hashCode = connection.hashCode(); } public Long getCheckOutTime() { return CheckOutTime; } public void setCheckOutTime(Long checkOutTime) { CheckOutTime = checkOutTime; } // 判断两个PoolConnection对象是否相等,其实是判断其中真实连接的hashCode是否相等 @Override public boolean equals(Object obj) { if(obj instanceof PoolConnection){ return connection.hashCode() == ((PoolConnection) obj).connection.hashCode(); }else if(obj instanceof Connection){ return obj.hashCode() == hashCode; }else { return false; } } @Override public int hashCode() { return hashCode; }}
NormalDataSource
/** * @Author xiabing * @Desc 普通数据源,这个数据源是用来产生数据库连接的。来一个请求就会建立一个数据库连接,没有池化的概念 **/public class NormalDataSource implements DataSource{ // 驱动名称 private String driverClassName; // 数据库连接URL private String url; // 数据库用户名 private String userName; // 数据库密码 private String passWord; // 驱动注册中心 private DriverRegister driverRegister = new DriverRegister(); public NormalDataSource(String driverClassName, String url, String userName, String passWord) { // 初始化时将驱动进行注册 this.driverRegister.loadDriver(driverClassName); this.driverClassName = driverClassName; this.url = url; this.userName = userName; this.passWord = passWord; } // 获取数据库连接 @Override public Connection getConnection() throws SQLException { return DriverManager.getConnection(url,userName,passWord); } // 移除数据库连接,此方法没有真正移除,只是将连接中未提交的事务进行回滚操作。 public void removeConnection(Connection connection) throws SQLException{ if(!connection.getAutoCommit()){ connection.rollback(); } } // 获取数据库连接 @Override public Connection getConnection(String username, String password) throws SQLException { return DriverManager.getConnection(url,username,password); } // 后续方法因为没有用到,所有没有进行重写了 @Override public T unwrap(Class 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 void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; }}
PoolDataSource
/** * @Author xiabing * @Desc 池化线程池,对连接进行管理 **/public class PoolDataSource implements DataSource{ private Integer maxActiveConnectCount = 10; // 最大活跃线程数,此处可根据实际情况自行设置 private Integer maxIdleConnectCount = 10; // 最大空闲线程数,此处可根据实际情况自行设置 private Long maxConnectTime = 30*1000L; // 连接最长使用时间,在自定义连接中配置了连接开始时间,在这里来判断该连接是否超时 private Integer waitTime = 2000; // 线程wait等待时间 private NormalDataSource normalDataSource; // 使用normalDataSource来产生连接 private Queue activeConList = new LinkedList<>(); // 存放活跃连接的队列 private Queue idleConList = new LinkedList<>(); // 存放空闲连接的队列 public PoolDataSource(String driverClassName, String url, String userName, String passWord) { this(driverClassName,url,userName,passWord,10,10); } public PoolDataSource(String driverClassName, String url, String userName, String passWord,Integer maxActiveConnectCount,Integer maxIdleConnectCount) { // 初始化normalDataSource,因为normalDataSource已经封装了新建连接的方法 this.normalDataSource = new NormalDataSource(driverClassName,url,userName,passWord); this.maxActiveConnectCount = maxActiveConnectCount; this.maxIdleConnectCount = maxIdleConnectCount; } /** * @Desc 获取连接时先从空闲连接列表中获取。若没有,则判断现在活跃连接是否已超过设置的最大活跃连接数,没超过,new一个 * 若超过,则判断第一个连接是否已超时,若超时,则移除掉在新建。若未超时,则wait()等待。 **/ @Override public Connection getConnection(){ Connection connection = null; try { connection = doGetPoolConnection().connection; }catch (SQLException e){ throw new RuntimeException(e); } return connection; } public void removeConnection(Connection connection){ PoolConnection poolConnection = new PoolConnection(connection); doRemovePoolConnection(poolConnection); } private PoolConnection doGetPoolConnection() throws SQLException{ PoolConnection connection = null; while (connection == null){ // 加锁 synchronized (this){ // 判断是否有空闲连接 if(idleConList.size() < 1){ // 判断活跃连接数是否已经超过预设的最大活跃连接数 if(activeConList.size() < maxActiveConnectCount){ // 如果还可以新建连接,就新建一个连接 connection = new PoolConnection(normalDataSource.getConnection()); }else { // 走到这一步,说明没有空闲连接,并且活跃连接已经满了,则只能看哪些连接超时,判断第一个连接是否超时 PoolConnection poolConnection = activeConList.peek(); // 这就是我为啥要给数据库连接加连接时间了 if(System.currentTimeMillis() - poolConnection.getCheckOutTime() > maxConnectTime){ // 若第一个连接已经超时了,移除第一个活跃连接 PoolConnection timeOutConnect = activeConList.poll(); if(!timeOutConnect.connection.getAutoCommit()){ // 如果该连接设的是非自动提交,则对事物进行回滚操作 timeOutConnect.connection.rollback(); } // 置为空,让垃圾收集器去收集 timeOutConnect.con