首先简单介绍一下以上三种类库的区别与联系:
JDBC 是 sun 公司定义的一套使用 java
连接数据库的规范,是一套接口加部分实现类,他规定了各大数据库厂商要想使用 java
语言操作他们的数据库就必须实现这些接口,可以说 JDBC 是一套规范。比如 MySQL
就实现了这一套接口,在com.mysql.jdbc
包下。
Apache-commons
是 Apache 的一个工具类库,相信大家都听说过这些工具类,非常实用,我们使用的Apache-commons-dbutils
就是其中一个类库,它对 JDBC 进行了简单的封装(其实也不是很简单的封装,只是相对于框架来说是简单封装),简化了 JDBC 操作。当然我们也可以不使用他提供的类库而自己实现,但是这是非常麻烦的,而且没有必要浪费时间在这上面,就好像你着急上班,别人有汽车你不坐,说跑步可以锻炼身体,所以走过去。但是这太浪费时间了,所以我们要学会开车,然后研究他的原理,这样我们也会进步。
Druid 是阿里巴巴研发的一套数据库连接池技术,这个类库并不是必须使用的,我们在这里使用的目的是因为它可以增强性能,因为我们如果使用原生的 JDBC,不使用数据库连接池,那么每进行一次数据库查询操作就会建立一条连接,而数据库连接池是首先创建很多连接,当你需要用的时候就拿走,用完了之后归还,这样可以提高资源的利用率。常用的数据库连接池有 C3P0 和 Druid,我选择 Druid的原因是因为这是 web 项目模板,而且 Druid 提供强大的数据库监控技术和统计技术。详情可以看这篇文章:JavaWeb 使用 Druid 连接池查询数据库。
我们主要实现的功能有以下几个:
Druid
交互;DBUtils
交互;本项目使用的数据库为 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;
配置类代码分为 pom
和 druid
。
首先来看一下 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>
我们使用 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();
}
}
}
我们使用 BaseDao
对 DBUtils 进行简单封装。
可以看到这个抽象类没有抽象方法:
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();
}
}
}
首先定义一个接口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);
}
}
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
}