一个简单的通用dao层工具

作者: JerryL_ | 来源:发表于2017-01-17 14:04 被阅读0次

dao层设计

通用性的思考
要如何做到dao层与数据库和表无关是一个很值得思考的问题,如果想要偷懒,不为每个业务都编写特定的dao层实现,就必须要考虑所有可能会出现的情况。
关于sql语句
其实要谈论的不是通不通用,因为sql语句就已经是最通用,最灵活的数据库操作实现了,因为你可以用它操作任何数据库只要你写出对应的sql语句。
但是正是因为sql语句太灵活,所以编写很麻烦,而且业务的不同导致数据库表的不同,则sql语句也必定会不同,即便一个业务很简单,你也几乎无法重用以前写过的sql语句。

因此我在程序中要做的就是,以java代码的思维去实现数据库操作,并且不为用户暴露过多的实现细节,所谓不暴露过多的细节是指方法的参数不要太复杂,使方法简单易用,同时能满足一些经常出现的业务。
底层当然采用的是拼接sql的形式,最终达到的效果是,即便你不是很懂sql语句,只要你懂java,不写sql,也可以操作数据库。

于是我便有了这个接口的设计:

/**
 * Created by liuruijie on 2017/1/17.
 * 能满足数据库的多种操作的通用的dao层接口
 */
public interface CommonDao<T> {
    /**
     * 根据主键查询一条数据
     * @param tableName 表名
     * @param pkName 主键字段名
     * @param id 值
     * @param type 要转换的返回类型
     * @return 将记录转换成的po类的实例
     */
    T selectById(String tableName, String pkName, String id, Class<T> type);

    /**
     * 根据查询条件查询记录
     * 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 查询条件
     * @param type 类型
     * @return 将记录转换成的po类的实例的列表
     */
    List<T> selectByCriteria(String tableName, Criteria criteria, Class<T> type);

    /**
     * 查询记录数
     * @param tableName 表名
     * @param criteria 查询条件
     * @return 记录数
     */
    long countByCriteria(String tableName, Criteria criteria);

    /**
     * 根据主键删除一条记录
     * @param tableName 表名
     * @param pkName 主键字段名
     * @param id 主键值
     * @return 影响行数 0或1
     */
    int removeById(String tableName, String pkName, String id);

    /**
     * 保存一个对象为一条数据库记录
     * 如果对象主键不存在,则会新建
     * 如果对象主键已经存在,则会更新
     * @param tableName 表名
     * @param pkName 主键字段名
     * @param entity 要保存的对象实体
     * @return 影响行数 0或1
     */
    int save(String tableName, String pkName, T entity);

    /**
     * 查询条件
     */
    interface Criteria{
        /**
         * 使接下来的条件取非
         */
        Criteria not();

        /**
         * 使与下一个条件的连接词变为or,默认为and
         */
        Criteria or();

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

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

        /**
         * 取两个值之间的值
         * @param field 字段名
         * @param val1 值1
         * @param val2 值2
         */
        Criteria between(String field, Object val1, Object val2);

        /**
         * 限制查询记录数
         * @param start 开始位置
         * @param row 记录数
         */
        Criteria limit(int start, int row);

        /**
         * 获取参数列表
         * @return 参数列表
         */
        List<Object> getParam();

        /**
         * 获取拼接好的where条件sql语句
         * @return sql
         */
        StringBuilder getCriteriaSQL();
    }

    /**
     * 让实现类自己实现建立条件的方法
     * @return 查询条件实例
     */
    Criteria createCriteria();
}

这个接口提供了最常用的一些操作:根据主键查询记录,多条件查询,删除一条记录,更新记录以及插入记录,而且都是能满足大多数业务的,并且需要用户提供的参数也不多。
当然要做到简单,就必须要舍弃一些不常见的细节,比如说这里我就只考虑了单主键的情况。
当然这些都可以不断地完善,一开始就做出十全十美的东西是肯定不可能的。
接下来看一下实现:

/**
 * Created by liuruijie on 2017/1/17.
 * 通用dao层接口的实现
 */
@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();
    }


    /**
     * 查询条件的实现
     */
    class Criteria implements CommonDao.Criteria{
        private boolean not; //是否标记了非
        private boolean begin; //是否正在拼接第一个条件
        private boolean or;//是否修改连接词为OR
        StringBuilder criteriaSQL; //从where开始的条件sql
        List<Object> param; //参数列表
        String limitStr; //限制条数

        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(){
            //判断是否是第一个条件
            // ,如果是就加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);
        }
    }
}

实现使用了spring的jdbcTemplate,其实也可以用最原始的jdbc来实现,虽然麻烦一点,但可以减少依赖。
只要接口设计合理,实现起来无非就是一些字符串的拼接,不会太复杂。这里面用到了map和对象互转的工具,为了偷懒,我两个转换方法的实现我是采用的fastjson中序列化json的方法来转换的。

/**
 * Created by liuruijie on 2017/1/17.
 * 对象工具
 */
public class ObjectUtil {
    /**
     * 对象转字典
     */
    public static Map<String, Object> objectToMap(Object obj){
        return (Map<String, Object>) JSON.toJSON(obj);
    }

    /**
     * 字典转对象
     */
    public static <T> T mapToObject(Map<String, Object> map, Class<T> T){
        return (T) JSON.parseObject(JSON.toJSONString(map), T);
    }
}

写在最后的一些感受
可能会有人觉得,为什么不用ORM框架来做dao层接口,也同样很简单,很方便。
这样说吧,以前我是使用的mybatis来做的数据库访问层,并且使用了它的插件mybatisGenerator来做的根据数据库,逆向生成代码,不得不承认它确实很方便,可同样可以做到不写一条sql语句就能够操作数据库。但是用到后来,每个表对应至少一个po类+一个xml文件+一个dao接口+一个Example条件类,到业务越来越复杂,这些东西也就越来越多,然后你就会发现这种自动生成的代码几乎是不能维护的。而且它为你生成了太多无用的代码,要想精简代码,还是必须要自己写sql语句。
虽然使用流行的框架,不仅让写代码更方便,还能保证稳定性,而且自己写sql语句还能进行优化,提高执行效率,但是请想一想,有多少人能写出高性能的sql语句呢,对于一些普通的业务,如:xxx人员管理系统,xxx图书管理系统等,这些小型项目,高性能的sql语句又能使其得到多大的提升呢。
我的一贯思路都是,实现优先,然后考虑扩展性和可重用性,然后考虑稳定性,最后才考虑性能问题。能让程序在最短的时间内能运行起来才是最重要的,而不是为了提升程序在运行时的速度,而使用复杂的实现,导致迟迟不能运行,最后由于代码考虑的因素过多,把自己搞晕。

到此,我在后端的方向将一个web项目的结构设计从前后端的交互,到业务层的异常处理,再到数据访问层的设计都给出了自己的思路。虽然不是很完美,但是对于我自己来说,这个设计还是挺使用的,这3篇文章中提到的设计思路几乎都是可以运用于各种项目中的。

相关文章

  • 一个简单的通用dao层工具

    dao层设计 通用性的思考要如何做到dao层与数据库和表无关是一个很值得思考的问题,如果想要偷懒,不为每个业务都编...

  • 高并发秒杀系统API之Web层

    实现完dao 和service 层后,接下来自然就是web层了。首先创建一个通用的ajax返回工具类。返回json...

  • 2019-07-01

    单表的curd 数据库 model层 dao层 继承通用Mapper适合于单表的增删改查 .xml文件也放在dao...

  • 对查询出来的数据数据进行分页

    采用DAO层设计模式详解1.dao包:1.通用Dao类(实现增删改查)2.EmpDao(实现Emp的一些操作)3....

  • spring事务里开启多线程

    dao层用的是mybatis,比如在dao层和service层中间加了一个manager层,给manager...

  • 工程结构

    三大层: DAO层: DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO...

  • DAO层

    DAO层 DAO 是 Data Access Object(数据访问对象的缩写),DAO层是业务逻辑层与数据库层之...

  • Spring Data JPA进阶+swagger练习

    实体类 dao层 Service层 Service层的实现类 Controller层 dao层的test类

  • 批量添加 修改

    批量添加 ---dao层 ----------mapper.xml 批量修改 ----dao层 ---------...

  • ssm介绍

    持久层:DAO层(mapper) DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装...

网友评论

    本文标题:一个简单的通用dao层工具

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