专栏首页Java小白成长之路第33次文章:SORM框架(三)

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

本周将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属性等等。最后将所得结果输出到控制台上,得到上图显示出来的结果。


本文分享自微信公众号 - Java小白成长之路(Java_xiaobai),作者:鹏程万里

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 第29次文章:事务机制

    -连接到数据库上,并执行一条DML语句(insert、update或delete)。

    鹏-程-万-里
  • 刷题第3篇:重复字符串的删除

    给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。你需要对 s 重复进行...

    鹏-程-万-里
  • 第18次文章:JVM中的类加载机制

    -主要用来加载java的核心库,是用原生代码C语言来实现的,并不继承自java.lang.ClassLoader。

    鹏-程-万-里
  • Groovy-16.数据库

    UPDATE和DELETE操作都是写好SQL然后通过execute执行,但是需要commit()提交(在后文“提交”中介绍)。

    悠扬前奏
  • 记一次神奇的sql查询经历,group by慢查询优化

    现网出现慢查询,在500万数量级的情况下,单表查询速度在30多秒,需要对sql进行优化,sql如下:

    梁规晓
  • Oracle AWR 阙值影响历史执行计划

          最近有网友提到为什么在dba_hist_sql_plan中无法查看到sql语句的历史执行计划,对于这个问题是由于缺省情况下,Oracle 设定的阙值...

    Leshami
  • sql格式化工具

    该工具支持oracle、mysql、sql server等关系型数据库,能让你看到sql美化后的清晰的结构,可运用于对复杂SQL语句的分析或者是程序代码优化上,...

    ixiaoyang8
  • 针对plsql developer使用做的三个小设置

    1、原来大家在sql窗口写多条sql语句,如果点击“执行”,那么会执行窗口下的所有语句,如果向执行所要的语句,必须选定它。

    williamwong
  • 你知道这种开发模式能更好的帮你排错吗?

    很多时候我们在开发一个项目的时候写着写着sql语句报错了?(这里多指使用框架开发,当然也有原声sql语句),之后有时候会扎耳挠腮,看来看去都感觉自己的sql语句...

    思梦php
  • JDBC的基本使用流程

    JDBC的基本使用流程: 1 导入jar包: 导入ojdbc6.jar,在项目上右键 builder path–>add to builder path. ...

    葆宁

扫码关注云+社区

领取腾讯云代金券