一個(gè)簡(jiǎn)單的通用dao層工具

dao層設(shè)計(jì)

通用性的思考
要如何做到dao層與數(shù)據(jù)庫(kù)和表無(wú)關(guān)是一個(gè)很值得思考的問(wèn)題住练,如果想要偷懶睦疫,不為每個(gè)業(yè)務(wù)都編寫(xiě)特定的dao層實(shí)現(xiàn)碍彭,就必須要考慮所有可能會(huì)出現(xiàn)的情況。
關(guān)于sql語(yǔ)句
其實(shí)要談?wù)摰牟皇峭ú煌ㄓ醚虼瘢驗(yàn)閟ql語(yǔ)句就已經(jīng)是最通用蹂午,最靈活的數(shù)據(jù)庫(kù)操作實(shí)現(xiàn)了,因?yàn)槟憧梢杂盟僮魅魏螖?shù)據(jù)庫(kù)只要你寫(xiě)出對(duì)應(yīng)的sql語(yǔ)句翔怎。
但是正是因?yàn)閟ql語(yǔ)句太靈活窃诉,所以編寫(xiě)很麻煩,而且業(yè)務(wù)的不同導(dǎo)致數(shù)據(jù)庫(kù)表的不同赤套,則sql語(yǔ)句也必定會(huì)不同飘痛,即便一個(gè)業(yè)務(wù)很簡(jiǎn)單,你也幾乎無(wú)法重用以前寫(xiě)過(guò)的sql語(yǔ)句容握。

因此我在程序中要做的就是宣脉,以java代碼的思維去實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作,并且不為用戶(hù)暴露過(guò)多的實(shí)現(xiàn)細(xì)節(jié)剔氏,所謂不暴露過(guò)多的細(xì)節(jié)是指方法的參數(shù)不要太復(fù)雜塑猖,使方法簡(jiǎn)單易用,同時(shí)能滿(mǎn)足一些經(jīng)常出現(xiàn)的業(yè)務(wù)谈跛。
底層當(dāng)然采用的是拼接sql的形式羊苟,最終達(dá)到的效果是,即便你不是很懂sql語(yǔ)句感憾,只要你懂java蜡励,不寫(xiě)sql,也可以操作數(shù)據(jù)庫(kù)阻桅。

于是我便有了這個(gè)接口的設(shè)計(jì):

/**
 * Created by liuruijie on 2017/1/17.
 * 能滿(mǎn)足數(shù)據(jù)庫(kù)的多種操作的通用的dao層接口
 */
public interface CommonDao<T> {
    /**
     * 根據(jù)主鍵查詢(xún)一條數(shù)據(jù)
     * @param tableName 表名
     * @param pkName 主鍵字段名
     * @param id 值
     * @param type 要轉(zhuǎn)換的返回類(lèi)型
     * @return 將記錄轉(zhuǎn)換成的po類(lèi)的實(shí)例
     */
    T selectById(String tableName, String pkName, String id, Class<T> type);

    /**
     * 根據(jù)查詢(xún)條件查詢(xún)記錄
     * List<Student> students = commonDao
     *                      .selectByCriteria("m_student"
     *                      , commonDao.createCriteria()
     *                              .not().like("id", "2013")
     *                              .between("age", 10, 20)
     *                              .not().eq("gender", "F")
     *                      , Student.class);
     * @param tableName 表名
     * @param criteria 查詢(xún)條件
     * @param type 類(lèi)型
     * @return 將記錄轉(zhuǎn)換成的po類(lèi)的實(shí)例的列表
     */
    List<T> selectByCriteria(String tableName, Criteria criteria, Class<T> type);

    /**
     * 查詢(xún)記錄數(shù)
     * @param tableName 表名
     * @param criteria 查詢(xún)條件
     * @return 記錄數(shù)
     */
    long countByCriteria(String tableName, Criteria criteria);

    /**
     * 根據(jù)主鍵刪除一條記錄
     * @param tableName 表名
     * @param pkName 主鍵字段名
     * @param id 主鍵值
     * @return 影響行數(shù) 0或1
     */
    int removeById(String tableName, String pkName, String id);

    /**
     * 保存一個(gè)對(duì)象為一條數(shù)據(jù)庫(kù)記錄
     * 如果對(duì)象主鍵不存在凉倚,則會(huì)新建
     * 如果對(duì)象主鍵已經(jīng)存在,則會(huì)更新
     * @param tableName 表名
     * @param pkName 主鍵字段名
     * @param entity 要保存的對(duì)象實(shí)體
     * @return 影響行數(shù) 0或1
     */
    int save(String tableName, String pkName, T entity);

    /**
     * 查詢(xún)條件
     */
    interface Criteria{
        /**
         * 使接下來(lái)的條件取非
         */
        Criteria not();

        /**
         * 使與下一個(gè)條件的連接詞變?yōu)閛r嫂沉,默認(rèn)為and
         */
        Criteria or();

        /**
         * 相等
         * @param field 字段名
         * @param val 值
         */
        Criteria eq(String field, Object val);

        /**
         * 字符串匹配
         * @param field 字段名
         * @param val 值
         */
        Criteria like(String field, Object val);

        /**
         * 取兩個(gè)值之間的值
         * @param field 字段名
         * @param val1 值1
         * @param val2 值2
         */
        Criteria between(String field, Object val1, Object val2);

        /**
         * 限制查詢(xún)記錄數(shù)
         * @param start 開(kāi)始位置
         * @param row 記錄數(shù)
         */
        Criteria limit(int start, int row);

        /**
         * 獲取參數(shù)列表
         * @return 參數(shù)列表
         */
        List<Object> getParam();

        /**
         * 獲取拼接好的where條件sql語(yǔ)句
         * @return sql
         */
        StringBuilder getCriteriaSQL();
    }

    /**
     * 讓實(shí)現(xiàn)類(lèi)自己實(shí)現(xiàn)建立條件的方法
     * @return 查詢(xún)條件實(shí)例
     */
    Criteria createCriteria();
}

這個(gè)接口提供了最常用的一些操作:根據(jù)主鍵查詢(xún)記錄稽寒,多條件查詢(xún),刪除一條記錄趟章,更新記錄以及插入記錄杏糙,而且都是能滿(mǎn)足大多數(shù)業(yè)務(wù)的,并且需要用戶(hù)提供的參數(shù)也不多尤揣。
當(dāng)然要做到簡(jiǎn)單搔啊,就必須要舍棄一些不常見(jiàn)的細(xì)節(jié),比如說(shuō)這里我就只考慮了單主鍵的情況北戏。
當(dāng)然這些都可以不斷地完善负芋,一開(kāi)始就做出十全十美的東西是肯定不可能的。
接下來(lái)看一下實(shí)現(xiàn):

/**
 * Created by liuruijie on 2017/1/17.
 * 通用dao層接口的實(shí)現(xiàn)
 */
@Service
public class CommonDaoImpl<T> implements CommonDao<T>{
    @Resource
    JdbcTemplate jdbcTemplate;

    @Override
    public T selectById(String tableName, String pkName, String id, Class<T> type) {
        Map<String, Object> obj = jdbcTemplate.queryForMap("SELECT * FROM "+tableName+" WHERE "+pkName+" = ?", id);
        return ObjectUtil.mapToObject(obj, type);
    }

    @Override
    public List<T> selectByCriteria(String tableName, CommonDao.Criteria criteria, Class<T> type) {
        StringBuilder sqlStr = new StringBuilder("");
        sqlStr.append("SELECT * FROM ")
                .append(tableName)
                .append(criteria.getCriteriaSQL());
        System.out.println(sqlStr.toString());
        Object[] params = criteria.getParam().toArray(new Object[criteria.getParam().size()]);
        List<Map<String, Object>> objs = jdbcTemplate.queryForList(sqlStr.toString(), params);
        List<T> results = new ArrayList<>();
        for(Map<String, Object> o: objs){
            results.add(ObjectUtil.mapToObject(o, type));
        }
        return results;
    }

    @Override
    public long countByCriteria(String tableName, CommonDao.Criteria criteria) {
        String sql = "SELECT COUNT(*) AS num FROM "+tableName + criteria.getCriteriaSQL();
        Map<String, Object> map = jdbcTemplate.queryForMap(sql, criteria.getParam().toArray());
        return (Long)map.get("num");
    }

    @Override
    public int removeById(String tableName, String pkName, String id) {
        String sql = "DELETE FROM " +
                tableName +
                " WHERE " +
                pkName +
                " = ?";
        return jdbcTemplate.update(sql, id);
    }

    @Override
    public int save(String tableName, String pkName, T entity) {
        Map<String, Object> obj = ObjectUtil.objectToMap(entity);
        StringBuffer sql1 = new StringBuffer("INSERT INTO ")
                .append(tableName)
                .append("(");
        StringBuffer sql2 = new StringBuffer(" VALUES(");
        List<Object> args = new ArrayList<>();
        int count = 0;
        for(String key: obj.keySet()){
            Object arg = obj.get(key);
            if (arg==null){
                continue;
            }
            sql1.append(key).append(",");
            sql2.append("?,");
            args.add(arg);
        }
        sql1.deleteCharAt(sql1.length() - 1);
        sql1.append(") ");
        sql2.deleteCharAt(sql2.length() - 1);
        sql2.append(") ");
        String sql = sql1.append(sql2).toString();
        System.out.println(sql);
        try {
            count += jdbcTemplate.update(sql, args.toArray());
        }catch (DuplicateKeyException e){
            sql1 = new StringBuffer("UPDATE ")
                    .append(tableName)
                    .append(" SET ");
            sql2 = new StringBuffer(" WHERE "+pkName+"=?");
            args = new ArrayList<>();
            for (String key: obj.keySet()){
                if (key.equals(pkName)){
                    continue;
                }
                Object arg = obj.get(key);
                if (arg==null){
                    continue;
                }
                sql1.append(key).append("=?,");
                args.add(arg);
            }

            sql1.deleteCharAt(sql1.length() - 1);
            args.add(obj.get(pkName));
            sql = sql1.append(sql2).toString();
            System.out.println(sql);
            count+=jdbcTemplate.update(sql, args.toArray());
        }
        return count;
    }

    @Override
    public CommonDao.Criteria createCriteria() {
        return new Criteria();
    }


    /**
     * 查詢(xún)條件的實(shí)現(xiàn)
     */
    class Criteria implements CommonDao.Criteria{
        private boolean not; //是否標(biāo)記了非
        private boolean begin; //是否正在拼接第一個(gè)條件
        private boolean or;//是否修改連接詞為OR
        StringBuilder criteriaSQL; //從where開(kāi)始的條件sql
        List<Object> param; //參數(shù)列表
        String limitStr; //限制條數(shù)

        public Criteria(){
            criteriaSQL = new StringBuilder("");
            param = new LinkedList<>();
            not = false;
            begin = true;
            limitStr = "";
        }

        public Criteria not(){
            not = true;
            return this;
        }

        @Override
        public CommonDao.Criteria or() {
            or = true;
            return this;
        }

        private void link(){
            //判斷是否是第一個(gè)條件
            // ,如果是就加WHERE不加連接詞
            // 旧蛾,不是就直接加連接詞
            if(begin){
                criteriaSQL.append(" WHERE ");
            }else{
                if(or){
                    criteriaSQL.append(" OR ");
                }else{
                    criteriaSQL.append(" AND ");
                }
            }
            or = false;
        }

        public Criteria eq(String field, Object val) {
            link();
            if (not) {
                criteriaSQL.append(field)
                        .append(" != ?");
            } else {
                criteriaSQL.append(field)
                        .append(" = ?");
            }
            not = false;
            begin = false;
            param.add(val);
            return this;
        }

        public Criteria like(String field, Object val){
            link();
            if(not){
                criteriaSQL.append(field)
                        .append(" NOT LIKE ?");
            }else{
                criteriaSQL.append(field)
                        .append(" LIKE ?");
            }
            not = false;
            begin = false;
            param.add("%"+val+"%");
            return this;
        }
        public Criteria between(String field, Object val1, Object val2){
            link();
            if(not){
                criteriaSQL.append(field)
                        .append(" NOT BETWEEN ? AND ? ");
            }else{
                criteriaSQL.append(field)
                        .append(" BETWEEN ? AND ? ");
            }
            not = false;
            begin = false;
            param.add(val1);
            param.add(val2);
            return this;
        }

        @Override
        public CommonDao.Criteria limit(int start, int row) {
            limitStr = " limit " + start + "," + row;
            return this;
        }

        public List<Object> getParam(){
            return this.param;
        }
        public StringBuilder getCriteriaSQL(){
            return new StringBuilder(criteriaSQL.toString()+limitStr);
        }
    }
}

實(shí)現(xiàn)使用了spring的jdbcTemplate莽龟,其實(shí)也可以用最原始的jdbc來(lái)實(shí)現(xiàn),雖然麻煩一點(diǎn)锨天,但可以減少依賴(lài)毯盈。
只要接口設(shè)計(jì)合理,實(shí)現(xiàn)起來(lái)無(wú)非就是一些字符串的拼接病袄,不會(huì)太復(fù)雜搂赋。這里面用到了map和對(duì)象互轉(zhuǎn)的工具,為了偷懶益缠,我兩個(gè)轉(zhuǎn)換方法的實(shí)現(xiàn)我是采用的fastjson中序列化json的方法來(lái)轉(zhuǎn)換的脑奠。

/**
 * Created by liuruijie on 2017/1/17.
 * 對(duì)象工具
 */
public class ObjectUtil {
    /**
     * 對(duì)象轉(zhuǎn)字典
     */
    public static Map<String, Object> objectToMap(Object obj){
        return (Map<String, Object>) JSON.toJSON(obj);
    }

    /**
     * 字典轉(zhuǎn)對(duì)象
     */
    public static <T> T mapToObject(Map<String, Object> map, Class<T> T){
        return (T) JSON.parseObject(JSON.toJSONString(map), T);
    }
}

寫(xiě)在最后的一些感受
可能會(huì)有人覺(jué)得,為什么不用ORM框架來(lái)做dao層接口幅慌,也同樣很簡(jiǎn)單宋欺,很方便。
這樣說(shuō)吧胰伍,以前我是使用的mybatis來(lái)做的數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)層齿诞,并且使用了它的插件mybatisGenerator來(lái)做的根據(jù)數(shù)據(jù)庫(kù),逆向生成代碼骂租,不得不承認(rèn)它確實(shí)很方便祷杈,可同樣可以做到不寫(xiě)一條sql語(yǔ)句就能夠操作數(shù)據(jù)庫(kù)。但是用到后來(lái)菩咨,每個(gè)表對(duì)應(yīng)至少一個(gè)po類(lèi)+一個(gè)xml文件+一個(gè)dao接口+一個(gè)Example條件類(lèi)吠式,到業(yè)務(wù)越來(lái)越復(fù)雜,這些東西也就越來(lái)越多抽米,然后你就會(huì)發(fā)現(xiàn)這種自動(dòng)生成的代碼幾乎是不能維護(hù)的。而且它為你生成了太多無(wú)用的代碼糙置,要想精簡(jiǎn)代碼云茸,還是必須要自己寫(xiě)sql語(yǔ)句。
雖然使用流行的框架谤饭,不僅讓寫(xiě)代碼更方便标捺,還能保證穩(wěn)定性,而且自己寫(xiě)sql語(yǔ)句還能進(jìn)行優(yōu)化揉抵,提高執(zhí)行效率亡容,但是請(qǐng)想一想,有多少人能寫(xiě)出高性能的sql語(yǔ)句呢冤今,對(duì)于一些普通的業(yè)務(wù)闺兢,如:xxx人員管理系統(tǒng),xxx圖書(shū)管理系統(tǒng)等戏罢,這些小型項(xiàng)目屋谭,高性能的sql語(yǔ)句又能使其得到多大的提升呢脚囊。
我的一貫思路都是,實(shí)現(xiàn)優(yōu)先桐磁,然后考慮擴(kuò)展性和可重用性悔耘,然后考慮穩(wěn)定性,最后才考慮性能問(wèn)題我擂。能讓程序在最短的時(shí)間內(nèi)能運(yùn)行起來(lái)才是最重要的衬以,而不是為了提升程序在運(yùn)行時(shí)的速度,而使用復(fù)雜的實(shí)現(xiàn)校摩,導(dǎo)致遲遲不能運(yùn)行看峻,最后由于代碼考慮的因素過(guò)多,把自己搞暈秧耗。

到此备籽,我在后端的方向?qū)⒁粋€(gè)web項(xiàng)目的結(jié)構(gòu)設(shè)計(jì)從前后端的交互,到業(yè)務(wù)層的異常處理分井,再到數(shù)據(jù)訪(fǎng)問(wèn)層的設(shè)計(jì)都給出了自己的思路车猬。雖然不是很完美,但是對(duì)于我自己來(lái)說(shuō)尺锚,這個(gè)設(shè)計(jì)還是挺使用的珠闰,這3篇文章中提到的設(shè)計(jì)思路幾乎都是可以運(yùn)用于各種項(xiàng)目中的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘫辩,一起剝皮案震驚了整個(gè)濱河市伏嗜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伐厌,老刑警劉巖承绸,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異挣轨,居然都是意外死亡军熏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)卷扮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荡澎,“玉大人,你說(shuō)我怎么就攤上這事晤锹∧︶#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵鞭铆,是天一觀(guān)的道長(zhǎng)或衡。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么薇宠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任偷办,我火速辦了婚禮,結(jié)果婚禮上澄港,老公的妹妹穿的比我還像新娘椒涯。我一直安慰自己,他們只是感情好回梧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布废岂。 她就那樣靜靜地躺著,像睡著了一般狱意。 火紅的嫁衣襯著肌膚如雪湖苞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天详囤,我揣著相機(jī)與錄音财骨,去河邊找鬼。 笑死藏姐,一個(gè)胖子當(dāng)著我的面吹牛隆箩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羔杨,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捌臊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兜材?” 一聲冷哼從身側(cè)響起理澎,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曙寡,沒(méi)想到半個(gè)月后糠爬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡举庶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年秩铆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灯变。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捅膘,靈堂內(nèi)的尸體忽然破棺而出添祸,到底是詐尸還是另有隱情,我是刑警寧澤寻仗,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布刃泌,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏耙替。R本人自食惡果不足惜亚侠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俗扇。 院中可真熱鬧硝烂,春花似錦、人聲如沸铜幽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)除抛。三九已至狮杨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間到忽,已是汗流浹背橄教。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喘漏,地道東北人护蝶。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像陷遮,于是被迫代替她去往敵國(guó)和親滓走。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容