目录
五、总结
一、前言
- 第七节内容,up打算和大家分享一下JDBC——BasicDAO相关的内容。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着过一遍才有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。 感谢阅读!
二、BasicDAO的引入
1.为什么需要BasicDAO?
在传统JDBC程序的基础上,为了优化连接,我们引入了连接池的概念,并且学习了Druid(德鲁伊)连接池的使用;而在此基础上,为了优化对结果集的操作,我们又引入了ApacheDBUtils工具类。靠着DBUtils + Druid,JDBC程序的编写流程和效率得到了极大优化。
但是,即便如此,"DBUtils + Druid"依然存在了一些不可忽视的问题。如下——
①在JDBC四部曲的“执行SQL”步骤中,SQL是已经写死的,要操作的表和字段均已固定,而不能通过参数传入,通用性差。
②对于DQL,如果有返回结果集的需要,返回值类型是无法确定的,需要使用泛型。
③很多情况下,需要同时操作多张表,业务需求复杂,不可能只靠一个Java类来实现。
2.BasicDAO示意图 :
示意图如下 : (从下往上看!)
初看时可能会感觉这张图复杂,这里稍微解读一下。
①先看最下面,这一块区域 : (Domain, POJO, JavaBean)
在上一小节DBUtils详解中我们说过这个JavaBean,就是把每个要操作的表都对应一个JavaBean类,类中的属性是表中字段的映射。这么做是为了将来把多个保存了表数据信息的JavaBean实例封装到一个集合中,便于数据的管理和复用。
②再看中间那一部分 : (DAO)
其实这里的DAO就是指“数据访问对象”(Data Access Object),当然我们后面是会讲到的。 DAO的设计理念是为每一张要操作的表都单独设置一个对应的DAO,以完成对该表的CRUD操作,不同DAO只完成它对应那张表的操作,以达到“各司其职,各尽其责”的效果,使表的操作更具有针对性,业务更清晰。
由于不同的DAO都存在共有的操作,例如获取连接,释放资源。因此根据OOP的设计理念,我们将这些共有的操作单独再封装到一个DAO中,也就是BasicDAO了,然后让操作表的DAO去继承BasicDAO。
③再看最上面,最右上方这一块区域 : (应用区)
有了DAO之后,我们想访问那张表,直接去操作该表对应的DAO即可(复读机)。
当然,up画的示意图中只划分了三部分,因为咱们还是基础入门阶段,实际上可能会有5个,6个甚至更多部分(比如业务层,界面层等等),大家了解一下即可。
三、BasicDAO的分析
1.基本说明 :
DAO(Data Access Object),指数据访问对象,用于完成对表中数据的访问。
BasicDAO作为这样的一个通用类,专门用于和数据库交互,即完成对数据库中表的crud操作;在BasicDAO的基础上,可以实现一张表对应一个DAO,以更好地完成功能。
eg : employee表 <=> Employee.java类(JavaBean)<=> EmployeeDAO.java。
2.简单设计 :
如果我们想设计并使用一个BasicDAO,可以在同一包下建多个子包,不同的子包存放不同功能的类或接口,此处我们只建立4个子包,up以src目录下的dao_ex包为演示包,如下图所示 :
在dao包下分别建立domain, dao, utils, test四个子包,它们的功能如下——
①dao_ex.domain : 用于存放相关JavaBean类;
②dao_ex.dao : 用于存放XxxDAO 和 BasicDAO;
③dao_ex.utils : 用于存放相关工具类;
④dao_ex.test : 用于存放DAO的测试类。
建立子包情况如下图所示 :
四、BasicDAO的实现
0.准备工作 :
up就以stus表为例,如下图所示 :
我们来逐个完成stus表对应的工具类,JavaBean类,DAO类和测试类。
1.工具类 :
此处的“工具类”其实就是我们之前在“连接池”小节中讲过的德鲁伊连接池的工具类,我们直接拿来用就行。
工具类JDBCUtilsDruid类,代码如下 :
package dao_ex.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtilsDruid {
private static DataSource dataSource;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放资源
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2.JavaBean类 :
对应于stus表中的id,name,sex,score四个字段,我们可以创建其对应的JavaBean类Stus类,并在其中建立四个字段,并给出它们的getter和setter方法,同时重写toString方法。
Stus类代码如下 :
package dao_ex.domain;
/**
stus表对应的JavaBean类
*/
public class Stus {
private Integer id;
private String name;
private String sex;
private Double score;
public Stus() {
}
public Stus(Integer id,String name,String sex,Double score) {
this.id = id;
this.name = name;
this.sex = sex;
this.score = score;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "Stus{" +
"id = " + id +
", name '= " + name + '\'' +
", sex = '" + sex + '\'' +
", score = " + score +
'}';
}
}
3.BasicDAO类 / StusDAO类 :
因为XxxDAO类是在BasicDAO类的基础上实现的,因此我们要先开发BasicDAO。
BasicDAO类代码如下 : (注意看注释!)
package dao_ex.dao;
import connection_pool.druid.JDBCUtilsDruid;
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 java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
(1)因为不确定将来会操作哪张表的DAO类,因此BasicDAO要使用泛型。
(2)设置形参后,可以指定地传入一个要执行的SQL.
(3)注意:实现不同功能DQL的不同方法内,调用query方法时,传入的ResultSetHandle接口的实现类不同。
*/
public class BasicDAO<T> {
//定义一个QueryRunner属性
private QueryRunner queryRunner = new QueryRunner();
//1.执行DML的方法
/**
* @param sql : 要执行的SQL
* @param parameters : 为SQL中的?进行赋值的可变参数
* @return : 返回受影响的行数
*/
public int update(String sql, Object... parameters) {
//获取链接
Connection connection = null;
try {
connection = JDBCUtilsDruid.getConnection();
int affectedRows = queryRunner.update(connection,sql,parameters);
return affectedRows;
} catch (SQLException e) {
throw new RuntimeException(e); //编译异常转换为运行异常
} finally {
//释放资源
JDBCUtilsDruid.close(null, null, connection);
}
}
//2.执行“返回多条记录的”DQL的方法(同样使用泛型)
/**
* @param sql : 要执行的SQL
* @param clazz : 根据传入的Class对象类型进行反射,
* 以确定对应的JavaBean实例类型,即List集合中存放什么类型。
* @param parameters : 为SQL中的?赋值的可变参数
* @return : 返回一个ArrayList类对象(eg : 返回了一个存放Stus实例的ArrayList集合)
*/
public List<T> queryMultiply(String sql, Class<T> clazz, Object... parameters) {
//获取连接
Connection connection = null;
try {
connection = JDBCUtilsDruid.getConnection();
List<T> query = queryRunner.query(connection,sql,new BeanListHandler<T>(clazz),parameters);
return query;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
}
//3.执行“返回单条记录的”DQL的方法
/**
* @param sql : 同上
* @param clazz : 同上,但只是作为单个实例来接收表中的一行数据(一条记录),没有集合
* @param parameters : 同上
* @return : 返回一个对应的JavaBean实例(该实例的属性保存了表中某一条记录的全部信息)
*/
public T querySingle(String sql, Class<T> clazz, Object... parameters) {
//获取连接
Connection connection = null;
try {
connection = JDBCUtilsDruid.getConnection();
return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
}
//4.执行“返回单行单列的”DQL的方法(即返回单值)
/**
* @param sql : 同上
* @param parameters : 同上
* @return : 由于仅返回单行单列,其实就是某一个具体的值,因此只需要使用Object类型做接受即可。
*/
public Object queryScalar(String sql, Object... parameters) {
//获取连接
Connection connection = null;
try {
connection = JDBCUtilsDruid.getConnection();
return queryRunner.query(connection,sql, new ScalarHandler(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
}
}
BasicDAO开发完毕后,我们可以创建负责stus表操作的StusDAO,并令其去继承BasicDAO。StusDAO代码如下 :
package dao_ex.dao;
import dao_ex.domain.Stus;
/**
StusDAO————负责管理stus表的DAO
*/
public class StusDAO extends BasicDAO<Stus> {
/*
StusDAO继承BasicDAO之后,便拥有了BasicDAO的全部非私有成员。
若业务需求复杂,也可以在StusDAO下自定义特有方法。
*/
}
4.测试类 :
在test包下创建用于测试DAO的TestDAO类,在TestDAO类中完成对StusDAO的访问。
TestDAO类代码如下 :
package dao_ex.test;
import dao_ex.dao.StusDAO;
import dao_ex.domain.Stus;
import org.testng.annotations.Test;
import java.util.List;
/**
(1)关闭连接的操作封装在了BasicDAO的方法中;
(2)关闭PreparedStatement和ResultSet的操作封装在了query方法中;
*/
public class TestDAO {
@Test
public void testStusDAO() {
//1.测试“返回多条记录”的DQL的执行
String sql1 = "SELECT * FROM stus " +
"WHERE `id` >= ?;";
StusDAO stusDAO = new StusDAO();
List<Stus> stusData = stusDAO.queryMultiply(sql1, Stus.class, 1);
for (Stus stusDatum : stusData) {
System.out.println(stusDatum);
}
System.out.println("------------------------------------------------");
//2.测试“返回单条记录”的DQL的执行
String sql2 = "SELECT * FROM stus " +
"WHERE `id` = ?;";
Stus stus = stusDAO.querySingle(sql2, Stus.class, 1);
System.out.println(stus);
System.out.println("------------------------------------------------");
//3.测试“返回单行单列(即返回单值)”的DQL的执行
String sql3 = "SELECT name FROM stus " +
"WHERE `id` = ?;";
Object o = stusDAO.queryScalar(sql3, 1);
System.out.println(o);
System.out.println("------------------------------------------------");
//4.测试DML的执行(INSERT, DELETE, UPDATE)
String sql4 = "INSERT INTO stus " +
"VALUES " +
"(NULL, ?, ?, ?);";
String sql5 = "UPDATE stus " +
"SET `name` = ? " +
"WHERE `name` = ?;";
String sql6 = "DELETE FROM stus " +
"WHERE `id` = ?;";
int update1 = stusDAO.update(sql4, "Wood", "male", 399.0);
int update2 = stusDAO.update(sql5, "Karry", "Cyan");
int update3 = stusDAO.update(sql6, 4);
System.out.println("插入操作的DML执行成功了吗?" + (update1 > 0 ? "yes!" : "no!"));
System.out.println("修改操作的DML执行成功了吗?" + (update2 > 0 ? "yes!" : "no!"));
System.out.println("删除操作的DML执行成功了吗?" + (update3 > 0 ? "yes!" : "no!"));
}
}
运行结果 :
我们再来查询一下stus表,看看DML操作有没有对stus造成影响,如下图所示 :
OK,没有问题。
五、总结
- 🆗,以上就是JDBC系列博文第七节的全部内容了。
- 总结一下,我们先是围绕“德鲁伊连接池 + 工具类”存在的弊端——即“SQL的优化”引入了DAO的概念;接着,又画出了BasicDAO的基本示意图;然后,进行了BasicDAO的基本设计,主要分为DAO, Domain, Utils, Test四部分;最后,我们以stus表为例,演示了BasicDAO的具体使用。大家一定要掌握BasicDAO的基本示意图,以及BasicDAO的设计逻辑。
- 下一节内容——JDBC 望舒客栈项目,我们不见不散。感谢阅读!
System.out.println("END-----------------------------------------------------------------------------");