第33次文章:SORM框架(三)

  • 2019 年 10 月 8 日
  • 笔记

本周将SORM框架的基本功能已经全部填充起来了,形成了SORM框架的1.0版本,有兴趣的同学可以通过下面的链接获取源码哟!下周将进入SORM框架的升级阶段,在现在1.0版本的基础上,加入设计模式等内容,使得这个架构具有更强的可扩性。


下面我们结合这段SORM框架源码以及基本用法进行一个相关介绍。

在我们日常使用数据库时,一般的操作为增、删、改、查。我们通过对4种操作的分析,可以将增删改归为java对象到数据库的操作,而查询操作可以将其归为数据库到java对象的操作。于是,我们根据数据在java和数据库中的传递方向,将所有操作分为了两大类进行讲解。

一、从java对象到数据库的操作

从java对象到数据库有增删改三类操作,虽然功能不同,但是在实现过程中,有一些基本的思路是相通的。为了简化用户的操作,我们一般都希望可以通过向三种方法中传递被操作的java对象,然后使对象和数据库之间产生相应的联系,最终改变数据库中存储的数据。

1.准备工作

在具体的实现的时候,我们遇到的一个问题:如何将传递的java对象与数据库中的表进行对应?

解决方案一:我们在对表格以及java类进行命名的时候,遵循了一个基本原则,java类的名称与表格中各类名称只有首字母的大小写不相同,其余的部分均相同,我们如果利用这种原则,可以通过字符串的匹配进行判断,选择每个java类对应的表格,但是显然会比较繁琐。

解决方案二:我们在根据数据库中的信息生成po包中的各个java类的时候,我们可以将每个类和表进行关联,存储在一个Map中。这样就可以在我们利用java类寻找关联表格的时候节省大量的时间,提高效率。

在我们的实践中,采取的就是解决方案二。在TableContex类中增设了updateJavaPOFile()方法,用于根据表结构来更新po包中的java类,以及loadPoTables()方法,用于加载po包的类,将其与表格进行关联。

经过上面的基础准备之后,我们对每个传入的java类对象进行操作的基本思想为:首先从给出的对象转换到class类,根据class类获取与之对应的表。然后根据class中的属性名等,开始拼接sql字符串。最后在java中执行sql语句,改变数据库中的数据内容。

2.excuteDML方法

由于增删改都是需要在java中执行mysql的语句来对数据库进行操作,每一种操作最后都需要执行sql语句,所以我们可以单独封装一个执行sql语句的方法,便于后续的重用。方法的源码如下所示:

  /**     * 直接执行一个DML语句     * @param sql sql语句     * @param params 参数     * @return 执行sql语句后影响记录的行数     */    @Override    public int excuteDML(String sql, Object[] params) {      Connection conn = DBManager.getConn();      int count = 0;      PreparedStatement ps = null;      try {        ps = (PreparedStatement) conn.prepareStatement(sql);        JDBCUtils.handleParams(ps, params);        System.out.println(ps);        count = ps.executeUpdate();      } catch (SQLException e) {        e.printStackTrace();      } finally {        DBManager.close(ps, conn);      }      return count;    }

tips:在增删改三种操作中,每一种操作执行sql语句的过程都是相同的,不同的仅仅是sql语句以及传递的参数而已,所以当我们封装好excuteDML方法之后,我们就可以在增删改当中,专注于sql语句的拼接即可,提高效率。

3.插入操作

下面我们介绍一下插入操作insert方法,源码如下:

  /**     * 将一个对象存储到数据库中     * 把对象中不为null的属性往数据库中存储!如果数字为null则放0.     * @param object 要存储的对象     */    @Override    public void insert(Object obj) {      //obj--->表中。insert into 表名 (id,uname,pwd) values (?,?,?)      Class clazz = obj.getClass();      TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);        StringBuilder sql = new StringBuilder( "insert into "+tableInfo.getTname()+" (");      int countNotNullField = 0;//计算不为null的属性值      List<Object> params = new ArrayList<Object>();//存储sql的参数对象      //获取所有不为空的属性      Field[] fs = clazz.getDeclaredFields();      for(Field f:fs) {        String fieldName = f.getName();//获取属性名        Object fieldValue = ReflectUtils.invokeGet(fieldName, obj);//获取属性对应的值        if(fieldValue != null) {//检查属性值是否为空          countNotNullField++;          sql.append(fieldName+",");          params.add(fieldValue);        }      }      sql.setCharAt(sql.length()-1, ')');//注意,在java中,单引号之间的字母被识别为char,双引号之间的字母被识别为Sring      sql.append(" values (");      for(int i=0;i<countNotNullField;i++) {        sql.append("?,");      }      sql.setCharAt(sql.length()-1, ')');      excuteDML(sql.toString(), params.toArray());    }

tips:在插入一个对象的时候,该对象就代表着表格中的一行记录。

我们首先需要知道此对象中,各个属性的值,也就是我们需要向sql语句中传递的参数列表,所以需要将对象中不为null的属性值获取出来,然后对属性名称拼接sql字符串,最后调用excuteDML方法,向方法中传入拼接好的sql语句,以及对应的参数数组params即可。

4.删除操作

删除操作也和上面的代码类似,主要就是拼接删除操作指令。

  /**     * 删除clazz表示类对应的表中的记录(指定主键值id的记录)     * @param clazz     * @param id     */    @Override    public void delete(Class clazz, Object id) {      //Emp.class,2 ----> delete from emp where id=2      //通过Class对象找TableInfo      TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);      //获得主键      ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();      String sql = "delete from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=?";      excuteDML(sql, new Object[] {id});    }      @Override    public void delete(Object obj) {      Class clazz = obj.getClass();//获取class对象      TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);//获取对象对应的表信息      ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();//主键        //通过反射机制,调用属性对应的get方法或set方法      Object priKeyValue = ReflectUtils.invokeGet(onlyPriKey.getName(), obj);      delete(clazz,priKeyValue);    }  

tips:在删除过程中,我们首先根据需要删除的对象,获取表中的主键,因为在删除时,我们只有根据主键的值来作为根据,才不会误删其他记录。所以最后向excuteDML方法中传递的参数即为需要删除的主键的值。在我们现阶段编写的SORM框架中,我们只支持数据库中为一个主键的情况。如果考虑到联合主键,那么情况将会较为复杂,留给以后考虑。

5.更改操作

方法如下:

  @Override    public int update(Object obj, String[] fieldNames) {      //obj{"uname","pwd"}----> update 表名 set uname=?,pwd=? where id=?      Class clazz = obj.getClass();      TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);      StringBuilder sql = new StringBuilder("update "+tableInfo.getTname()+" set ");        List<Object> params = new ArrayList<Object>();      ColumnInfo priKey = tableInfo.getOnlyPriKey();//获取主键      for(String fieldName:fieldNames) {        params.add(ReflectUtils.invokeGet(fieldName, obj));        sql.append(fieldName+"=?,");      }      sql.setCharAt(sql.length()-1, ' ');      sql.append("where "+priKey.getName()+"=? ");        params.add(ReflectUtils.invokeGet(priKey.getName(),obj));//加入主键的值      return excuteDML(sql.toString(), params.toArray());    }

tips:更改操作和删除具有一点相像之处,主要在于两者都是依赖对象的主键来对数据库操作。在更改操作中,我们传入需要更改的属性名称,在对象obj中获取对应的属性值,最后再拼接sql语句字符串,执行更新操作。

二、从数据库到java对象的操作

从数据库中查询操作,由于查询的内容都是属于外部传输,所以我们直接向方法中提供sql语句以及相关的参数即可。

整个方法的基本思路为:首先与数据库进行连接,获取连接connection对象,然后通过查询语句返回查询的结果,最后将查询得到的结果封装在用户需要使用的类中。

1.多行多列查询操作

对于查询,有时候会涉及到查询得到的结果是多个对象的多个属性值,面对这样的情况,我们需要按照行和列的不同维度去封装每一个返回对象结果。

  /**     * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中     * @param sql 查询语句     * @param clazz 封装数据的javabean类的class对象     * @param params sql的参数     * @return 查询到的结果     */    @Override    public List queryRows(String sql, Class clazz, Object[] params) {      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();          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 (SQLException e) {        e.printStackTrace();      } catch (InstantiationException e) {        // TODO Auto-generated catch block        e.printStackTrace();      } catch (IllegalAccessException e) {        // TODO Auto-generated catch block        e.printStackTrace();      } finally {        DBManager.close(ps, conn);      }      return list;    }

tips:在整个查询的过程中,我们使用两个循环来分别封装每个查询到的对象。多行对应着多个对象,多列对应着每个对象的不同的属性。将查询到的每个属性封装到提前创建好的类中,把所得每个类加入到链表中,最后返回链表。

2.查询其他类型

对应于多行多列的查询,还有一行多列,和一行一列,以及单独返回一个数字。

  /**     * 查询返回一行记录,并将每行记录封装到clazz指定的类的对象中     * @param sql 查询语句     * @param clazz 封装数据的javabean类的class对象     * @param params sql的参数     * @return 查询到的结果     */    @Override    public Object queryUniqueRow(String sql, Class clazz, Object[] params) {      List list = queryRows(sql, clazz, params);      return (list==null&&list.size()>0)?null:list.get(0);    }      /**     * 查询返回一个值(一行一列),并将该值返回     * @param sql 查询语句     * @param params sql的参数     * @return 查询到的结果     */    @Override    public Object queryValue(String sql, Object[] params) {      Connection conn = DBManager.getConn();      Object value = 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();        while(rs.next()) {          value = rs.getObject(1);        }      } catch (SQLException e) {        e.printStackTrace();      } finally {        DBManager.close(ps, conn);      }      return value;    }      /**     * 查询返回一个数字(一行一列),并将该值返回     * @param sql 查询语句     * @param params sql的参数     * @return 查询到的数字     */    @Override    public Number queryNumber(String sql, Object[] params) {      return (Number)queryValue(sql,params);//查询一个数字,返回对象    }

tips:当我们将多行多列的查询实现之后,剩余的几种方法实现起来都较为容易。对于一行多列的情况,我们可以直接调用多行多列的方法queryRows(),从返回得到的list取出唯一的对象即可。

3.简单的测试一下查询方法的效果

  public static void main(String[] args) {      List<Emp> list = new MySqlQuery().queryRows("select id,empname,age,birthday from emp where age>? and salary<?",          Emp.class, new Object[] {10,10000});        for(Emp e:list) {        System.out.println(e.getEmpname()+" "+e.getAge()+" "+e.getId()+" "+e.getBirthday());        System.out.println("******************");      }        //复杂查询,将两个表进行关联查询      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 = new MySqlQuery().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("******************");      }    }

所得结果:

tips:在上面这段测试代码中,我们进行了两次查询。我们关注一下后面的这个复杂查询。

我们的数据库中有两张表格,分别为dept和emp表格,两张表格的分别如下所示:

可以很明显的看到一点,在emp和dept可以通过deptId进行关联。当我们单独查询emp表格的时候,无法显示出每个雇员的办公地址。当我们使用复杂查询,联合两张表格的时候,我们需要重新定义一个可以封装查询到的各个属性数据的类。所以我们根据自己的需要,重新定义了一个EmpVO类。在此类中,加入了需要封装的属性,比如emp中的Name属性,以及dept中的addr属性等等。最后将所得结果输出到控制台上,得到上图显示出来的结果。