美文网首页
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