简单ORM论道

作者: _chubby | 来源:发表于2019-03-24 20:23 被阅读0次

    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
    • 将表定义、执行相关信息等进行缓存。一级二级缓存。

    其他

    相关文章

      网友评论

        本文标题:简单ORM论道

        本文链接:https://www.haomeiwen.com/subject/pbkpvqtx.html