【3月28日】手撕架构(三)service的优化之路,从三十行到两行

本文通过封装数据库连接和查询操作,解决了数据库初始化代码冗余和查询操作复杂的问题,提升了开发效率。

在上文中,已经基本实现了getCustomerList功能并顺利通过了的单元测试。但是也遗留了两个问题:
1.在CustomerService类中读取config.properties文件,这是不合理的。一个项目中不可能只有一个service,如果每个service都需要读取config,一来造成不必要的冗余,也存在安全风险。
2.执行一条select语句真的是好累啊,需要编写一大推代码,还必须使用try……catch……finally结构,开发效率明显不高。
第一个问题实质是数据库初始化代码冗余的问题,第二个问题是数据库查询操作复杂的问题。依次解决。

解决数据库初始化代码冗余的问题

创建一个helper包,在该包中创建一个DataBaseHelper类:

package org.smart4j.chapter2.helper;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.slf4j.LoggerFactory;
import org.smart4j.chapter2.model.Customer;
import org.smart4j.chapter2.util.PropsUtil;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Created by Tree on 2017/3/27.
 * 将数据库连接的操作分离出来,这样避免在每一个service里面都要写
 * 很长的数据库连接的代码
 */
public class DataBaseHelper {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
    private static final QueryRunner QUERY_RUNNER = new QueryRunner();

    /**
     * 使用静态代码块初始化配置项定义的一些常量
     */
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;
    static {
        Properties conf = PropsUtil.loadProps("config.properties");
        DRIVER = conf.getProperty("jdbc.driver");
        URL = conf.getProperty("jdbc.url");
        USERNAME = conf.getProperty("jdbc.username");
        PASSWORD = conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch(ClassNotFoundException e){
            LOGGER.error("can not load jdbc drive", e);
        }
    }

    /**
     * 获取数据库连接
     */
    public static Connection getConnection(){
        Connection conn = null;
        try{
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        }catch (SQLException e){
            LOGGER.error("get connection failure", e);
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection(){
        Connection conn =getConnection();
        if(conn!=null){
            try{
                conn.close();
            }catch (SQLException e){
                LOGGER.error("close connection failure", e);
            }
        }
    }

在DataBaseHelper中使用一些静态方法来封装数据库的相关操作,并且提供了”获取数据库连接以及”关闭数据库连接“的两个工具方法。

public List<Customer> getCustomerList(){
        Connection conn = null;
        try{
            List<Customer> customerList = new ArrayList<Customer>();
            String sql = "SELECT * FROM customer";
            conn = DataBaseHelper.getConnection();
            PreparedStatement stmt = conn.prepareStatement(sql);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()){
                Customer customer = new Customer();
                customer.setId(rs.getLong("id"));
                customer.setName(rs.getString("name"));
                customer.setContract(rs.getString("contact"));
                customer.setTelephone(rs.getString("telephone"));
                customer.setEmail(rs.getString("email"));
                customer.setRemark(rs.getString("remark"));
                customerList.add(customer);
            }
            return customerList;
        }catch (SQLException e){
            LOGGER.error("execute sql failure", e);
            return null;
        }finally {
            DataBaseHelper.closeConnection(conn);
        }
    }

在service中,开启数据库的操作由:

conn=DriveManager.getConnection(URL,USERNAME,PASSWORD);

可以转变为:

conn = DataBaseHelper.getConnection();

这样在service中就不必进行显示的读取配置文件的信息并进行配置项的操作了(USERNAME,PASSWORD)不会在该类中暴露出来,提高了程序的健壮性。同样,关闭数据库的操作也精简了很多,不必在最后的finally{}块中继续try……catch……的操作了。

解决数据库查询操作复杂的问题

这个我们还是用一下轮子,著名的Apache Common项目中有一款DbUtil的类库,封装了一些JDBC的操作,下面就借助这款工具来解决查询代码复杂的问题。
首先,在pom中添加依赖:

 <!--Apache Commons DbUtils-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>

在DataBaseHelper中使用以上工具:

 private static final QueryRunner QUERY_RUNNER = new QueryRunner();
 /**
     * 查询客户列表:使用工具DbUtil
     * Object... params:“Varargs”机制。借助这一机制,可以定义能和多个实参相匹配的形参。
     * 但是该方法每次操作都需要创建并关闭Connection,Connection对于开发人员并不完全透明
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass,String sql,Object... params){
        List<T> entityList;
        try{
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn,sql,new BeanListHandler<T>(entityClass), params);
        }catch (SQLException e){
            LOGGER.error("query entity list failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return  entityList;
    }

借助DbUtils提供的QueryRunner对象可以面向实体(Entity)进行查询。实际上。DbUtils首先执行SQL语句并返回一个ResultSet,随后通过反射区创建并初始化实体对象。关于反射的相关知识,在前面的博客有过初步的涉及,不过我现在也还是在学习阶段,对深层的反射机制,还不是很清楚。暂且理解为:根据ResultSet返回的信息获取实体的名称,再以其名称并配合其基本的方法(如model中属性的get和set)创建实体。
有了上述的操作,现在再回到CustomerService中的getCustomerList方法:

 public List<Customer> getCustomerList(){
        Connection conn = DataBaseHelper.getConnection();
        try{
            String sql = "SELECT * FROM customer";
            return DataBaseHelper.queryEntityList(Customer.class,conn,sql);
        }finally {
            DataBaseHelper.closeConnection(conn);
        }
    }

可见,现在的代码简洁了很多,不需要面对PreparedStatement和ResultSet了,只需要借助DataBaseHelper就能对数据库进行操作。但是,这仍然存在一点瑕疵,对于开发者而言,在service层总是希望更多的关注业务本身而不是其他的操作,然而在目前的service中,开发人员每开发一个业务功能首先要创建数据库,进行数据操作,关闭数据库。开发人员只关心数据操作而不关系数据库的创建和关闭。有必要进行进一步的封装,使得Connection在service层对于开发人员完全透明。

在service层完全透明化connection

如何使Connection对于service完全透明呢?是否说简单的隐藏掉就可以呢?答案是否定的,因为程序要保证在执行service中的数据库查询的方法必须是线性安全的,如果只是粗暴的在DataBaseHelper中封装数据库的创建和关闭操作,会影响到程序的健壮性。为了确保一个线程中只有一个Connection,我们可以使用ThreadLocal来存放本地线程变量。
也就是说,将当前的线程中的Connection放入ThreadLocal中存起来,这些Connection一定不会出现线程不安全的问题,可以将ThreadLocal理解为一个隔离线程的容器。

public class DataBaseHelper {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
    private static final QueryRunner QUERY_RUNNER = new QueryRunner();
    private static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<Connection>();

    /**
     * 使用静态代码块初始化配置项定义的一些常量
     */
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;
    static {
        Properties conf = PropsUtil.loadProps("config.properties");
        DRIVER = conf.getProperty("jdbc.driver");
        URL = conf.getProperty("jdbc.url");
        USERNAME = conf.getProperty("jdbc.username");
        PASSWORD = conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch(ClassNotFoundException e){
            LOGGER.error("can not load jdbc drive", e);
        }
    }

    /**
     * 获取数据库连接
     */
    public static Connection getConnection(){
        Connection conn = CONNECTION_THREAD_LOCAL.get();
        try{
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        }catch (SQLException e){
            LOGGER.error("get connection failure", e);
            throw new RuntimeException(e);
        }finally {
            CONNECTION_THREAD_LOCAL.set(conn);
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection(){
        Connection conn = CONNECTION_THREAD_LOCAL.get()();
        if(conn!=null){
            try{
                conn.close();
            }catch (SQLException e){
                LOGGER.error("close connection failure", e);
                throw new RuntimeException(e);
            }finally {
                CONNECTION_THREAD_LOCAL.remove();
            }
        }
    }

    /**
     * 查询客户列表:使用工具DbUtil
     * Object... params:“Varargs”机制。借助这一机制,可以定义能和多个实参相匹配的形参。
     * 但是该方法每次操作都需要创建并关闭Connection,Connection对于开发人员并不完全透明
     * 通过引入本地线程变量CONNECTION_THREAD_LOCAL提供线程安全的保证。
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass,String sql,Object... params){
        List<T> entityList;
        try{
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn,sql,new BeanListHandler<T>(entityClass), params);
        }catch (SQLException e){
            LOGGER.error("query entity list failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return  entityList;
    }
}

当每次获取Connection时,首先在ThreadLocal中寻找,如果不存在,则创建一个新的Connection,并将其放入到ThreadLocal中。当这个连接使用完毕后,就从ThreadLocal中移除。
至此,CustomerService中的getCustomerList方法又可以进一步简化了,只有短短的两行:

 public List<Customer> getCustomerList(){
            String sql = "SELECT * FROM customer";
            return DataBaseHelper.queryEntityList(Customer.class,sql);
    }

对比一下一开始的代码,感受下:

 public List<Customer> getCustomerList(){
        Connection conn = null;
        try{
            List<Customer> customerList = new ArrayList<Customer>();
            String sql = "SELECT * FROM customer";
            conn = DriveManager.getConnection(URL, USERNAME, PASSWORD);
            PreparedStatement stmt = conn.prepareStatement(sql);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()){
                Customer customer = new Customer();
                customer.setId(rs.getLong("id"));
                customer.setName(rs.getString("name"));
                customer.setContract(rs.getString("contact"));
                customer.setTelephone(rs.getString("telephone"));
                customer.setEmail(rs.getString("email"));
                customer.setRemark(rs.getString("remark"));
                customerList.add(customer);
            }
            return customerList;
        }catch (SQLException e){
            LOGGER.error("execute sql failure", e);
        }finally {
            if(conn!=null){
                try{
                    conn.close();
                }catch (SQLException e){
                    LOGGER.error("close connection failure", e);
                }
            }

        }
    }

三十行的代码被压缩到两行啦。当然,除了看得到的简洁,更重要的是提高的程序的整体质量。

### 优化算法的最佳实践与技巧 优化算法涉及多个方面,包括但不限于超参数调优、模型结构调整以及代码层面的性能优化。以下是几个关键领域及其最佳实践: #### 超参数调优 超参数的选择直接影响到模型的表现。常见的超参数包括学习率、批量大小、正则化系数等。 - **网格搜索**是一种穷举法,它会尝试所有的可能组合并找到最优解[^3]。尽管这种方法简单直观,但在高维空间中可能会非常耗时。 - **随机搜索**相比网格搜索更加高效,因为它只采样部分候选值而不是全部测试它们。对于那些不那么重要的超参数来说,这通常已经足够好。 - 使用更先进的技术比如**贝叶斯优化**或者树结构Parzen估计器(TPE),这些方法利用先前迭代的信息来指导后续探索方向,往往能更快收敛至较佳配置。 #### 模型架构调整 除了调节外部可见的控制变量外,内部网络的设计同样重要。 - 对于特定任务类型(如序列预测),采用专门定制化的框架例如Transformer,并配合恰当设定好的子组件(多头注意力机制层数/维度大小等等)[^2],有助于增强表达力同时减少冗余计算量开销。 - 定期评估当前使用的梯度下降变体是否仍然适用当前场景下的挑战;如果发现现有方案存在明显不足之处,则考虑切换成其他更适合的新一代替代品——像Adam那样兼具自适应性和稳健性的选项通常是不错的选择之一[^1]。 #### 编程实现细节上的考量 即使拥有优秀的理论基础支撑起整个流程运转良好,但如果忽视掉底层操作系统的细微差异也可能导致最终成果不尽如意料之中那般理想完美无缺憾可言。 - Python作为一种解释型高级脚本语言虽然易于开发维护但天然存在着速度瓶颈问题因此建议开发者们积极探寻各类可段去缓解这一矛盾局面比如说借助第方扩展库NumPy加快数值运算过程又或者是引入JIT即时编译技术支持从而获得接近原生C++水平的极致效能表现[^4]。 ```python import numpy as np def fast_computation(x): return np.exp(-x) * (np.cos(2*np.pi*x)) # 利用 NumPy 加速数学函数处理 if __name__ == "__main__": import timeit setup = 'from __main__ import fast_computation; import numpy as np' stmt = '[fast_computation(i) for i in range(100)]' print(timeit.timeit(setup=setup, stmt=stmt, number=1000)) ``` 此外还应该养成习惯经常审查自己的源码是否存在潜在低效片段并通过重构消除浪费现象以此达成事半功倍的效果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值