第34次文章:SORM框架(四)

  • 2019 年 10 月 8 日
  • 笔记

本周我们在上周SORMv1.0框架的基础上对其进行升级,加入了一些设计模式,连接池等改造,大大的提高了整个框架运行的效率,得到现在的SORMv1.8版本。

SORMv1.8的完全版本的获取链接为:

链接:https://pan.baidu.com/s/1r9aCf3IUg4uH_Lj9OZIO6g 提取码:a4r8

与此同时,我们结束了第一阶段的java学习,开始进入数据库的学习阶段。


一、对SORM进行升级改造

1.对query进行简化操作

我们在编写SORMv1.0时,构建了一个MySqlQuery类,实现了Query接口。但是经过分析之后,MySqlQuery类中的每个方法的实现并不仅限于MySql数据库的操作,对于其他数据库也是可以使用这些方法进行操作的。所以我们将Query接口改变为一个抽象类,将之前所有MySqlQuery类中实现的方法全部搬移到Query抽象类中直接进行实现。同时,由于每种不同的数据库会具有不同的分页查询方法,所以我们在Query类中,增加一个分页查询抽象方法。提供给每个不同的数据库方法进行单独实现。

  /**     * 分页查询     * @param pageNum 第几页数据     * @param size 每页显示多少记录     * @return     */    public abstract Object queryPagenate(int pageNum,int size);

由于当前的SORM数据库框架并没有涉及到分页部分,所以我们现在仅仅是为以后预留出此接口,并不进行实现。

2.使用模板方法简化Query

当我们分析一下Query类中的queryRows和queryValue方法时,我们会发现两者的前半部分都是相同的,均为先获取与数据库的连接,然后传入sql语句,给sql语句设置参数,不同的地方在于两者的查询方式。所以我们使用模板方法模式,新建一个模板方法excueteQueryTemplate,将相同的部分一起进行实现,不同的部分,我们使用回调的方式,在各自的方法中进行实现。具体的实现如下:

  /**     * 采用模板方法模式将JDBC操作封装成模板,便于重用     * @param sql sql语句     * @param params sql的参数     * @param clazz 记录要封装到的java类     * @param back CallBack的实现类,实现回调     * @return     */    public Object excueteQueryTemplate(String sql,Object[] params,Class clazz,CallBack back) {        Connection conn = DBManager.getConn();      List list = null;    //存储查询结果的容器      PreparedStatement ps = null;      ResultSet rs = null;      try {        ps = (PreparedStatement) conn.prepareStatement(sql);        //给sql设置参数        JDBCUtils.handleParams(ps, params);        System.out.println(ps);        rs = ps.executeQuery();          return back.doExecute(conn, ps, rs);        } catch (Exception e) {        e.printStackTrace();        return null;      } finally {        DBManager.close(ps, conn);      }    }

为了实现回调,我们新建一个CallBack接口,然后增加一个doExcute方法,提供给后续的每个方法进行自主实现。

import java.sql.Connection;  import java.sql.PreparedStatement;  import java.sql.ResultSet;    public interface CallBack {    public Object doExecute(Connection conn,PreparedStatement ps,ResultSet rs);  }

经过模板方法模式的改造之后,我们可以看到queryRows和queryValue方法的代码如下:

  /**     * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中     * @param sql 查询语句     * @param clazz 封装数据的javabean类的class对象     * @param params sql的参数     * @return 查询到的结果     */    public List queryRows(final String sql,final Class clazz,final Object[] params) {      return (List)excueteQueryTemplate(sql, params, clazz, new CallBack() {          @Override        public Object doExecute(Connection conn, java.sql.PreparedStatement ps, ResultSet rs) {          List list = null;          try {            ResultSetMetaData metaData = rs.getMetaData();            //多行            while(rs.next()) {              if(list==null) {                list = new ArrayList();              }                Object rowObj = clazz.newInstance();  //调用Javabean的无参构造器                //多列 select username,pwd,age from user where id>? and age>18              for(int i=0;i<metaData.getColumnCount();i++) {                String columnName = metaData.getColumnLabel(i+1);                Object columnValue = rs.getObject(i+1);                  //调用rowObject对象的setUsername方法,将ColumnValue的值设置进去                ReflectUtils.invokeSet(rowObj, columnName, columnValue);              }              list.add(rowObj);            }          }catch(Exception e) {            e.printStackTrace();          }          return list;        }        });    }      /**     * 查询返回一个值(一行一列),并将该值返回     * @param sql 查询语句     * @param params sql的参数     * @return 查询到的结果     */    public Object queryValue(String sql,Object[] params) {        return excueteQueryTemplate(sql, params, null, new CallBack() {          @Override        public Object doExecute(Connection conn, java.sql.PreparedStatement ps, ResultSet rs) {          Object value = null;          try {            while(rs.next()) {              value = rs.getObject(1);            }          } catch (SQLException e) {            e.printStackTrace();          }          return value;        }      });    }

tips:使用模板方法模式之后,可以避免我们在两个方法中写重复性代码。我们都是通过调用回调函数CallBack对两个方法进行具体实现。在回调函数中,我们使用匿名内部类,直接对CallBack接口中的doExcute方法进行实现。大大简化了代码,使得代码具有更强的阅读性。

3.增加连接池(Connection Pool)

在我们学习连接的时候就已经知道,获取connection对象的底层实现是利用Socket套接字对象,是十分耗时的一项操作。所以在实际的使用中,我们对于connection的使用应该尽可能的节省。对connection对象进行重复操作,可以大大提高整个系统的效率。所以我们建立连接池对象的基本思想如下:

(1)将Connection对象放入List中,反复重用。

(2)在连接池的初始化的时候,事先放入多个连接池对象。当我们从连接池中取连接对象时,如果池中有可用连接,则将池中最后一个返回,同时,将该连接从池中remove,表示正在使用。如果池中无可用连接,则创建一个新的。

(3)在关闭连接时,不是真正关闭连接,而是将用完的连接放入池中。

所以根据上面我们对连接池实现思路,创建一个连接池类,具体实现如下:

package com.peng.sorm.pool;    import java.sql.Connection;  import java.sql.SQLException;  import java.util.ArrayList;  import java.util.List;    import com.peng.sorm.core.DBManager;    /**   * 连接池的类   */  public class DBConnPool {      /**     * 连接池对象     */    private static List<Connection> pool;    /**     * 最大连接数     */    private static final int POOL_MAX_SIZE=DBManager.getConf().getPoolMaxSize();    /**     * 最小连接数     */    private static final int POOL_MIN_SIZE=DBManager.getConf().getPoolMinSize();      /**     * 初始化连接池,使池中连接数达到最小值     */    public void initPool() {      if(pool == null) {        pool = new ArrayList<Connection>();      }      while(pool.size()< POOL_MIN_SIZE) {        pool.add(DBManager.createConn());        System.out.println("初始化池,池中连接数:"+pool.size());      }    }      /**     * 从池中取出一个连接     * @return     */    public synchronized Connection getConnection() {      int last_index = pool.size()-1;      Connection conn = pool.get(last_index);      pool.remove(last_index);      return conn;    }      /**     * 所谓的关闭连接并不是真的连接,只是将此连接放回到连接池中     * @param conn     */    public synchronized void close(Connection conn) {      if(pool.size()>=POOL_MAX_SIZE) {        try {          conn.close();        } catch (SQLException e) {          e.printStackTrace();        }      }else {        pool.add(conn);      }    }      public DBConnPool() {      initPool();    }  }

tips:由于每个客户的用途有所差异,所以对连接池的大小需求也有所不同。为了避免用户重新修改我们的代码。我们可以在配置文件中增设连接池的最大最小值选项,然后我们在代码中就可以直接通过配置文件来获取我们需要的值。从而避免了客户修改代码的风险。

测试连接池效率:

import java.util.List;    import com.peng.sorm.core.Query;  import com.peng.sorm.core.QueryFactory;  import com.peng.vo.EmpVO;    /**   * 测试连接池的效率   *   */  public class Test2 {    public static void test() {      Query q = QueryFactory.createQuery();      //复杂查询,将两个表进行关联查询      String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e "      +"join dept d on e.deptId=d.id ";      List<EmpVO> list2 = q.queryRows(sql2,EmpVO.class, null);      for(EmpVO e:list2) {        System.out.println(e.getEmpname()+" "+e.getAge()+" "+e.getId()+" "+e.getXinshui()+" "+e.getDeptName()+" "+e.getDeptAddr());        System.out.println("******************");      }    }      public static void main(String[] args) {      long a = System.currentTimeMillis();      for(int i=0;i<3000;i++) {        test();      }      long b = System.currentTimeMillis();      System.out.println(b-a);//不加连接池:14187毫秒. 增加连接池之后:1957    }  }

在使用连接池和不使用连接池时,最后的耗时结果如下

不使用连接池:

使用连接池:

tips:经过两者的对比之后就可以发现,提高的效率不是一点点,而是接近10倍。未使用的时候,耗时14187ms,使用连接池的时候,耗时1957ms,由此可见使用连接池对象时的优越性。


以上就是关于JAVA的SORM基础啦!下面我们进入数据库的正式学习!


二、数据库

1.相关概念

DB:数据库(datebase)存储数据的“仓库”。它保存了一系列有组织的数据。

DBMS:数据库管理系统(Datebase Management System)。数据库是通过DBMS创建和操作的容器

SQL:结构化查询语言(Structure Query Language):专门用来与数据库通信的语言

SQL的优点

(1)不是某个特定数据库供应商专用的语言,几乎所有DBMS都支持SQL

(2)简单易学

(3)虽然简单,但实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。

2.数据库的特点

(1)将数据放到表中,表再放到库中

(2)一个数据库中可以有多个表,每个表都有一个名字,用来标识自己。表名具有唯一性

(3)表具有一些特性,这些特定定义了数据在表中如何存储,类似于java中“类”的设计

(4)表由列组成,我们也称为字段。所有表都是由一个或多个列组成的,每一列类似java中的”属性“。

(5)表中的数据是按行存储的,每一行类似于java中的“对象”。

3.MySQL服务的启动和停止

方式一:计算机-右击管理-服务

方式二:通过管理员身份运行cmd窗口

net statrt 服务名(启动服务)

net stop 服务名(停止服务)

4.MySQL服务的登录和退出

方式一:通过mysql自带的客户端 只限于root用户

方式二:通过Windows自带的客户端

登录:mysql -h主机名 -P端口号 -u用户名 -p密码

退出:exit 或 ctrl+c

5.MySQL的常见命令

(1)查看当前所有的数据库

show databases;

(2)打开指定的库

use 库名;

(3)查看当前库中所有的表

show tables;

(4)查看其他库中的表

show tables from 库名;

(5)创建表

create table 表名(

列名 列类型,

列名 列类型,

………

);

(6)查看表结构

desc 表名;

(7)查看服务器的版本

方式一:登录到mysql服务器

select version();

方式二:没有登录到mysql服务端

mysql –version 或 mysql –V

6.MySQL的语法规范

(1)不区分大小写,但是建议关键字大写,表名、列名小写

(2)每条命令最好用分号结尾

(3)每条命令根据需要,可以进行缩进 或换行

(4)注释

单行注释:#注释文字

单行注释:— 注释文字(注意在两个单横线之后有一个空格)

多行注释:/* 注释文字 */