jdbc代码重构实现的实践

本文介绍了一种基于 Java 的 JDBC 框架设计,旨在简化数据库操作并提高代码复用性。通过抽象和封装数据库连接管理、SQL 执行及结果集处理等通用过程,实现了业务逻辑与数据访问层的有效分离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近刚买回一本关于java-web轻量级框架的设计书籍,希望能开阔视野,同时也复习一下快忘完的知识(其实也是在进入公司之后,所做的本质工作比较基础,在公司较老的技术框架,成熟的产品下,越干,越会感受到一种焦虑,很多贴近行业的开源的东西,一些基础的技术知识长时间不用,也有些遗忘),一遍看书,一遍敲代码。对于一些公用的工具,作为菜鸟的我,总希望自己也能完成一些基础的实现(有人说重复早轮子是无意义的,但是闲的时候自己去写一写,总会有一些收获,当然,工作之中为了稳定性和开发效率,当然会直接选用成熟的),于是有了本文。

        在编写书中基础实现的部分内容时,看到在做jdbc的编码部分,作者由最直白的代码,逐步重构,减少代码冗余,逐步将重复的编码部分向上提取,逐步变得可重用和透明,大大简化业务层的数据操作代码,看的十分爽快。jdbc部分的编码已有很多的成熟的框架,各种设计模式和方法都包含其中,是一个值得模仿的轮子。自己便写了一些简单的实现。

        jdbc部分的编码是模式化,重复的,开启连接,预编译sql,执行数据库操作,封装返回的结果集。各种不同的业务主体的操作并无二致,十分累人。要简化开发,就需要将那些冗长的模式化的东西向上提取作为公共的内容,而针对每个模块下的业务内容只编写与具体业务相关的,那些有差异,有变化的内容。将变化的内容传入上层的模板化的方法之中。大大简化开发。

数据库连接的获得:

一、数据库驱动应该是可配置,便于数据库的迁移,获取连接,关闭连接的方法封装起来可以减少处理异常的麻烦,使用静态定义能更方便的进行操作

    private static final Logger LOGGER = LoggerFactory.getLogger(DBHelper.class);
    private static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<>();

    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties props = PropUtil.loadProperties("dbconfig.properties");
        DRIVER = props.getProperty("jdbc.driver");
        URL = props.getProperty("jdbc.url");
        USERNAME = props.getProperty("jdbc.username");
        PASSWORD = props.getProperty("jdbc.password");
    }

    /**
     * 获得数据库连接
     * @return
     */
    public static Connection getConnection() {
        Connection conn = CONNECTION_THREAD_LOCAL.get();
        if (conn == null) {
            try {
                Class.forName(DRIVER);
                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            } catch (ClassNotFoundException e) {
                LOGGER.error(DRIVER + "load database driver failure", e);
            } catch (SQLException e) {
                LOGGER.error("get connection failure", e);
            } finally {
                CONNECTION_THREAD_LOCAL.set(conn);
            }
        }
        return conn;
    }
/**
     * 关闭数据库连接
     * @param statement
     * @param resultSet
     */
    public static void close(Statement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                LOGGER.error("close resultset failure", e);
            } finally {
                resultSet = null;
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                LOGGER.error("close statement failure", e);
            } finally {
                statement = null;
            }
        }
        Connection conn = CONNECTION_THREAD_LOCAL.get();
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                LOGGER.error("close connection failure", e);
            } finally {
                conn = null;
                CONNECTION_THREAD_LOCAL.remove();
            }
        }
    }

二、操作连接进行查询,套路完全固定,将变化的sql,查询参数项,实体类型作为参数传递进去,定义泛型方法实现g

/**
     * 获取实体的列表
     * @param sql
     * @param params
     * @param entityClass
     * @param <T>
     * @return
     */
    public <T> List<T> getBeanList(String sql, Map<String, Object> params, Class entityClass) {
        Connection conn = DBHelper.getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<T> entityList = new ArrayList<T>();
        try {
            pstmt = conn.prepareStatement(sql);
            LOGGER.info("execute " + sql + " ? ==>" + params);
            rs = pstmt.executeQuery();
            entityList = rs2BeanList(sql,rs,entityClass);
        } catch (SQLException e) {
            LOGGER.error("查询数据失败 sql==> " + sql, e);
        } finally {
            DBHelper.close(pstmt,rs);
        }
        return entityList;
    }
/**
 * 获取单个实体
 * @param sql
 * @param params
 * @param entityClass
 * @param <T>
 * @return
 */
public <T> T getSingleBean(String sql, Map<String, Object> params, Class entityClass) {
    // TODO
    Connection conn = DBHelper.getConnection();
    PreparedStatement pstmt = null;
    T entity = null;
    ResultSet rs = null;
    try {
        pstmt = conn.prepareStatement(sql);
        setQueryParams(pstmt,params);
        LOGGER.info("execute : " + sql + " ?==>" + params);
        rs = pstmt.executeQuery();
        String[] cols = getColsName(sql);
        if (rs.next()) {
            entity = rsCol2Bean(cols,rs,entityClass);
        }
    } catch (SQLException e) {
        LOGGER.error("查询数据失败 sql==>" + sql + "?==>" + params, e);
    } finally {
        DBHelper.close(pstmt,rs);
    }
    return entity;
}

三、查询参数的赋值是一个变化的项,统一起来用容器封装,方便进行赋值操作,当然一下可能是直白的方式,还需要更多的设计和考虑,但是一定程度起到了简化开发的目的

/**
     * 为sql查询赋值
     * @param pstmt
     * @param params
     * @throws SQLException
     */
    private void setQueryParams(PreparedStatement pstmt, Map<String, Object> params) throws SQLException {
        // where key1 = value1 and key2 = value2
        int index = 1;
        for (String key : params.keySet()) {
            pstmt.setObject(index++,params.get(key));
        }
    }

四、结果集的处理虽然不同业务实体具体实现不同,但概况的看依然是有其规律性和重复性的,这里利用java语音重要的动态特性:反射技术,实现对单表实体的自动映射,其他多表连接复杂查询可以通过Map容器进行结果的接收返回,思路大致一样,当然这只是简单的技术实践,可能在稳定性,安全性都有所欠缺,但是对于技术点的连习还是有益处的(个人感受)

/**
     * 结果集行转为bean
     * @param rs
     * @param entityClass
     * @param <T>
     * @return
     * @throws SQLException
     */
    private <T> T rsCol2Bean(String[] cols, ResultSet rs, Class entityClass) throws SQLException {
        T entity = null;
        try {
            entity = (T) entityClass.newInstance();
        } catch (InstantiationException e) {
            LOGGER.error("实例化 " + entityClass.toString() + "失败", e);
        } catch (IllegalAccessException e) {
            LOGGER.error("实例化 " + entityClass.toString() + "非法访问", e);
        }
        //反射获得field进行set
        if (cols != null) {
            initFieldByCols(cols, rs, entityClass, entity);
        } else {
            initFieldByEntity(rs, entityClass, entity);
        }
        return entity;
    }

    /**
     * 根据bean属性名初始化bean
     * @param rs
     * @param entityClass
     * @param entity
     * @param <T>
     * @throws SQLException
     */
    private <T> void initFieldByEntity(ResultSet rs, Class entityClass, T entity) throws SQLException {
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            Object obj = rs.getObject(field.getName());
            if (obj != null) {
                field.setAccessible(true);
                try {
                    field.set(entity,obj);
                } catch (IllegalAccessException e) {
                    LOGGER.error("字段映射异常" + field + "非法访问", e);
                }
            }
        }
    }

    /**
     * 根据查询列名初始化bean属性
     * @param cols
     * @param rs
     * @param entityClass
     * @param entity
     * @param <T>
     * @throws SQLException
     */
    private <T> void initFieldByCols(String[] cols, ResultSet rs, Class entityClass, T entity) throws SQLException {
        Object value = null;
        for (String str : cols) {
            value = rs.getObject(str);
            if (value != null) {
                Field field = null;
                try {
                    field = entityClass.getDeclaredField(str);
                    field.setAccessible(true);
                    field.set(entity,value);
                } catch (IllegalAccessException e) {
                    LOGGER.error("字段映射异常" + field + "非法访问", e);
                } catch (NoSuchFieldException e) {
                    LOGGER.error("字段映射异常" + field + "不存在", e);
                }
            }
        }
    }

    /**
     * 获取查询的列名
     * @param sql
     * @return
     */
    private String[] getColsName(String sql) {
        String colName = sql.substring(sql.indexOf("select") + 6, sql.lastIndexOf("from")).trim();
        String[] colsName = null;
        if (!colName.equals("*")) {
            String[] cols = colName.split(",");
            colsName = new String[cols.length];
            for (int i = 0; i < cols.length; i++) {
                colsName[i] = cols[i].trim();
            }
            return colsName;
        }
        return null;
    }

这里编写了采用查询列名进行实体封装和通过javabean本身属性进行封装的两种方式,主要考虑到可能会写“select * ...”之类的sql语句,采用列名反射的方式需要遵循table列名定义和javabean属性定义一致的规则,否则会有问题,此种方式开发更方便,数据库执行sql语句效率也会更高,但也有偷懒一个“*”搞定的人哦。

完成以上重构操作后,业务层的代码就很清晰,简介啦

private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);

    /**
     * 获取客户列表
     * @param keyword
     * @return
     */
    public List<Customer> getCustomerList(String keyword) {
        // TODO
        String sql = "select id,name , telephone from customer";
        Map<String,Object> fieldMap = null;
        if (keyword != null) {
            sql += " where remark like ?";
            fieldMap = new HashMap<>();
            fieldMap.put("remark",keyword);
        }
        return new DBHelper().getBeanList(sql,fieldMap,Customer.class);
    }

    /**
     * 获取客户
     * @param id
     * @return
     */
    public Customer getCustomer(long id) {
        // TODO
        String sql = "select * from customer where id = ?";
        Map<String,Object> fieldMap = new HashMap<>();
        fieldMap.put("id",id);
        return new DBHelper().getSingleBean(sql, fieldMap, Customer.class);
    }

        以上是看书过程中的一点小练习,一次记录一下,可能也能有益于他人,原书采用的是apache提供的DBUtil包进行了封装,笔者计划接下来就进行实践,便研究一下apache大神们的实现哦!相信会有很大收益呢!

        初次写文,毫无经验,有点啰嗦了,也不知道有些东西表达清楚没,技术的大海,无边无界,一只小虾米在其中,难免有不足,错误之处,欢迎批评,建议,指正。━(*`∀´*)ノ亻!





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值