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

0x01 前言

  前序://www.greatytc.com/p/b5d88389444d

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

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

  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

  创建具体操作对象,定义字段 idnameagephoneaddress。并在数据库中创建对象表格。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

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