架构探险-第二章:为Web应用添加业务功能(5)-优化服务层-Connection的线程隔离

一前言

上一节针对之前服务层存在的两个问题,对服务层进行了优化
    1,提取DatabaeHelper类,便于多Service复用数据库相关操作的方法
    2,使用DbUtils解决数据库查询时大量重复代码问题
目前仍存在需优化的问题:
    每次执行数据库操作时,都需要新创建一个Connection对象,操作完成后关闭
    如何能将这部分操作从Service中移除,是Connection对于开发者完全透明
本节将对此部分进行优化

二,优化服务层-Connection的线程隔离

1,问题分析

目前Service中的每个数据库操作都需要先创建一个Connection连接对象,再销毁
public List<Customer> getCustomerList() {
    // 优化点:创建Connection连接
    Connection conn = DatabaseHelper.getConnection();
    try{
        String sql = "select * from customer";
        return DatabaseHelper.queryEntityList(Customer.class, sql);
    }finally {
        // 优化点:关闭Connection连接
        DatabaseHelper.closeConnection(conn);
    }
}
如果能够将这部分重复的操作在Service类中移除,放入DatabaseHelper中会变得非常方便
为了做到这一点,需要确保每一个线程中只有一个Connection对象
这里我们使用ThreadLocal存放本地线程变量,ThreadLocal作为隔离线程的容器
将当前线程中的Connection放入ThreadLocal中存起来,确保不会出现线程安全问题

2,调整DatabaseHelper,引入ThreadLocal保存Connection对象

// 隔离线程:用于保存当前线程的Connection对象
private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<Connection>();

/**
 * 获取数据库连接
 */
public static Connection getConnection() {
    // 每次获取Connection连接,先查看是否存在
    Connection conn = CONNECTION_HOLDER.get();
    if(conn == null){
        try {
            LOGGER.info("创建Connection");
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (SQLException e) {
            LOGGER.error("get connection failure", e);
            throw new RuntimeException(e);
        } finally {
            // 将Connection保存到ThreadLocal中
            CONNECTION_HOLDER.set(conn);
        }
    }
    return conn;
}

/**
 * 关闭数据库连接
 */
public static void closeConnection(){
    // 获取当前线程Connection对象
    Connection conn = CONNECTION_HOLDER.get();
    if(conn != null){
        try {
            conn.close();
            LOGGER.info("销毁Connection");
        } catch (SQLException e) {
            LOGGER.error("close connection failure", e);
            throw new RuntimeException(e);
        } finally {
            // 销毁当前线程的Connection对象
            CONNECTION_HOLDER.remove();
        }
    }
}
每次获取Connection时,先在ThreadLocal中查看是否存在
若不存在,创建一个新的Connection并将其放入ThreadLocal中
当Connection使用完毕,移除ThreadLocal中持有的Connection对象

3,优化Service查询操作

有了上边对线程中Connection进行隔离的方法,我们就可以简化Service中数据库操作了
    /**
     * 获取客户列表
     */
    public List<Customer> getCustomerList() {
        String sql = "select * from customer";
        return DatabaseHelper.queryEntityList(Customer.class, sql);
    }
可以看到,在"查询客户列表"方法中,去除了创建/获取连接和关闭连接的操作
这意味着,所有的Service方法都不需要再在开头和结尾做这件事情了

三,测试

现在Service中的查询方法不再需要获取和关闭Connection对象了
数据库查询方法和获取,销毁Connection操作的方法都在DatabaseHelper中
所以可以在查询方法中进行数据库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;
}

测试结果:

这里写图片描述


四,结尾:

这一节将每个线程的Connection对象保存到ThreadLocal中,实现线程安全
将需要在每一个Service方法中获取和关闭数据库Connection对象的操作进行了提取,
将这些操作提取到了DatabaseHelper中,优化并实现了代码的重用,使Service更加简单

经过这些优化后依然存在一些问题,例如:
每次连接数据库时,都要调用getConnection方法,操作完毕后调用closeConnection方法
虽然这件事已经被DatabaseHelper封装了,但是目前对Service的操作会频繁创建Connection
数据库的连接数是有限的,频繁创建数据库连接会对数据库造成大量的系统开销
因此,需要一种方案,实现对数据库的"池化",也就是"数据库连接池"来解决这个问题

猜你喜欢

转载自blog.csdn.net/abap_brave/article/details/80437561
今日推荐