nighty

          折騰的年華
          posts - 37, comments - 143, trackbacks - 0, articles - 0

                  引言:最近又用到dbutils,之前一直用Map映射的方式取出select的結(jié)果再手工做轉(zhuǎn)換。有寫過一篇文章說MapHandler方式的一個(gè)缺陷:關(guān)于commons dbutils組件的一個(gè)小缺陷分析 ,用這種方式,在項(xiàng)目不大的情況下,寫一些Map到JavaBean的轉(zhuǎn)換代碼工作量不大,但是在數(shù)據(jù)庫表過多并且表中的字段過多的情況下,這種重復(fù)的setter感覺有點(diǎn)煩。于是又重新思考了BeanHandler和BeanListHandler的情況,dbutils底層映射用的反射,性能上肯定有損失,不過在大多數(shù)項(xiàng)目規(guī)模不是很大的情況下,這點(diǎn)損失可以忽略,帶來的代碼減少卻是比較可觀。
                  問題在哪里?先看一段官方的示例代碼:

          QueryRunner run = new QueryRunner(dataSource);

          // Use the BeanHandler implementation to convert the first
          // ResultSet row into a Person JavaBean.
          ResultSetHandler<Person> h = new BeanHandler<Person>(Person.class);

          // Execute the SQL statement with one replacement parameter and
          // return the results in a new Person object generated by the BeanHandler.
          Person p = run.query(
              
          "SELECT * FROM Person WHERE name=?", h, "John Doe");

                  這里有個(gè)地方有約束,就是要求示例中的JavaBean類Person中的字段定義要和數(shù)據(jù)庫的字段定義一致。Java的命名習(xí)慣一般是駱峰寫法,例如userId,那么數(shù)據(jù)庫中就必須定義為userId,而問題在于:有時(shí)候我們需要數(shù)據(jù)庫中字段的定義格式與JavaBean的命名不一樣,比如數(shù)據(jù)庫定義為:user_id,而JavaBean則定義為userId
                  看源代碼可能有點(diǎn)費(fèi)時(shí)間,在官方的example頁面的最下面果然有一段關(guān)于自定義BeanProcessor的指引。摘錄出來:

                BasicRowProcessor uses a BeanProcessor to convert ResultSet columns into JavaBean properties. You can subclass and override processing steps to handle datatype mapping specific to your application. The provided implementation delegates datatype conversion to the JDBC driver.
                BeanProcessor maps columns to bean properties as documented in the BeanProcessor.toBean() javadoc. Column names must match the bean's property names case insensitively. For example, the firstname column would be stored in the bean by calling its setFirstName() method. However, many database column names include characters that either can't be used or are not typically used in Java method names. You can do one of the following to map these columns to bean properties:
                1. Alias the column names in the SQL so they match the Java names: select social_sec# as socialSecurityNumber from person
                2. Subclass BeanProcessor and override the mapColumnsToProperties() method to strip out the offending characters.


                大概意思就是提供二種方式:一種就是最直接的,用as關(guān)鍵字把colName重命名,另一種方式就是繼承BeanProcessor類,重寫mapColumnsToProperties()方法。
                那當(dāng)然是第二種方式更加具有代表性。嘗試了一下。代碼如下:
              
           1public class CustomBeanProcessor extends BeanProcessor {
           2    
           3    @Override
           4    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
           5            PropertyDescriptor[] props) throws SQLException {
           6        int cols = rsmd.getColumnCount();
           7        int columnToProperty[] = new int[cols + 1];
           8        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
           9
          10        for (int col = 1; col <= cols; col++{
          11            String columnName = rsmd.getColumnLabel(col); 
          12            if (null == columnName || 0 == columnName.length()) {
          13              columnName = rsmd.getColumnName(col);
          14            }

          15            columnName = colNameConvent(columnName); // 在這里進(jìn)行數(shù)據(jù)庫表columnName的特殊處理
          16            for (int i = 0; i < props.length; i++{
          17
          18                if (columnName.equalsIgnoreCase(props[i].getName())) {
          19                    columnToProperty[col] = i;
          20                    break;
          21                }

          22            }

          23        }

          24        return columnToProperty;
          25    }

          26
          27    /**
          28     * 數(shù)據(jù)庫列名重新約定
          29     * @param columnName
          30     * @return
          31     */

          32    private String colNameConvent(String columnName) {
          33        String[] strs = columnName.split("_");
          34        String conventName = "";
          35        for (int i = 0; i < strs.length; i++{
          36            conventName += StringUtils.capitalize(strs[i]);
          37        }

          38        StringUtils.uncapitalize(conventName);
          39        return conventName;
          40    }

          41}

                  注意mapColumnsToProperties方法的邏輯是從父類的方法中直接復(fù)制出來的,然后在第15行那里變了個(gè)戲法,這里的columnName就是從數(shù)據(jù)庫中讀出來的,自定義一個(gè)private方法用于轉(zhuǎn)換命名,這里你就可以添加自己的命名約束。例如上面就是把 user_id 轉(zhuǎn)化為Java的駱峰寫法:userId
                 再深入一層思考,你可以在這里進(jìn)行更多擴(kuò)展,以便讓自己可以選擇不同的命名轉(zhuǎn)換方式。定義了這個(gè)Processor之后,下面看看如何調(diào)用:
          Connection conn = ConnectionManager.getInstance().getConnection();
          QueryRunner qr 
          = new QueryRunner();
          CustomBeanProcessor convert 
          = new CustomBeanProcessor();
          RowProcessor rp 
          = new BasicRowProcessor(convert);
          BeanHandler
          <User> bh = new BeanHandler<User>(User.class, rp);
          User u 
          = qr.query(conn, sql, bh, params);
          DbUtils.close(conn);
                是不是非常靈活?如果是想返回List結(jié)果的,就把BeanHandler替換成BeanListHander類,還可以再進(jìn)一步封裝這些操作,抽象到公共模塊中去,讓外部直接傳入sql語句和Class就能直接返回想要的結(jié)果,當(dāng)然你得增加泛型的定義。同樣舉一個(gè)封裝的例子:
           1protected <T> List<T> selectBeanList(Connection conn, String sql, Class<T> type,
           2            Object[] params) throws Exception {
           3        log.debug("select sql:[" + sql + "]");
           4        QueryRunner qr = new QueryRunner();
           5        CustomBeanProcessor convert = new CustomBeanProcessor();
           6        RowProcessor rp = new BasicRowProcessor(convert);
           7        ResultSetHandler<List<T>> bh = new BeanListHandler<T>(type, rp);
           8        List<T> list = qr.query(conn, sql, bh, params);
           9        return list;
          10    }

                  至于為什么擴(kuò)展這個(gè)方法就可以實(shí)現(xiàn)這個(gè)邏輯就得去跟源代碼看它的內(nèi)部實(shí)現(xiàn),用了一些JavaBean的處理和反映的技巧來做的。具體就不說。
                  總結(jié):commons組件都設(shè)計(jì)得非常好,可擴(kuò)展性和實(shí)用性都非常高。雖然上面舉例實(shí)現(xiàn)了轉(zhuǎn)換邏輯的替換,但是仍然需要開發(fā)人員在設(shè)計(jì)數(shù)據(jù)庫的時(shí)候和寫JavaBean時(shí)都要嚴(yán)格做好規(guī)范,避免產(chǎn)生不必要的問題。這方面Ruby On Rails就直接內(nèi)部實(shí)現(xiàn),動(dòng)態(tài)語言的優(yōu)點(diǎn)特別能體現(xiàn),同時(shí)強(qiáng)制你在設(shè)計(jì)時(shí)必須用這種方式,典型的約定優(yōu)于配置原則。當(dāng)然,在dbutils里你愿意二種字段名都一樣也無可厚非。
                 缺點(diǎn):BeanProcessor是不支持關(guān)聯(lián)查詢的,所以上面的方式也只能局限于單表的轉(zhuǎn)換,這點(diǎn)就不如myBatis和Hibernate,當(dāng)然用這二個(gè)就引入了一些復(fù)雜性,如何權(quán)衡需要自己衡量,哪個(gè)用得好都一樣。本人就不喜歡myBatis那種把SQL寫到XML中的方式,見過太復(fù)雜的SQL最終在XML里面變得面目全非,如果是接手別人的代碼,是很痛苦的,而且你無法避免只修改XML而不改Java,既然二者都要改,那直接寫Java里又有什么區(qū)別?簡單就是美。格式和注釋寫好一點(diǎn)同樣很容易理解!

          剛進(jìn)場的時(shí)候戲就落幕
          主站蜘蛛池模板: 广水市| 潞西市| 甘洛县| 聂拉木县| 长春市| 苍南县| 稻城县| 宜川县| 饶河县| 句容市| 高碑店市| 苏尼特左旗| 于都县| 明水县| 舟曲县| 万源市| 阿勒泰市| 酒泉市| 怀化市| 瑞安市| 德钦县| 武汉市| 河东区| 怀集县| 隆林| 西华县| 德钦县| 金门县| 剑川县| 青岛市| 华池县| 策勒县| 都昌县| 平邑县| 陕西省| 汾阳市| 顺平县| 化德县| 原阳县| 兴业县| 崇仁县|