在进行测试的时候,经常需要对数据库进行操作。我们知道,通过代码与数据库交互,需要以下几步:

1.加载驱动

之前有盆友问我,为什么Selenium操作浏览器的时候,非要下载浏览器驱动?为啥对数据库进行操作的时候,直接加载driver就可以了呢?--------之类稍作解释:浏览器并没有供一个API给java来直接操作浏览器,而是提供了一套API给selenium中的XXXDriver.exe(如Chrome的ChromeDriver)来对自己进行各种操作,此时我们想自动化测试浏览器就要下载selenium官网上的各浏览器驱动,通过这个浏览器驱动再去操作浏览器;而各类数据库,如mySql是提供了一套api直接给java等各种开发语言使用,来直接对数据库进行各种操作。---这种情况下,我们直接加载数据库的driver即可。

驱动加载的工作原理:

Class.forName("com.mysql.jdbc.Driver");

在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例

在初始化的时候会执行注册当前Driver给DriverManager

驱动加载一次就可了,所以一般写在static程序块中。

2.创建数据库链接

加载驱动之后,就可以通过DriverManager来管理并使用该驱动。通过DriverManager.getConnection()方法来获得一个数据库连接。

遍历所有之前已经注册过的驱动,能成功建立连接的则返回。

3.准备sql

4.创建数据库陈述对象/预编译陈述对象

作者这里选择的是预编译陈述对象,因为可以防止Sql注入。sql注入的在这里不再赘述。

preparedStatement p=connect.preparedStatement(sql);

作用是创建一个参数化的preparedStatement对象参数化的sql语句来发送到数据库。

5.执行sql

一般select使用p.executeQuery()来执行,返回的是一个resultSet的结果集

update、delete、insert使用executeUpdate() ,对于dml语言,返回的就是受影响的行数

6.关闭之前打开的链接、resultSet和PreparedStatement

如果每次操作数据库,都要将上述语句都写一遍,会比较繁琐,代码看起来会比较臃肿。为了方便,通常将其封装为一个帮助类。关于数据库操作的封装:

1.只有查询会返回一个结果集,增删改只会返回受影响的行数---------------可以将增删改合并成一个方法

2.查询之后,我希望将查询结果对应到某个类中。比如我们在做自动化测试的时候,会选择把所有的数据库表映射成我们代码中的类A。此时,我希望从数据库中查询到的数据存储到类A的对象中。----------封装一个select方法,使其将查询结果保存到我们的类对象中。

3.查询之后,我们可能只是看一下结果,并不想把结果存放到某个类的对象。这时,就可能会考虑将查询结果放到一个list列表中。-----------封装一个方法,将我们的查询结果保存到list中

4.获得一个数据库链接的操作,也封装成方法,每次调用就可以返回一个数据库连接。

5.关闭数据库连接、关闭数据集、关闭预编译陈述,在每次数据库操作完成之后,都要去依次关闭。因此考虑也将其封装到一个方法中。

基于上面的考虑,笔者完成了下面的代码:

1.包含了获得数据库连接方法和关闭方法的类

package jdbc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties; public class jdbConnUnit {
static Connection conn = null;
static {
try {
//加载驱动,此时会自动向driverManager注册该driver的实例
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("找不到这个类jdbc.driver");
e.printStackTrace();
}
} /*
* 提供一个获取数据库连接的方法
* String properties参数 是数据库配置文件的绝对地址
*/
static Connection getConn(String properties) { try {
//通常将数据库连接信息写在一个配置文件中
//创建一个Properties对象
Properties pro = new Properties();
//创建一个流,怼到我们的配置文件上
FileInputStream stream = new FileInputStream(properties);
//将流加载到配置文件中,相当于缓存下来,之后直接get相关数据即可,不用用一次读一次
pro.load(stream); //getConnection方法会遍历之前加载进来的多有driver,尝试建立数据库连接,如果成功则返回
conn = DriverManager.getConnection(pro.getProperty("url"), pro.getProperty("userName"), pro.getProperty("password")); } catch (FileNotFoundException e) {
System.out.println("配置文件未找到!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("配置文件读取失败!");
e.printStackTrace();
} catch (SQLException e) {
System.out.println("数据库连接创建失败");
e.printStackTrace();
} return conn;
} /*
* 提供一个关闭之前打开的链接的方法
* PreparedStatement pStatement----预编译对象
* ResultSet... set------结果集,对于增删改操作,没有返回结果集,所以这里设计成可变参数列表的形式
*/
static void close(PreparedStatement pStatement,ResultSet... set) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.out.println("数据库连接关闭失败!");
e.printStackTrace();
}
}
if (pStatement != null) {
try {
pStatement.close();
} catch (SQLException e) {
System.out.println("预编译声明对象关闭失败!");
e.printStackTrace();
}
} for (int i = 0; i < set.length; i++) {
if (set[i] != null) {
try {
set[i].close();
} catch (SQLException e) {
System.out.println("结果集关闭失败!");
e.printStackTrace();
}
}
} } }

2.封装了查询方法和增删改方法的类

package jdbc;

import java.io.File;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap; import org.omg.PortableServer.IdAssignmentPolicy; public class jdbcExecUnit { /*
* 该方法是执行select语句,并将查询到的数据返回到调用者提供的类对象中。
* 该方法是一个泛型方法,泛型方法的声明方式为:修饰符 <T> 返回值类型 方法名(参数列表)
* class<T> class 要存储数据的类
* string properties 存储调用者自己的数据库配置文件
* String sql 存储调用者的sql语句
* String... args 存储调用者sql语句占位符相应的参数值
*/
public static <T> List<T> select2(Class<T> clazz, String properties, String sql, String... args)
throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException { //准备一个预编译的sql语句的对象,用来将预编译的sql发送到数据库
PreparedStatement pStatement = null;
//准备一个结果集,用来存储查询获得的结果
ResultSet DataSet = null;
//调用jdbConnUnit类中getConn的方法来获得一个数据库连接
Connection connection = jdbConnUnit.getConn(properties);
//准备一个List,来存放我们的泛型对象
List<T> list = new ArrayList<T>(); try { // 对sql语句执行预编译,并发送给数据库。可以防止sql注入
pStatement = connection.prepareStatement(sql); // 获取可变参数的个数(对应的是sql语句中占位符的个数)
int length = args.length; /* 利用pStatement.setString方法将参数一次赋值给sql中的占位符
* select Id,RoleName from roles WHERE RoleName =? orRoleName=?,
* 相当于给?占位符的位置填充好参数
*/
for (int i = 0; i < args.length; i++) {
pStatement.setString(i + 1, args[i]);
} // 执行预编译好的sql,获得一个结果集
DataSet = pStatement.executeQuery();
// 获得结果集对象的 列的描述(可以通过它得到表头、数据集的列数)
ResultSetMetaData dataTitle = DataSet.getMetaData();
//获得列数
int length1 = dataTitle.getColumnCount(); // 利用结果集的next方法,将游标指向第一行数据,以便后面将数据取出
while (DataSet.next()) {
// 得到泛型T的对象
T t = clazz.newInstance();
// 获得T所有的属性
Field[] fields = clazz.getDeclaredFields();
/*
* 下面的for,判断了数据的类型,按照从数据库读出来的类型对应存储到对象中
*/
for (int i = 0; i < fields.length; i++) {
//得到对应的域,并获得域名
Field field = fields[i];
String tempFieldName = field.getName();
//将每一个域名与列名进行对比,如果有,则赋值
for (int j = 0; j < length1; j++) {
//得到列名
String columnName = dataTitle.getColumnName(j + 1);
if (tempFieldName.equals(columnName)) {
// 将查询出来的resultSet中数据读取为object类型,再将其转换成和域相同的类型
field.set(t, (field.getType().cast(DataSet.getObject(columnName))));
}
}
} // 下面的for,没有判断数据类型,直接按照String类型存入到成员变量中
/*
* for (int i = 0; i < length1; i++) {
*
* String tempTitle=dataTitle.getColumnName(i+1); //根据表头中的列名来获得对应的属性名称 Field
* field=clazz.getDeclaredField(tempTitle); //给对象t的属性field赋值 field.set(t,
* DataSet.getString(tempTitle));
*
* }
*/ //将每一个t对象加入到list中
list.add(t); } } catch (SQLException e) {
System.out.println("查询语句执行失败!");
e.printStackTrace();
}finally{//这个关闭必须写在finally里面,如果获取的conn或者statement的时候出现了问题,这没有在finally中的话,就调用不到close 方法了,或者conn为null的时候,还会报空指针异常
jdbConnUnit.close(pStatement, DataSet);
} return list; } /* * 该方法是执行查询语句,并将结果保存到map中返回。 * 参数: * String properties----数据库配置文件的绝对地址; * String sql-----要执行的sql语句 * String... args----上述sql语句中如果包含占位符?,就需要传入占位符相应的参数值 */ public static List<Map<String, String>> select(String properties, String sql, String... args) { ResultSet DataSet = null; Connection connection = jdbConnUnit.getConn(properties); List<Map<String, String>> list = new ArrayList<Map<String, String>>(); PreparedStatement pStatement = null; try { // 对sql语句执行预编译,发送给数据库。防止sql注入 pStatement = connection.prepareStatement(sql); // 获取可变参数的个数(对应的是占位符的个数) int length = args.length; // 利用setString方法将参数一次赋值给sql中的占位符 // select Id,RoleName from roles WHERE RoleName =? or // RoleName=?,相当于给?占位符的位置填充好参数 for (int i = 0; i < args.length; i++) { pStatement.setString(i + 1, args[i]); } // 执行预编译好的sql,获得一个结果集 DataSet = pStatement.executeQuery(); // 获得结果集对象列的描述(包括数量、类型、属性) ResultSetMetaData dataTitle = DataSet.getMetaData(); int length1 = dataTitle.getColumnCount(); // 利用结果集的next方法,将光标移动到第一行数据,之后获得这一行的数据 while (DataSet.next()) { //这里的map用来存放一行数据,key为列名,Value为列名对应的值。 //必须在循环内部创建,否则最后的list中每个元素都会指向同一个map,而且由于map的key的不可重复性,后面的值会全部覆盖掉前面的值 Map<String, String> map = new TreeMap<String, String>(); for (int i = 0; i < length1; i++) { map.put(dataTitle.getColumnName(i + 1), DataSet.getString(dataTitle.getColumnName(i + 1))); // System.out.println(dataTitle.getColumnName(i+1));  } list.add(map); } } catch (SQLException e) { System.out.println("查询语句执行失败!"); e.printStackTrace(); } jdbConnUnit.close(pStatement, DataSet); return list; } /* * 该方法提供了增删改的语句的执行,返回受影响的行数 * String properties----数据库配置文件的绝对地址; * String sql-----要执行的sql语句 * String... args----上述sql语句中如果包含占位符?,就需要传入占位符相应的参数值 */ public static int update(String properties, String sql, String... args) { Connection connection = jdbConnUnit.getConn(properties); PreparedStatement pStatement = null; try { // 预编译sql pStatement = connection.prepareStatement(sql); } catch (SQLException e) { System.out.println("sql预编译失败!"); e.printStackTrace(); } for (int i = 0; i < args.length; i++) { try { // 给预编译之后的sql中的参数(占位符的位置)赋值 pStatement.setString(i + 1, args[i]); } catch (SQLException e) { System.out.println("更新sql占位符赋值失败"); e.printStackTrace(); } } int byUpdate = 0; try { // 执行增删改的操作 byUpdate = pStatement.executeUpdate(); } catch (SQLException e) { System.out.println("执行增删改异常"); e.printStackTrace(); } if (byUpdate > 0) { System.out.println("更新增删改操作成功,更新了" + byUpdate + "条数据"); } else { System.out.println("更新操作失败"); } jdbConnUnit.close(pStatement); return byUpdate; } }

3.下面是与数据库表对应的一个类(为了演示,只取出了测试库一个表的映射。这种做法是就是根据ORM--对象关系映射来实现的)

package jdbc;

import java.math.BigDecimal;

public class  Trafficinfos{
//Addition AdultPrice ChildPrice CreateTime Destination
//From IsDeleted StartDate TrafficNo TrafficTypeId TravelGroupId
String Id;
String Addition;
BigDecimal AdultPrice;
String ChildPrice;
String CreateTime;
String Destination;
String From;
String IsDeleted;
String StartDate;
String TrafficNo;
String TrafficTypeId;
String TravelGroupId;
public String getId() {
return Id;
}
public void setId(String id) {
Id = id;
}
public String getAddition() {
return Addition;
}
public void setAddition(String addition) {
Addition = addition;
}
public BigDecimal getAdultPrice() {
return AdultPrice;
}
public void setAdultPrice(BigDecimal adultPrice) {
AdultPrice = adultPrice;
}
public String getChildPrice() {
return ChildPrice;
}
public void setChildPrice(String childPrice) {
ChildPrice = childPrice;
}
public String getCreateTime() {
return CreateTime;
}
public void setCreateTime(String createTime) {
CreateTime = createTime;
}
public String getDestination() {
return Destination;
}
public void setDestination(String destination) {
Destination = destination;
}
public String getFrom() {
return From;
}
public void setFrom(String from) {
From = from;
}
public String getIsDeleted() {
return IsDeleted;
}
public void setIsDeleted(String isDeleted) {
IsDeleted = isDeleted;
}
public String getStartDate() {
return StartDate;
}
public void setStartDate(String startDate) {
StartDate = startDate;
}
public String getTrafficNo() {
return TrafficNo;
}
public void setTrafficNo(String trafficNo) {
TrafficNo = trafficNo;
}
public String getTrafficTypeId() {
return TrafficTypeId;
}
public void setTrafficTypeId(String trafficTypeId) {
TrafficTypeId = trafficTypeId;
}
public String getTravelGroupId() {
return TravelGroupId;
}
public void setTravelGroupId(String travelGroupId) {
TravelGroupId = travelGroupId;
} //toString
@Override
public String toString() {
return "Trafficinfos [Id=" + Id + ", Addition=" + Addition + ", AdultPrice=" + AdultPrice + ", ChildPrice="
+ ChildPrice + ", CreateTime=" + CreateTime + ", Destination=" + Destination + ", From=" + From
+ ", IsDeleted=" + IsDeleted + ", StartDate=" + StartDate + ", TrafficNo=" + TrafficNo
+ ", TrafficTypeId=" + TrafficTypeId + ", TravelGroupId=" + TravelGroupId + "]";
} }

上面就完成了一个对数据库操作的简单封装。提供一个测试类,进行简单的测试:

package jdbc;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set; public class TestJdbc { public static void main(String[] args)
throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException {
// update执行
String updateSql = "UPDATE trafficinfos set AdultPrice=?,`From`=?;";
jdbcExecUnit.update("D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties", updateSql, "3333",
"yuhangnanyuan"); // select执行,数据存储到map里
String selectSql = "SELECT Id,AdultPrice FROM trafficinfos ;";
List<Map<String, String>> data = jdbcExecUnit.select("D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties",selectSql); for (Map<String, String> map : data) {
Set<String> keySets = map.keySet();
for (String keyset : keySets) {
System.out.println(keyset + "--------" + map.get(keyset));
}
} // select执行,数据存储到类的对象里
String selectSql2 = "SELECT Id,AdultPrice FROM trafficinfos ;";
List<Trafficinfos> list = jdbcExecUnit.<Trafficinfos>select2(Trafficinfos.class,
"D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties", selectSql2);
for (Trafficinfos trafficinfos : list) {
System.out.println(trafficinfos);
} } }

完成上述封装之后,可以打成jar包,提交到maven库上,供其他人使用。

自动化测试--封装JDBCUnit的更多相关文章

  1. APP 自动化测试封装结构模式

    原文出处http://www.toutiao.com/a6268089772108333314/ 做过UI自动化测试同学,都会深深体会几个痛点:维护量大.适配量大.编写代码巨大等.基于这些问题,大家都 ...

  2. 自动化测试--封装getDriver的方法

    在自动化测试的时候,通常都会把最常用的功能封装起来,实现通用性. 该篇博客是实现了getDriver方法的封装. 第一次封装的时候,是使用的传参. @Parameters(value = {" ...

  3. appium+python 【Mac】UI自动化测试封装框架流程简介 <一>

    为了多人之间更方便的协作,那么框架本身的结构和编写方式将变得很重要,因此每个团队都有适合自己的框架.如下本人对APP的UI自动化测试的框架进行进行了简单的汇总.主要目的是为了让团队中的其余人员接手写脚 ...

  4. appium+python 【Mac】UI自动化测试封装框架介绍 <二>---脚本编写(单设备)

    1.单设备的执行很简单,平时可多见的是直接在config中进行配置并进行运行即可.如下: # coding=UTF- ''' Created on // @author: SYW ''' from T ...

  5. appium+python 【Mac】UI自动化测试封装框架介绍 <七>---脚本编写规范

    脚本的使用,注释非常关键,无论自己的后期查看还是别人使用,都可以通过注释很明确的知道代码所表达的意思,明确的知道如何调用方法等等.每个团队均有不同的商定形式来写脚本,因此没有明确的要求和规范来约束.如 ...

  6. appium+python 【Mac】UI自动化测试封装框架介绍 <五>---脚本编写(多设备)

    目的: 通过添加设备号,则自动给添加的设备分配端口,启动对应的appium服务.注意:为了方便,将共用一个配置文件. 1.公共的配置文件名称:desired_caps.yaml platformVer ...

  7. appium+python 【Mac】UI自动化测试封装框架介绍 <四>---脚本的调试

    优秀的脚本调试定位问题具备的特点: 1.方便调试. 2.运行报错后容易定位出现的问题. 3.日志的记录清晰 4.日志可被存储,一般测试结果的分析在测试之后会进行,那么日志的存储将会为后期的分析问题带来 ...

  8. appium+python 【Mac】UI自动化测试封装框架介绍 <三>---脚本的执行

    我自己编写的脚本框架中,所有的脚本执行均放在一个py文件中,此文件作为启动文件执行,包含了运行此文件将执行脚本.分配设备端口.自启appium服务等. 详细的介绍待后期补充.

  9. Selenium自动化测试Python四:WebDriver封装

    WebDriver 封装 欢迎阅读WebDriver封装讲义.本篇讲义将会重点介绍Selenium WebDriver API的封装的概念和方法,以及使用封装进行自动化测试的设计. WebDriver ...

随机推荐

  1. maven项目发布到Tomcat丢失jar包

    昨天看了一篇tomcat设置的文章,说要把第一个勾上,这样不需要更新到tomcat.  一启动tomcat就发现丢包.后来在网上看了许多文章,说要update maeven项目,然后你就会发现启动过程 ...

  2. Tomcat 服务器体系结构

    connector 监听端口,监听到以后,交给 Engine 引擎 处理,引擎会根据请求找到对应的主机,找到主机后再去找对应的应用. 如果我们将 port 改为 80,那访问的时候就不用输入端口号,因 ...

  3. sql中 decode() 的用法

    sql中 decode() 的用法 SELECT ID,DECODE(inParam,'Param','value1' ,'value2') name FROM yytj2018 如果 inParam ...

  4. vue 修改框架less变量

    以vant框架为例,vue项目以less作为css处理器: less/var-reset.less @import '~vant/lib/index.less'; // Color variables ...

  5. GET&&POST请求编码过程

    编码.解码 我们在开发过程中不可避免的一个话题就是编码和解码,那么什么是编码什么是解码呢?为什么要进行编码和解码呢?下面我们一一分析! 编码和解码的概念 编码是信息从一种形式或格式转换为另一种形式的过 ...

  6. 简易Dubbo的搭建过程

    dubbo是一个高性能的,基于java的,开源RPC框架,主要功能是让构建分布式计算更加容易. (分布式:多台计算机实现不同功能,形成一个整体对外服务) (集群式:多台计算机实现相同功能,分担计算压力 ...

  7. Oracle递归 start with...connect by...prior

    prior一侧是父节点 另一侧是子节点 --查询region_id等于4519的节点下面的所有子节点 查找出给定节点的所有子节点 SELECT sr.* FROM spc_region sr wher ...

  8. C++定义一个简单的Computer类

    /*定义一个简单的Computer类 有数据成员芯片(cpu).内存(ram).光驱(cdrom)等等, 有两个公有成员函数run.stop.cpu为CPU类的一个对象, ram为RAM类的一个对象, ...

  9. <CPP学习>第一天 第一个CPP程序 hello word

    由于我是计算机类嵌入式专业的大一学生,之前一直使用的是生万物的C语言,了解了其过程性语言的特性及其基础语法,在大一下学期期末阶段想自学一下C++,其实在开学初就买了一本C++ Primer,但由于各种 ...

  10. HTTP头部信息解释分析

    HTTP 头部解释 1. Accept:告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type. 2. Accept-Chars ...