Java 编程之 Druid 结合 JDBC 简单实现

0x01 前言

  前序:http://www.jianshu.com/p/b5d88389444d

  在上述操作中,有许多问题,因此我们将对代码进行重构。问题如下:

  1. 每次进行数据库操作都要进行数据库连接,发送数据库连接四要素 driverClassName, url, username, password 用于加载数据库注册驱动,不利于维护,存在硬编码问题。

  2. 获取数据库连接 connection 我们仅仅需要获得已经连接好的 connection 对象,不需要关心如何创建的。

  3. 每次进行数据库连接都会创建一个 Connection 类的实例,性能开销大。

  4. 释放数据库连接代码也存在代码重复问题。

  5. 拼接数据库语句 SQL 存在硬编码问题,且容易造成 SQL 注入攻击。

  6. DML 操作和 DQL 操作存在重复代码。

  根据上述问题,我们提出以下的解决方案。

  1. 解决问题一:数据库连接四要素构成 properties 配置文件 db.properties,并将加载配置文件抽取到工具类 DruidUtil 中。

  2. 解决问题二:抽取数据库连接操作 getConnection() 到工具类 DruidUtil 中。

  3. 解决问题三:工具类 DruidUtil 中,把加载数据库注册驱动的代码放在静态代码块中,加载 JVM 的时候一并加载,只执行一次。Connection 实例由数据库连接池 Druid管理。

  4. 解决问题四:同样抽取释放资源代码到工具类 DruidUtil 中。

  5. 解决问题五:采用 PreparedStatement 构建 SQL 语句,采取 ? 占位符预编译 SQL 语句。

  6. 解决问题六:将 DML 和 DQL 操作抽取到工具类 JdbcTemplate 工具类中。

Druid 连接池

  Druid 是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分析。尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时, Druid 仍能够保持 100% 正常运行。创建 Druid 的最初意图主要是为了解决查询延迟问题,当时试图使用 Hadoop 来实现交互式查询分析,但是很难满足实时分析的需要。而 Druid 提供了以交互方式访问数据的能力,并权衡了查询的灵活性和性能而采取了特殊的存储格式。

0x02 项目结构

01. 项目结构图.png
  • dao 包用于访问数据库。

  • domain 包定义操作的实际对象。

  • handler 包封装了查询数据的统一处理操作。

  • test 包用于单元测试。

  • util 包封装数据库连接、释放和提交数据库操作语句的操作。

0x03 具体实现

创建 domain

  创建具体操作对象,定义字段 id,name,age,phone,address。并在数据库中创建对象表格。

// dao.example.domain.Account

package dao.example.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Account {

    private Long id;
    private String name;
    private Integer age;
    private String phone;
    private String address;

}

创建数据库连接配置文件

  创建数据库连接配置 properties 文件 db.properties。

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db
username=root
password=root

利用 Druid 连接池管理 Connection

  利用 Druid 管理 Connection 对象,静态代码块加载配置资源,getConnection() 方法获取数据库连接。 releaseSqlConnection() 方法释放数据库操作资源。

  导包使用 java.sql.* 下包,非 com.mysql.* 下包。使模板类能统一操作各类数据库,而非只能操作 MySQL 数据库。

// dao.example.util.DruidUtil

package dao.example.util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class DruidUtil {

    // 工具类,私有化无参构造函数
    private DruidUtil() {
    }

    // 静态数据源变量,供全局操作且用于静态代码块加载资源。
    private static DataSource dataSource;

    // 静态代码块,加载配置文件。
    static {
        InputStream inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties");
        Properties properties = new Properties();
        try {
            properties.load(inStream);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建数据库连接实例
     * @return 数据库连接实例 connection
     */
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("获取数据库连接异常");
    }

    /**
     * 释放数据库连接 connection 到数据库缓存池,并关闭 rSet 和 pStatement 资源
     * @param rSet 数据库处理结果集
     * @param pStatement 数据库操作语句
     * @param connection 数据库连接对象
     */
    public static void releaseSqlConnection(ResultSet rSet, PreparedStatement pStatement, Connection connection) {
        try {
            if (rSet != null) {
                rSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (pStatement != null) {
                    pStatement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (connection != null) {
                        connection.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

JDBC 操作模板方法

  上述中我们将抽取 DML 和 DQL 操作,我们创建 JdbcTemplate 模板类,模板类中定义 DML 操作方法 update(), DML 操作方法 query()。

  首先,对于 DML 操作没有返回结果集需要处理,而 DQL 则有返回结果集,如何对返回结果集进行统一、规范的处理是我们需要考虑的问题。我们创建一个 BeanHandler 类进行结果集处理。

  返回结果集在项目中可能存在多个类型,因此我们采取泛型创建 BeanHandler 中的处理方法 T handler(ResultSet rSet)。然而采取泛型之后,在调用 handler(...) 操作的对象具体类型 T 是什么类型就不得而知。因此我们利用构造函数 public BeanHandler(Class<T> clazz) 传入需要操作的具体的类型字节码。就知道所操作的对象的具体类型。

结果集操作模板
// dao.example.handler.IResultSetHandler<T>

package dao.example.handler;

import java.sql.ResultSet;

public interface IResultSetHandler<T> {

    T handler(ResultSet rSet);

}

  为什么定义接口?在项目开发中,当涉及到业务具体的操作对象后,一切的业务需求都是可能会变化的,定义接口规范,方便维护和拓展,符合开闭原则。

// dao.example.handler.impl.BeanHandler<T>

package dao.example.handler.impl;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import dao.example.handler.IResultSetHandler;

public class BeanHandler<T> implements IResultSetHandler<List<T>> {

    // 创建字节码对象
    private Class<T> clazz;

    // 创建有参构造函数,用于传入具体操作对象的类型
    public BeanHandler(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 数据库集操作类
     * @param rSet 数据库处理结果集
     * @return 数据库结果集 List 集合
     */
    @Override
    public List<T> handler(ResultSet rSet) {
        // 创建 List 用于存放装箱后的对象
        List<T> list = new ArrayList<T>();
        try {
            // 获取类的属性描述符
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);
            PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            // 对结果集进行装箱操作
            while (rSet.next()) {
                T obj = clazz.newInstance();
                for (PropertyDescriptor descriptor : descriptors) {
                    Object value = rSet.getObject(descriptor.getName());
                    descriptor.getWriteMethod().invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

}
DML & DQL 操作模板

   query() 方法中,使用接口作为形参,而实际调用中,传入具体的实现类形参,符合开闭原则。

// dao.example.util.JdbcTemplate

package dao.example.util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class JdbcTemplate {

    // 工具类,私有化无参构造函数
    private JdbcTemplate() {
    }

    /**
     * DML 操作模板方法
     * @param sql 执行操作的 SQL 语句
     * @param arguments SQL 语句参数
     */
    public static void update(String sql, Object... arguments) {
        // 获取数据库连接 connection
        Connection connection = DruidUtil.getConnection();
        // 创建数据库语句载体
        PreparedStatement pStatement = null;
        try {
            pStatement = connection.prepareStatement(sql);
            // 给预编译好的 sql 语句中的占位符进行赋值
            if (arguments != null && arguments.length > 0) {
                for (int i = 0; i < arguments.length; i++) {
                    pStatement.setObject(i + 1, arguments[i]);
                }
            }
            // 执行 SQL 语句
            pStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放数据库连接
            DruidUtil.releaseSqlConnection(null, pStatement, connection);
        }
    }

    /**
     * DQL 操作模板
     * @param sql 执行操作的 SQL 语句
     * @param handler 对数据库返回结果集进行装箱的操作类
     * @param arguments SQL 语句参数
     * @return 返回数据库查询结果集
     */
    public static <T> T query(String sql, IResultSetHandler<T> handler, Object... arguments) {
        Connection connection = DruidUtil.getConnection();
        PreparedStatement pStatement = null;
        ResultSet rSet = null;
        try {
            pStatement = connection.prepareStatement(sql);
            if (arguments != null && arguments.length > 0) {
                for (int i = 0; i < arguments.length; i++) {
                    pStatement.setObject(i + 1, arguments[i]);
                }
            }
            rSet = pStatement.executeQuery();
            // 调用处理结果集类对数据库查询结果集进行装箱
            return handler.handler(rSet);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DruidUtil.releaseSqlConnection(rSet, pStatement, connection);
        }
        return null;
    }

}

具体实现数据库访问层 DAO

  定义数据库操作方法。

// dao.example.dao.IAccountDAO

package dao.example.dao;

import java.util.List;

import dao.example.domain.Account;

public interface IAccountDAO {

    void save(Account account);

    void delete(Long id);

    void update(Account account);

    Account get(Long id);

    List<Account> list();

}

  数据库操作方法具体实现。

// dao.example.dao.impl.AccountDAOImpl

package dao.example.dao.impl;

import java.util.List;

import dao.example.dao.IAccountDAO;
import dao.example.domain.Account;
import dao.example.handler.impl.BeanHandler;
import dao.example.util.JdbcTemplate;

public class AccountDAOImpl implements IAccountDAO {

    @Override
    public void save(Account account) {
        String sql = "INSERT INTO t_account(name, age, phone, address) VALUES(?, ?, ?, ?)";
        Object[] arguments = new Object[] { account.getName(), account.getAge(), account.getPhone(), account.getAddress() };
        JdbcTemplate.update(sql, arguments);
    }

    @Override
    public void delete(Long id) {
        String sql = "DELETE FROM t_account WHERE id = ?";
        JdbcTemplate.update(sql, id);
    }

    @Override
    public void update(Account account) {
        String sql = "UPDATE t_account SET name = ?, age = ?, phone = ?, address = ? WHERE id = ?";
        Object[] arguments = new Object[] { account.getName(), account.getAge(), account.getPhone(), account.getAddress(), account.getId() };
        JdbcTemplate.update(sql, arguments);
    }

    @Override
    public Account get(Long id) {
        String sql = "SELECT id, name, age, phone, address FROM t_account WHERE id = ?";
        return JdbcTemplate.query(sql, new BeanHandler<>(Account.class), id).get(0);
    }

    @Override
    public List<Account> list() {
        String sql = "SELECT id, name, age, phone, address FROM t_account";
        return JdbcTemplate.query(sql, new BeanHandler<>(Account.class));
    }

}

单元测试

// dao.example.test.testDAO

package dao.example.test;

import org.junit.Test;

import dao.example.dao.impl.AccountDAOImpl;
import dao.example.domain.Account;

public class testDAO {

    @Test
    public void testSave() {
        new AccountDAOImpl().save(new Account(null, "张三", 25, "18819412345", "广州"));
    }

    @Test
    public void testDelete() {
        new AccountDAOImpl().delete(2L);
    }

    @Test
    public void testUpdate() {
        new AccountDAOImpl().update(new Account(2L, "李四", 27, "18819412345", "广州"));
    }

    @Test
    public void testGet() {
        System.out.println(new AccountDAOImpl().get(2L));
    }

    @Test
    public void testList() {
        System.out.println(new AccountDAOImpl().list());
    }

}

0x04 小结

  此次简单 JDBC 操作,我们需要知道在日后的开发中,需要对代码进行重构。抽取相同操作代码作为模板类,方便日后的维护。解决硬编码问题,将某些资源操作配置写入配置文件中。松耦合、解决硬编码、易维护都是开发当中重中之重。

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 35,339评论 18赞 399
  • 0x01 前言 JDBC(Java DataBase Connectivity,java 数据库连接)是一种用于执...
    菩提树下成魔阅读 1,426评论 1赞 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 137,266评论 19赞 139
  • 案例(HTML+CSS): 百度首页登录界面宠物网站月福首页 总结 1、“打地基” 项目准备:index.html...
    imChay阅读 440评论 0赞 0
  • 这两天一件极其的喜事让我特别乐,这种喜悦的心情难以名状,就是那种淡淡的快乐!小弟要来上海了,不就是个弟弟嘛,在交通...
    宗少躬身行阅读 214评论 0赞 1

友情链接更多精彩内容