首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第33次文章:SORM框架(三)

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

作者头像
鹏-程-万-里
发布2019-09-27 15:47:30
9750
发布2019-09-27 15:47:30
举报

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


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、从java对象到数据库的操作
  • 二、从数据库到java对象的操作
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档