目录
一、前言
- 第七节内容,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-----------------------------------------------------------------------------");