apache-commons-dbutils + Druid + JDBC 简单实现 CRUD

文章目录

    • 一、类库介绍
      • JDBC
      • DBUtils
      • Druid
    • 二、功能分析
    • 三、代码实现
      • 建表
      • 配置类代码
      • 封装 Druid
      • 封装 DBUtils
      • 封装 Dao
      • 测试

一、类库介绍

首先简单介绍一下以上三种类库的区别与联系:

JDBC

JDBC 是 sun 公司定义的一套使用 java 连接数据库的规范,是一套接口加部分实现类,他规定了各大数据库厂商要想使用 java 语言操作他们的数据库就必须实现这些接口,可以说 JDBC 是一套规范。比如 MySQL 就实现了这一套接口,在com.mysql.jdbc包下。

DBUtils

Apache-commons 是 Apache 的一个工具类库,相信大家都听说过这些工具类,非常实用,我们使用的Apache-commons-dbutils就是其中一个类库,它对 JDBC 进行了简单的封装(其实也不是很简单的封装,只是相对于框架来说是简单封装),简化了 JDBC 操作。当然我们也可以不使用他提供的类库而自己实现,但是这是非常麻烦的,而且没有必要浪费时间在这上面,就好像你着急上班,别人有汽车你不坐,说跑步可以锻炼身体,所以走过去。但是这太浪费时间了,所以我们要学会开车,然后研究他的原理,这样我们也会进步。

Druid

Druid 是阿里巴巴研发的一套数据库连接池技术,这个类库并不是必须使用的,我们在这里使用的目的是因为它可以增强性能,因为我们如果使用原生的 JDBC,不使用数据库连接池,那么每进行一次数据库查询操作就会建立一条连接,而数据库连接池是首先创建很多连接,当你需要用的时候就拿走,用完了之后归还,这样可以提高资源的利用率。常用的数据库连接池有 C3P0 和 Druid,我选择 Druid的原因是因为这是 web 项目模板,而且 Druid 提供强大的数据库监控技术和统计技术。详情可以看这篇文章:JavaWeb 使用 Druid 连接池查询数据库。

二、功能分析

我们主要实现的功能有以下几个:

  • 首先建立数据库连接池类,用于和 Druid 交互;
  • 然后建立连接数据库操作的 BaseDao 基类,用于和 DBUtils 交互;
  • 由于 DBUtils 已经实现了和 JDBC 交互,所以我们只需要封装以上两层即可简化 JDBC 操作;
  • 为了简化业务层操作,我们继续封装 DBUtils ,实现 CRUD 接口,这样业务层只需要调用接口即可实现与数据库交互。

三、代码实现

这里是一个 JavaWeb 项目
apache-commons-dbutils + Druid + JDBC 简单实现 CRUD_第1张图片

建表

本项目使用的数据库为 school,如果想和我一起操作请建表,并注意编码设置为 UTF-8

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(100) NOT NULL AUTO_INCREMENT,
  `username` varchar(25) DEFAULT NULL,
  `password` varchar(25) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

配置类代码

配置类代码分为 pomdruid

首先来看一下 pom文件:

<dependencies>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.11version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.26version>
            <scope>compilescope>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.22version>
        dependency>

        
        <dependency>
            <groupId>commons-dbutilsgroupId>
            <artifactId>commons-dbutilsartifactId>
            <version>1.7version>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.16.8version>
        dependency>
    dependencies>

    <build>
        <finalName>jdbc-demofinalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-clean-pluginartifactId>
                    <version>3.1.0version>
                plugin>
                
                <plugin>
                    <artifactId>maven-resources-pluginartifactId>
                    <version>3.0.2version>
                plugin>
                <plugin>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>3.8.0version>
                plugin>
                <plugin>
                    <artifactId>maven-surefire-pluginartifactId>
                    <version>2.22.1version>
                plugin>
                <plugin>
                    <artifactId>maven-war-pluginartifactId>
                    <version>3.2.2version>
                plugin>
                <plugin>
                    <artifactId>maven-install-pluginartifactId>
                    <version>2.5.2version>
                plugin>
                <plugin>
                    <artifactId>maven-deploy-pluginartifactId>
                    <version>2.8.2version>
                plugin>
            plugins>
        pluginManagement>
        
        <plugins>
            
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <encoding>utf-8encoding>
                configuration>
            plugin>
            
            <plugin>
                <groupId>org.apache.tomcat.mavengroupId>
                
                <artifactId>tomcat7-maven-pluginartifactId>
                <version>2.1version>
                <configuration>
                    
                    <port>8080port>
                    
                    <path>/path>
                configuration>
            plugin>

        plugins>
    build>

然后是druid 配置文件:

driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql:///school?characterEncoding=utf-8
username = root
password = root
initialSize=5
maxActive=10
maxWait=3000

同时为了能够访问到 Druid 的图形化界面,我们还需要在 web.xml 中配置一下它自带的Servlet,具体解释请看这篇文章 https://blog.csdn.net/weixin_43941364/article/details/105851395:

<web-app>
    <display-name>Archetype Created Web Applicationdisplay-name>
    
    <servlet>
        <servlet-name>DruidStatViewservlet-name>
        <servlet-class>com.alibaba.druid.support.http.StatViewServletservlet-class>
        <init-param>
            
            <param-name>resetEnableparam-name>
            <param-value>trueparam-value>
        init-param>
        <init-param>
            
            <param-name>loginUsernameparam-name>
            <param-value>rootparam-value>
        init-param>
        <init-param>
            
            <param-name>loginPasswordparam-name>
            <param-value>rootparam-value>
        init-param>
    servlet>
    <servlet-mapping>
        <servlet-name>DruidStatViewservlet-name>
        <url-pattern>/druid/*url-pattern>
    servlet-mapping>
web-app>

封装 Druid

我们使用 DruidUtils 类对 Druid 进行简单封装。

package top.wsuo.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.Properties;
import java.sql.*;

/**
 * Druid数据库连接池工具类
 *
 * @Author shuo wang
 * @Date 2020/4/29 0029 14:46
 * @Version 1.0
 */
public class DruidUtils {

    // 数据源
    private static DataSource dataSource;

    // 注册驱动,使用静态代码块,类一旦加载就会执行
    static {
        try {
            // 获取类对象,读取配置文件
            InputStream resource = DruidUtils
                    .class
                    .getClassLoader()
                    .getResourceAsStream("druid.properties");
            Properties properties = new Properties();
            properties.load(resource);
            dataSource = DruidDataSourceFactory
                    .createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnect() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取连接池对象供commons使用
     *
     * @return 返回Druid连接池对象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 释放2个资源
     *
     * @param conn      连接对象
     * @param statement statement对象
     */
    public static void close(Connection conn,
                             PreparedStatement statement) {
        assert conn != null;
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        assert statement != null;
        try {
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放3个资源
     *
     * @param conn      连接对象
     * @param statement statement对象
     * @param resultSet 返回结果集
     */
    public static void close(Connection conn,
                             PreparedStatement statement,
                             ResultSet resultSet) {

        close(conn, statement);
        assert resultSet != null;
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

封装 DBUtils

我们使用 BaseDao 对 DBUtils 进行简单封装。

  • 这里为什么要定义成一个抽象类呢 ?
    • 对于一个父类,如果它的某个方法在父类中实现出来没有任何意义
    • 必须根据子类的实际需求来进行不同的实现,就要定义为抽象类
    • 像我们的 BaseDao ,我们需要它根据自己的业务需求灵活的变化
    • 比如有时候需要查询 User 类,有时候需要 Student 类。

可以看到这个抽象类没有抽象方法:

  • 因为我们知道抽象类是不能创建实例的,所以我们定义为抽象类就隐含的限制了其它用户的行为,即:你不可以直接使用此类,必须使用其它给你提供好的实现类。该类的灵感来自于org.apache.commons.dbutils.AbstractQueryRunner类,他也是没有一个抽象方法的抽象类,但是他有两个子类,这就限制了我们必须使用其子类完成操作。

另外,这里考虑到事务的操作,所以我在这里定义了一个 updateCommit 方法,它使用独立的 Connect 对象,而 DBUtils 使用默认的 Connect 对象,他默认每一条 SQL 语句就是一次事务,但是我们有时候业务需求比如银行转账,肯定是要多条 SQL 语句合成一次事务。这样定义一个专门的 updateCommit 方法之后当需要事务的时候就直接调用即可,然后调用 commit 方法完成一次事务操作。

这里使用了反射的思想来获取子类的类型。

package top.wsuo.dao;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import top.wsuo.util.DruidUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * 定义一个用来被继承的对数据库进行基本操作的 Dao
 * 这里为什么要定义成一个抽象类呢 ?
 * - 对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,
 * - 必须根据子类的实际需求来进行不同的实现,就要定义为抽象类
 * - 像我们的BaseDao,我们需要它根据自己的业务需求灵活的变化
 * - 比如有时候需要查询User类,有时候需要Student类
 *
 * @param 
 */
public abstract class BaseDao<T> {

    /*
     * 注意这里可以使用 QueryRunner 的两个构造方法来获取该对象
     *  - 一个是无参构造: 默认自己管理事务,因为框架没有连接池无法获得数据库连接
     *  - 另外一个是有参构造: 传入一个连接池对象,
     *    数据库事务交给DBUtils框架进行管理 ---- 默认情况下每条SQL语句单独一个事务。
     *
     * */

    // 使用 Druid 的连接池
    private QueryRunner queryRunner =
            new QueryRunner(DruidUtils.getDataSource());

    // 定义一个变量来接收泛型的类型
    private Class<T> type;

    // 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
    public BaseDao() {

        // 获取子类的类型
        Class clazz = this.getClass();

        // 获取父类的类型,ParameterizedType表示的是带泛型的类型,
        // getGenericSuperclass()用来获取当前类的父类的类型
        ParameterizedType parameterizedType =
                (ParameterizedType) clazz.getGenericSuperclass();

        // 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型,
        // 这个方法会返回一个Type的数组
        Type[] types =
                parameterizedType.getActualTypeArguments();

        // 获取具体的泛型的类型
        // noinspection unchecked
        this.type = (Class<T>) types[0];
    }

    /**
     * 手动提交事务
     * 通用的-增删改-操作, 但是一般只用于修改
     * 这里使用的是用户自定义的 Connection 对象,这样的话用户可以自己控制事务
     * 其他的查询方法不提供 Connection 对象,因为查询不涉及事务的操作.
     */
    public int updateCommit(Connection conn, String sql, Object... params) {
        int count = 0;
        try {
            count = queryRunner.update(conn, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }


    /**
     * 数据的增删改
     * 默认一个SQL语句为一个事务,数据库事务交给DBUtils框架进行管理
     *
     * @param sql    SQL语句
     * @param params 执行参数
     * @return 返回受影响的行数
     */
    public int update(String sql, Object... params) {
        int count = 0;
        try {
            count = queryRunner.update(sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 自动提交事务的查询方法
     *
     * @param sql    SQL 语句
     * @param params 查询参数
     * @return 返回泛型
     */
    public T queryBean(String sql, Object... params) {
        T t = null;
        try {
            t = queryRunner
                    .query(sql, new BeanHandler<>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return t;
    }

    /**
     * 自动提交事务的查询所有方法
     *
     * @param sql    SQL 语句
     * @param params 查询参数
     * @return 返回泛型集合
     */
    public List<T> queryBeanList(String sql, Object... params) {
        List<T> list = null;
        try {
            list = queryRunner
                    .query(sql, new BeanListHandler<>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 自动提交事务的值查询
     *
     * @param sql    SQL 语句
     * @param params 参数
     * @return 返回数值如 count(*) sum(total) ...
     */
    public Object queryValue(String sql, Object... params) {
        Object count = null;
        try {
            count = queryRunner
                    .query(sql, new ScalarHandler<>(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 处理事务提交与回滚
     *
     * @param connection 连接的对象
     */
    public void commit(Connection connection) {
        try {
            DbUtils.commitAndClose(connection);
        } catch (SQLException e) {
            System.out.println("事务提交失败!");
            DbUtils.rollbackAndCloseQuietly(connection);
            e.printStackTrace();
        }
    }
}

封装 Dao

首先定义一个接口UserDao

使用了 Page类,所以我们先介绍 Page 类:

package top.wsuo.pojo;

import java.util.List;

/**
 * 分页类
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:07
 * @Version 1.0
 */
public class Page<T> {
    private static final int PAGE_CURR = 1; // 当前页码
    private static final int PAGE_SIZE = 5; // 每页的数量

    private List<T> list;   // 实体类
    private Integer current;    // 当前页码
    private Integer size;   // 每页的条数
    private int totalRecord;    // 总记录数

    public Page(Integer current, Integer size) {
        this.current = current;
        this.size = size;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public Integer getCurrent() {
        return this.current;
    }

    public void setCurrent(Integer current) {
        this.current = current;
    }

    public Integer getSize() {
        return this.size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }

    public int getTotalRecord() {
        return totalRecord;
    }

    public void setTotalRecord(int totalRecord) {
        this.totalRecord = totalRecord;
    }

    @Override
    public String toString() {
        return "Page{\n" +
                "\tlist=" + list +
                ",\n \t当前页码=" + current +
                ",\n \t每页的数量=" + size +
                ",\n \t总记录数=" + totalRecord +
                "\n}";
    }
}

Page 类很简单,只定义了几个分页必须的属性。

package top.wsuo.dao;

import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;

import java.util.List;

/**
 * 用户接口类
 *
 * @author shuo wang
 * @version 1.0
 * @date 2020/4/30 0030 20:27
 */
public interface UserDao {
    /**
     * 根据User对象中的用户名和密码从数据库中获取一条记录
     */
    User queryUser(User user);

    /**
     * 根据User对象中的条件从数据库中获取多条记录
     */
    List<User> queryAll();

    /**
     * 分页从数据库中获取多条记录
     */
    Page<User> queryPageList(Page<User> page);

    /**
     * 根据User对象中的用户名从数据库中获取一条记录
     */
    boolean checkUsername(User user);

    /**
     * 向数据库中插入User对象
     */
    int saveUser(User user);

    /**
     * 向数据库中修改User对象
     */
    int updateUser(User user);

    /**
     * 向数据库中删除User对象
     */
    int deleteUser(int id);
}

再定义其实现类。

该类中定义 SQL 语句,如果我们又业务上的改变,可以直接修改此类而不用关心其余底层的 JDBC 操作,所以这是不使用框架比较好的实现,而如果使用反射就和框架更像了。

这里使用 limit 进行分页查询,在页数和页码处进行了相关操作。

package top.wsuo.dao.impl;

import top.wsuo.dao.BaseDao;
import top.wsuo.dao.UserDao;
import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;
import top.wsuo.util.DruidUtils;

import java.sql.Connection;
import java.util.List;

/**
 * 用户操作的DAO实现类
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:02
 * @Version 1.0
 */
public class UserDaoImpl extends BaseDao<User> implements UserDao {

    /**
     * 根据姓名和密码查询用户,登陆时用
     *
     * @param user 用户
     * @return 返回用户
     */
    @Override
    public User queryUser(User user) {
        String sql = "select * from user where username = ? and password = ?";
        return queryBean(sql, user.getUsername(), user.getPassword());
    }

    /**
     * 查询所有
     *
     * @return 返回集合
     */
    @Override
    public List<User> queryAll() {
        String sql = "select * from user";
        return queryBeanList(sql);
    }

    /**
     * 分页查询
     *
     * @param page 分页
     * @return 返回分页对象
     */
    @Override
    public Page<User> queryPageList(Page<User> page) {
        //  where username like concat('%', ? '%')
        String sql2 = "select * from user limit ? offset ?";
        int size = page.getSize();
        int curr = page.getCurrent();
        List<User> userList = queryBeanList(sql2, size, (curr - 1) * size);
        page.setTotalRecord(queryAll().size());
        page.setList(userList);
        return page;
    }

    /**
     * 检查用户名是否存在
     *
     * @param user 用户对象
     * @return 返回布尔值
     */
    @Override
    public boolean checkUsername(User user) {
        return queryUser(user) != null;
    }

    /**
     * 保存用户到数据库
     *
     * @param user 用户
     * @return 返回受影响的行数
     */
    @Override
    public int saveUser(User user) {
        String sql = "insert into user(username, password, address, phone) " +
                "values(?,?,?,?);";
        return update(sql, user.getUsername(), user.getPassword(),
                user.getAddress(), user.getPhone());
    }


    /**
     * 处理事务的修改方法
     *
     * @param user 实体类
     * @return 受影响的行数
     */
    @Override
    public int updateUser(User user) {
        Connection connection = DruidUtils.getConnect();
        int i = updateCommit(connection, "update user set username = ?, password = ?, " +
                        "address = ?, phone = ?", user.getUsername(),
                user.getPassword(), user.getAddress(), user.getPhone());
//        updateCommit(connection, "语句二", user);
        commit(connection); // 提交事务
        return i;
    }

    /**
     * 根据id删除
     *
     * @return 返回受影响的行数
     */
    @Override
    public int deleteUser(int id) {
        String sql = "delete from user where id = ?";
        return update(sql, id);
    }
}

至此,所有持久层代码就都写完了。

测试

我们使用 Junit 进行测试:

package top.wsuo.dao;

import org.junit.Test;
import top.wsuo.dao.impl.UserDaoImpl;
import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;

/**
 * 测试类
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:53
 * @Version 1.0
 */
public class DaoTest {

    private UserDaoImpl dao = new UserDaoImpl();

    /**
     * 保存数据测试方法
     */
    @Test
    public void saveTest() {
        User user = new User("test1",
                "123",
                "东北",
                "123456789");
        int i = dao.saveUser(user);
        System.out.println("插入成功: " + i);
    }

    /**
     * 查询用户
     */
    @Test
    public void queryUser() {
        User user = new User("test2",
                "123",
                "东北",
                "123456789");
        User user1 = dao.queryUser(user);
        System.out.println(user1);
    }

    /**
     * 分页查询测试方法
     */
    @Test
    public void pageTest() {
        Page<User> page = new Page<>(2,3);
        Page<User> pageList = dao.queryPageList(page);
        System.out.println(pageList);
    }

    /**
     * 修改测试方法
     */
    @Test
    public void updateTest() {
        User user = new User(
                "test6",
                "456",
                "吉林",
                "34234234"
        );
    }

    /**
     * 删除测试方法
     */
    @Test
    public void deleteTest() {
        int i = dao.deleteUser(1);
        System.out.println("删除成功: " + i);
    }
}

我们致力仅展示一下分页查询的查询结果。
apache-commons-dbutils + Druid + JDBC 简单实现 CRUD_第2张图片
查询结果:

Page{
	list=[User(id=5, username=test5, password=123, address=东北, phone=123456789), User(id=6, username=test6, password=123, address=东北, phone=123456789), User(id=7, username=test1, password=123, address=东北, phone=123456789)],
 	当前页码=2,
 	每页的数量=3,
 	总记录数=7
}

再去看数据库,查询正确:
apache-commons-dbutils + Druid + JDBC 简单实现 CRUD_第3张图片
至此一个使用 JDBC 操作数据库的简单模板就完成了。

你可能感兴趣的:(JavaWeb)