文章目录
代码语言:txt复制- [一、类库介绍](https://cloud.tencent.com/developer/audit/support-plan/10680784#_1)
- [JDBC](https://cloud.tencent.com/developer/audit/support-plan/10680784#JDBC_4)
- [DBUtils](https://cloud.tencent.com/developer/audit/support-plan/10680784#DBUtils_7)
- [Druid](https://cloud.tencent.com/developer/audit/support-plan/10680784#Druid_10)
- [二、功能分析](https://cloud.tencent.com/developer/audit/support-plan/10680784#_13)
- [三、代码实现](https://cloud.tencent.com/developer/audit/support-plan/10680784#_19)
- [建表](https://cloud.tencent.com/developer/audit/support-plan/10680784#_22)
- [配置类代码](https://cloud.tencent.com/developer/audit/support-plan/10680784#_39)
- [封装 Druid](https://cloud.tencent.com/developer/audit/support-plan/10680784#_Druid_199)
- [封装 DBUtils](https://cloud.tencent.com/developer/audit/support-plan/10680784#_DBUtils_302)
- [封装 Dao](https://cloud.tencent.com/developer/audit/support-plan/10680784#_Dao_487)
- [测试](https://cloud.tencent.com/developer/audit/support-plan/10680784#_741)
一、类库介绍
首先简单介绍一下以上三种类库的区别与联系:
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 项目
建表
本项目使用的数据库为 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>
<!--Junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--servlet api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
<scope>compile</scope>
</dependency>
<!--Druid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<!--apache dbUtils : 简化 JDBC 操作-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!--lombok : 自动生成 get set 方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
</dependencies>
<build>
<finalName>jdbc-demo</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<!--maven插件-->
<plugins>
<!--jdk编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<!-- tomcat7的插件, 不同tomcat版本这个也不一样 -->
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
<port>8080</port>
<!-- 项目访问路径 本例:localhost:9090, 如果配置的aa, 则访问路径为localhost:9090/aa-->
<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 Application</display-name>
<!-- 配置 Druid 监控信息显示页面 -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<!-- 允许清空统计数据 -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用户名 -->
<param-name>loginUsername</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<!-- 密码 -->
<param-name>loginPassword</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-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
方法完成一次事务操作。
这里使用了反射的思想来获取子类的类型。
代码语言:javascript复制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 <T>
*/
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 类很简单,只定义了几个分页必须的属性。
代码语言:javascript复制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 进行测试:
代码语言:javascript复制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);
}
}
我们致力仅展示一下分页查询的查询结果。
查询结果:
代码语言:javascript复制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
}
再去看数据库,查询正确:
至此一个使用 JDBC 操作数据库的简单模板就完成了。