美文网首页
Java 编程之 Druid 结合 JDBC 简单实现

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

作者: 菩提树下成魔 | 来源:发表于2017-11-01 21:17 被阅读845次

0x01 前言

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

相关文章

网友评论

      本文标题:Java 编程之 Druid 结合 JDBC 简单实现

      本文链接:https://www.haomeiwen.com/subject/vxtnpxtx.html