简单ORM论道

ORM定义:对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换

万物同源,始于JDBC
  • 加载JDBC驱动程序

  • 根据参数建立数据库连接

  • 执行查询

  • 处理结果集

  • 处理异常,关闭连接

其他所有框架对关系数据库的操作都是基于以上步骤的封装。

基于JdbcTemplate实现简单的ORM,ORM的关注点是表跟实体的映射。那么关注点就在于
  • 获取实体的定义

  • 获取表的定义

  • 实体跟表字段的一一映射

  • 将对实体的操作转化为sql进而转化为对表的操作

简单InsertBean实现

方法定义

/**
 * 往数据库指定的表插入一个BEAN的数据
 * @param tableName
 * @param keyId
 * @param dataBean
 * @return
 */
public Long insertBean(String tableName,String keyColumn,Object dataBean) throws DbException;

实现的模板方法

Map<String,Field> fieldMap = getClassFieldMap(bean.getClass());
List<String> fieldList = getTableFieldList(tableName);
Map<String,Object> dataMap = constructInsertMap(fieldMap, fieldList, keyColumn);
insertMap(dataMap,tableName)

insertMap(Map<String, ?> dataMap, String tableName) 具体实现

wrapInsertMap(dataMap); //对dataMap进行包装、再一次赋空值、日期处理等等
SimpleJdbcInsert insert = getSimpleJdbcInsert();    
insert.withTableName(tableName).usingColumns(getCols(dataMap));
insert.execute(insertMap);

更直白的描述

InsertBeanActivity.png

获取实体属性具体实现 getClassFieldMap(bean.getClass())

    /**
     * 取得类的属性名
     * @param clazz
     * @return
     */
    public static Map<String,Field> getClassFieldMap(Class<?> clazz){
        Map<String,Field> rtMap = null;
        if(clazz != null){
            if(null == rtMap){
                rtMap = new HashMap<String,Field>();
                Field[] fields = clazz.getDeclaredFields();
                if(fields != null && fields.length>0){
                    Field.setAccessible(fields,true);
                    Field f = null;
                    for (int j = 0; j < fields.length; j++) {
                        f = fields[j];
                        rtMap.put(f.getName().toLowerCase(), f);
                    }
                }
            }           
        }
        return rtMap;       
    }

获取表的字段具体实现(基于Oracle实现) getTableFieldList(tableName)

    public List<String> getTableFieldList(String tableName){       
        List<String> list = null;
        DbTable dbTable = getDbTableDefine(tableName);
        if(dbTable != null && dbTable.getFieldMap() != null && !dbTable.getFieldMap().isEmpty()){
            list = Arrays.asList(dbTable.getFieldMap().keySet().toArray(new String[dbTable.getFieldMap().keySet().size()]));
        }
        return list;
    }   
    
    public DbTable getDbTableDefine(String tableName){
        try {           
            return DbUtil.getDbTableDefine(tableName, this.getJdbcTemplate());
        } catch (Exception e) {         
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取Table的配置
     * @param tableName
     * @param conn
     * @return
     */
    public static DbTable getDbTableDefine(String tableName, JdbcTemplate jdbcTemplate){
        
        if(StringUtils.isEmpty(tableName)){
            return null;
        }
        WebRmInforesCacheService cacheService = SpringContextUtil.getBean("com.gpdi.infores.services.bs.WebRmInforesCacheServiceImpl");
        tableName = tableName.toUpperCase();
        DbTable dbTable = cacheService.getData("DbTable_"+tableName);
        if(dbTable == null){            
            Connection conn = null;//DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            
            ResultSet rsTable = null;//取表
            ResultSet rsField = null;//取字段
            
            ResultSet rsKey = null;//取主键
            
            ResultSet indexInfo = null;//索引
            
            Statement stmt = null;//字段注释
            ResultSet rsRemark = null;//字段注释
            try {               
                conn = jdbcTemplate.getDataSource().getConnection();
                DatabaseMetaData dm = conn.getMetaData();
                String catalog = conn.getCatalog();
                rsTable = dm.getTables(catalog, dm.getUserName(), tableName.toUpperCase(), new String[]{"TABLE", "SYNONYM"});
                if(rsTable.next()) {
                    tableName = rsTable.getString("TABLE_NAME");
                    if(StringUtils.isNotEmpty(tableName)){
                        tableName = tableName.toUpperCase();
                        dbTable = new DbTable();
                        dbTable.setUserName(dm.getUserName());
                        dbTable.setTableName(tableName);
                        LinkedHashMap<String,DbTableField> fields = new LinkedHashMap<String,DbTableField>();
                        //取主键
                        rsKey = dm.getPrimaryKeys(catalog, null, tableName);
                        String keyField = null;
                        while (rsKey.next()) {
                            keyField = rsKey.getString("COLUMN_NAME");//组合组键时、只要最后一个主键
                        }
                        //取字段
                        rsField = dm.getColumns(catalog, null, tableName, null);
                        while (rsField.next()) {
                            DbTableField f = new DbTableField();
                            
                            String fieldName = rsField.getString("COLUMN_NAME").toUpperCase();
                            f.setName(fieldName);//参数值可参考dm.getColumns(catalog, null, tableName, null)的帮助文档
                            f.setSqlType(new Integer(rsField.getString("DATA_TYPE")).intValue());//如:java.sql.Types.INTEGER
                            f.setTypeName(rsField.getString("TYPE_NAME"));//如:BIGINT
                            f.setSize(rsField.getString("COLUMN_SIZE"));//用户定义的字段长度
                            f.setBufferLength(rsField.getString("BUFFER_LENGTH"));//字段缓冲区大小
                            f.setDecimal(rsField.getString("DECIMAL_DIGITS"));//精度
                            f.setNullable("1".equals(rsField.getString("NULLABLE")));//取值0||1,1允许空值,0不允许空值
                            f.setDefaultValue(rsField.getString("COLUMN_DEF"));//默认值
                            f.setPosition(rsField.getString("ORDINAL_POSITION"));//在表中的位置
                            f.setRemark(rsField.getString("REMARKS"));//注释为空的、下面会有专门取注释
                            
                            if(StringUtils.isNotEmpty(keyField) && keyField.equalsIgnoreCase(fieldName)){
                                f.setPrimarykey(true);
                                dbTable.setKeyField(f);//主键字段
                            }
                            fields.put(fieldName.toLowerCase(),f);
                        }
                        //字段注释
                        String remarkSQL = "select c.* from all_col_comments c where lower(c.table_name) = lower('"+tableName+"')";
                        if(StringUtils.isNotEmpty(dm.getUserName()))
                            remarkSQL += " and c.owner='"+dm.getUserName()+"'";
                        stmt = conn.createStatement();
                        rsRemark = stmt.executeQuery(remarkSQL);
                        while(rsRemark.next()){
                            String fieldNmae = rsRemark.getString("COLUMN_NAME");
                            fields.get(fieldNmae.toLowerCase()).setRemark(rsRemark.getString("COMMENTS"));
                        }
                        //表名注释
                        remarkSQL = "select c.* from all_tab_comments c where lower(c.table_name) = lower('"+tableName+"')";
                        if(StringUtils.isNotEmpty(dm.getUserName())) remarkSQL += " and c.owner='"+dm.getUserName()+"'";
                        ResultSet rstRemark = stmt.executeQuery(remarkSQL);
                        if(rstRemark.next()){
                            dbTable.setTableCnName(rstRemark.getString("COMMENTS"));
                        }
                        dbTable.setFieldMap(fields);        
                        cacheService.cacheData("DbTable_"+tableName, dbTable);
                       
                    }
                }else{
                    dbTable = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                close(rsRemark);
                close(stmt);
                close(indexInfo);
                close(rsKey);
                close(rsField);
                close(rsTable);
                close(conn);
            }
        }
            
        return dbTable;
    }
updateBean、deleteBean类似不再详述
selectBean

jdbcTemplate中实现了一个BeanPropertyRowMapper或者可以自己实现一个更加通用的RowMapper

好奇心害死猫--------------JdbcTemplate后面做了什么

几个重要的类与接口
  • SimpleJdbcInsertOperations 定义了一些简单插入基本操作的接口
  • AbstractJdbcInsert 抽象插入类、实现了几乎所有的插入相关操作
  • SimpleJdbcInsert 插入操作的具体实现、简单将请求委托给父类
  • TableMetaDataContext 保存与表相关的信息、提供一些插入构造参数的方法
关系------------只画出重要操作与属性
Relation.png
流程

insert.withTableName(tableName).usingColumns(getCols(dataMap))

注册需要插入的表至TableMeteDataContext、插入字段则保存至AbstractJdbcInsert的declaredColumns中

insert.execute(insertMap)

插入具体流程


InsertFlow.png

重要方法

  • createInsertString

    /**
       * Build the insert string based on configuration and metadata information
       * @return the insert string to be used
       */
      public String createInsertString(String... generatedKeyNames) {
          Set<String> keys = new LinkedHashSet<String>(generatedKeyNames.length);
          for (String key : generatedKeyNames) {
              keys.add(key.toUpperCase());
          }
          StringBuilder insertStatement = new StringBuilder();
          insertStatement.append("INSERT INTO ");
          if (getSchemaName() != null) {
              insertStatement.append(getSchemaName());
              insertStatement.append(".");
          }
          insertStatement.append(getTableName());
          insertStatement.append(" (");
          int columnCount = 0;
          for (String columnName : getTableColumns()) {
              if (!keys.contains(columnName.toUpperCase())) {
                  columnCount++;
                  if (columnCount > 1) {
                      insertStatement.append(", ");
                  }
                  insertStatement.append(columnName);
              }
          }
          insertStatement.append(") VALUES(");
          if (columnCount < 1) {
              if (this.generatedKeyColumnsUsed) {
                  logger.info("Unable to locate non-key columns for table '" +
                          getTableName() + "' so an empty insert statement is generated");
              }
              else {
                  throw new InvalidDataAccessApiUsageException("Unable to locate columns for table '" +
                          getTableName() + "' so an insert statement can't be generated");
              }
          }
          for (int i = 0; i < columnCount; i++) {
              if (i > 0) {
                  insertStatement.append(", ");
              }
              insertStatement.append("?");
          }
          insertStatement.append(")");
          return insertStatement.toString();
      }
    
  • createInsertTypes 不再贴出源码

  • executeInsertInternal

    if (logger.isDebugEnabled()) {
              logger.debug("The following parameters are used for insert " + getInsertString() + " with: " + values);
          }
          return getJdbcTemplate().update(getInsertString(), values.toArray(), getInsertTypes());
    

    update回到JdbcTemplate类一路封装最后执行public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)方法

其他一些有意思的东西

  • 更大的自由度---定义表与实体类、表字段与实体类属性对应关系。Hibernate的Implicit Naming StrategyPhysical Naming Strategy
  • 将sql转移到别的地方。MybatisxmlHibernate的基于注解的NamedQueries
  • 基于方法名生成简单的sql。类似JPA 或者像我们系统根据自定义表单、表格生成稍微复杂的自定义sql
  • 将表定义、执行相关信息等进行缓存。一级二级缓存。

其他

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容