您的位置:首页 > 其它

JDBC学习笔记

2015-12-21 14:51 417 查看
JDBC是什么?为什么要使用JDBC呢?

大家都知道,有各种数据库可以使用,如mysql、oracle、DB2等,那么在程序开发过程中,如何保证程序与数据库的独立性,以及降低访问数据库的操作繁杂性,引入了JDBC(Java Data Base Connectivity),相当于一种介质,负责java程序与数据库的沟通。JDBC实际上提供了一套标准,一套接口,各数据厂商安装这个标准去开发自己的数据库软件,从而隐藏了各数据库软件底层的差异,使用者只需调用这些标准的接口,就能完成一系列对数据库的操作。

1、Driver接口与DriverManager

Driver接口

Driver接口是JDBC这套标准接口中,最基本的接口,每个数据库厂商都必须实现这个接口以及其提供的方法。例如mysql数据库的该接口的实现类就是com.mysql.jdbc.Driver

Driver接口中有connect(String url, Properties info)方法,负责与数据连接,并返回相应的连接Connection类对象。

@Test
public void test() throws SQLException {

//创建Driver接口的实现类对象
Driver driver = new com.mysql.jdbc.Driver();
String jdbcUrl= "jdbc:mysql://localhost:3306/test";
Properties prop = new Properties();
prop.put("user", "root");
prop.put("password", "root");
//利用connect方法连接数据库
Connection connection = driver.connect(jdbcUrl, prop);
System.out.println(connection);

}
可以看出,连接数据库的四要素:Driver的实现类,数据库访问url,用户user和密码password

数据库url的格式:

jdbc:mysql://localhost:3306/test

协议:子协议://地址:端口号/数据库的表

但是直接使用Driver接口,必须要与其实现类关联,可以使用配置文件降低耦合,在配置文件中写入Driver的实现类的全类名,在程序中使用反射获得实现类对象。

public Connection getConnection() throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{
String url = null;
String user = null;
String password = null;
String driverClass = null;

//将配置文件读入到输入流InputStream中
InputStream in= getClass().getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
//利用load方法加载输入流
prop.load(in);

url = prop.getProperty("url");
driverClass = prop.getProperty("driver");
user = prop.getProperty("user");
password = prop.getProperty("password");

Properties info = new Properties();
info.put("user", user);
info.put("password", password);
//使用反射,根据Driver的全类名获得实现类
Driver driver = (Driver) Class.forName(driverClass).newInstance();
Connection connection = driver.connect(url, info);

return connection;
}
jdbc.properities文件

driver = com.mysql.jdbc.Driver
user = root
password = root
url = jdbc:mysql://localhost:3306/test


所以为了降低耦合,以及更方便的与数据库相连,一般使用DriverManager访问数据库,DriverManager的底层实现就是依赖Driver接口的实现,Driver接口一般不直接使用。

DriverManager

DriverManager可以注册多个Driver的实现类,即不同的数据库驱动,从而可以根据url来自动区分应该使用哪一个数据库的驱动类

@Test
public void testDriverManager() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException{
String url="jdbc:mysql://localhost:3306/test";
String url2="jdbc:oracle:thin:@localhost:1521:orcl";
String user="root";
String password = "root";
String driverClass = "com.mysql.jdbc.Driver";
String driverClass2="oracle.jdbc.driver.OracleDriver";

//加载驱动(注册驱动)
//DriverManager.registerDriver((Driver) Class.forName(driverClass).newInstance());
//Driver的实现类里 有静态代码块,将驱动实例注册到DriverManager
//同时加载两种数据库驱动
Class.forName(driverClass);
Class.forName(driverClass2);
//可以根据url的不同,自动选择驱动类型
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("DriverManager :"+connection);
}
使用DriverManager连接数据库的步骤:

1、在配置文件中配置四要素:驱动全类名、数据库url、user、password,并在程序中利用Properties类获取到

2、使用反射加载数据库驱动 :Class.forName或者DriverManager的registerDriver方法

3、使用DriverManager的getConnection方法获取连接

public Connection getConnection2() throws IOException, ClassNotFoundException, SQLException{
//1.获取四要素
String url = null;
String user = null;
String password = null;
String driverClass = null;

InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(in);

url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
driverClass = prop.getProperty("driver");

//2.注册驱动
Class.forName(driverClass);

//3.获得连接
Connection connection = DriverManager.getConnection(url, user, password);

return connection;
}

注意:mysql与oracle的url和驱动实现类的区别

mysql:

url jdbc:mysql://localhost:3306/test (默认可写为 jdbc:mysql:///test)

全类名: com.mysql.jdbc.Driver

oracle:

url jdbc:oracle:thin:@localhost:1521:orcl(待检验)

全类名: oracle.jdbc.driver.OracleDriver

2、Statement 执行sql语句

利用DriverManager获取连接之后,就需要Statement来执行我们需要的sql语句了,

执行sql语句的过程

大概分为四步:

1、建立连接、关闭连接(建立之后顺手关闭,中间再插入代码,为了不忘记关闭,因为建立的连接以及Statement都是应用程序与数据库连接的资源,要及时释放)

2、建立Statement,释放statement

3、准备sql语句

4、执行sql语句

@Test
public void testStatement() throws ClassNotFoundException, IOException, SQLException{
Connection con=null;
Statement state = null;
try {
//1.1获取连接
con = getConnection2();

//2.构建sql语句
String sql=null;
sql="INSERT customers(NAME,EMAIL,BIRTH) VALUES('yuchen','yuchen@cqupt.com','1990-12-12' )";
//			sql= "DELETE FROM customers WHERE id=2;";
//			sql="UPDATE customers SET NAME='yy', EMAIL= 'yy@cqupt.com', BIRTH='1992-11-12' WHERE id=3";

//3.1 获取Statement
state = con.createStatement();

//4. 执行sql语句,通过executeUpdata执行的语句只能是 INSERT,DELETE,UPDATA,不能使SELECT
state.executeUpdate(sql);

} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(state!=null)
//3.2关闭statement
state.close();
} catch (Exception e1) {
e1.printStackTrace();
}finally{
if(con!=null)
//1.2 关闭连接
con.close();
}
}

}
为了确保connection 与 statement都能关闭,即使在出现异常时,所以最后使用嵌套的try/catch/finally语句

也可以是这样:try/catch代码块,只要异常被catch捕捉到,后续的代码是可以照常执行的;如果不能被捕捉到,后续代码块是不能执行的。

}finally{
if(con != null){
try{
con.close();
}catch(Exception ex){
ex.printStackTrace();
}
}

//因为如果con.close()发生异常,被后续catch捕获,catch代码块执行完毕会接着执行state的关闭
if(state != null){
try{
state.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
}

工具类的建立与使用

其实可已看出,上述代码单不美观,而且繁杂,仔细分析,我们在执行sql语句的过程可以分为几个部分:获取连接,获取statement,执行sql,释放资源

那么,我们把根据properties文件获得连接 、和释放资源单独拿出来,作为工具方法Tools

建立一个工具类JDBCTools,所有方法为静态

public class JDBCTools {

public static void release(Statement statement, Connection conn){
if(statement != null){
try{
statement.close();
}catch (Exception e){
e.printStackTrace();
}
}

if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException{
//1.获取四要素
String url = null;
String user = null;
String password = null;
String driverClass = null;

InputStream in = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");//注意静态方法中的使用
Properties prop = new Properties();
prop.load(in);

url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
driverClass = prop.getProperty("driver");

//2.注册驱动
Class.forName(driverClass);

//3.获得连接
Connection connection = DriverManager.getConnection(url, user, password);

return connection;
}
}

那么在执行sql语句时就简单多了:

public void update(String sql){

Connection conn=null;
Statement statement=null;
try {
//获得连接
conn = JDBCTools.getConnection();
//获取statement
statement = conn.createStatement();
//执行sql
statement.executeUpdate(sql);
} catch (ClassNotFoundException | IOException | SQLException e) {
e.printStackTrace();
} finally{
//释放资源
JDBCTools.release(statement, conn);
}

}

@Test
public void testStatement() throws ClassNotFoundException, IOException, SQLException{
String sql = "DELETE FROM customers WHERE id =3";
update(sql);
}


在编写jdbc程序时,要时刻注意用ty/catch/fianlly将连接资源的释放

当然也可以把update方法,写到JDBCTools中。

3、结果集ResultSet

结果集ResultSet是执行查询语句,将查询到的结果保存的一个类。

方法next()查看其是否有下一列数据,并将指针下移一列。resultSet的指针初始指向数据的第一列之前,所以要先resultSet.next()之后才可以读数据。

从中获取值得方法有两种,一种是依据类型和列标号,resultSet.getInt(1);标号是从1开始得,根据“SELECT 字段 FROM”中的字段进行定位。

一种是根据类型和列的字段名称:如resultSet.getDate("Birth");返回值类型是Date型

ResultSet使用完毕之后要关闭。

public class JDBCTest {

@Test
public void testResultSet() {

Connection conn =null;
Statement statement = null;
ResultSet resultSet = null;
try {
conn = getConnection();
statement = conn.createStatement();
String sql= "SELECT id, name, email,birth FROM customers  ";
resultSet = statement.executeQuery(sql);

if(resultSet.next()){

//			String name = resultSet.getString("NAME");
String name = resultSet.getString(2);

//			String email = resultSet.getString("EMAIL");
String email = resultSet.getString(3);

Date date = resultSet.getDate("BIRTH");
//			Date date = resultSet.getDate(4);

System.out.println("name :"+name+" email:"+email+" birth:"+date);
}
} catch (InstantiationException | IllegalAccessException
| ClassNotFoundException | IOException | SQLException e) {
e.printStackTrace();
} finally{
try {
//关闭结果集
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCTools.release(statement, conn);
}
}
}


当然也可以重载JDBCTools的release方法,一次关闭连接、statement、resultSet资源。如果将返回的数据全部显示,可以while(resultSet.next())

4、以面向对象的方法向数据库中输入信息

我们经常说以面向对象的方法写程序,那么在向数据库中输入信息时,该如何利用面向对象的思想呢?

其实,一个数据库的表,就可以看作为一个类型,表中的每一行数据都可以作为一个对象看待,所以,向一个表中输入数据,就先构造该表的一个类,用该类的对象,进行信息的输入。

数据库中的examstudent表,有字段:FlowId, Type, IdCard, ExamCard, StudentName, Location, Grade,

构造一个Student类型

public class Student {

@Override
public String toString() {
return "Student [flowId=" + flowId + ", type=" + type + ", idCard="
+ idCard + ", exameCard=" + exameCard + ", studentName="
+ studentName + ", location=" + location + ", grade=" + grade
+ "]";
}
public int getFlowId() {
return flowId;
}
public void setFlowId(int flowId) {
this.flowId = flowId;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getExameCard() {
return exameCard;
}
public void setExameCard(String exameCard) {
this.exameCard = exameCard;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
private int flowId;
private int type;
private String idCard;
private String exameCard;
private String studentName;
private String location;
private int grade;

}
利用控制台的Scanner类,进行数据的输入,并赋值到Student类的对象student中

public Student getStudentFromConsole(){
Scanner scanner = new Scanner(System.in);
Student student = new Student();

System.out.print("Please input FlowId:");
student.setFlowId(scanner.nextInt());

System.out.print("Please input Type:");
student.setType(scanner.nextInt());

System.out.print("Please input IdCard:");
student.setIdCard(scanner.next());

System.out.print("Please input ExameCard:");
student.setExameCard(scanner.next());

System.out.print("Please input StudentName:");
student.setStudentName(scanner.next());

System.out.print("Please input Location:");
student.setLocation(scanner.next());

System.out.print("Please input Grade:");
student.setGrade(scanner.nextInt());

return student;
}
利用student进行对数据库信息的插入

public void addNewStudent(Student student){
//		String sql="INSERT  INTO examstudent VALUES(1,2,'123456','1214','Yuchen','BeiJing',98)";
String sql = "INSERT INTO examstudent"
+" VALUES("+student.getFlowId()
+","+student.getType()
+",'"+student.getIdCard()
+"','"+student.getExameCard()
+"','"+student.getStudentName()
+"','"+student.getLocation()
+"',"+student.getGrade()+")";//这种sql拼写的方式非常费力,且易出错
System.out.println(sql);

JDBCTools.update(sql);
}
那么在主程序中就调用即可:

@Test
public void testAddNewStudent(){
Student student = getStudentFromConsole();
addNewStudent(student);
}
这样就完成了数据的插入。

不过可以看出,在这个过程中,使用sql的拼写方式,对于大型数据库的操作,非常费力,而且容器出错,所以就引入了PreparedStatement这个类。

5、PreparedStatement

PreparedStatement

的使用好处,其一就是避免了拼写sql语句带来的麻烦,避免产生错误;其二就是可以避免由于拼接sql产生的sql注入安全问题。

PreparedStatement是Statement的一个子接口,也是在建立连接之后,通过连接对象获得。

执行增删改的方法都是,通过对象的executeUpdate方法。

不过,PreparedStatement是在获取对象时,传入具有占位符类型的sql语句。"INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)",在执行executeUpdate方法时,不需要再传入sql语句;

而Statement在获取对象是,不需要传入sql,而是在执行executeUpdate方法时,传入sql语句。

@Test
public void testAddNewStudent(){
Student student = getStudentFromConsole();
addNewStudent(student);
}
public void addNewStudent(Student student){
String sql= "INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)";
Connection conn=null;
PreparedStatement preparedStatement = null;

try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, student.getFlowId());
preparedStatement.setInt(2, student.getFlowId());
preparedStatement.setString(3, student.getIdCard());
preparedStatement.setString(4, student.getExameCard());
preparedStatement.setString(5, student.getStudentName());
preparedStatement.setString(6, student.getLocation());
preparedStatement.setInt(7, student.getGrade());

preparedStatement.executeUpdate();
} catch (ClassNotFoundException | IOException | SQLException e) {
e.printStackTrace();
}finally{
JDBCTools.release(preparedStatement, conn);
}

}
为了抽离出PreparedStatement的使用,对于所有数据库的操作都适合,建立一个工具类方法update

public static void updata(String sql, Object ... args){//可变形参使用
Connection conn = null;
PreparedStatement preparedStatement = null;

try {
conn = getConnection();
preparedStatement = conn.prepareStatement(sql);

for(int i=0; i<args.length;i++){
preparedStatement.setObject(i+1, args[i]);
}
preparedStatement.executeUpdate();
} catch (ClassNotFoundException | IOException | SQLException e) {
e.printStackTrace();
} finally{
release(null,preparedStatement, conn);
}

}
那么在主程序:

@Test
public void testAddNewStudent(){
Student student = getStudentFromConsole();
addNewStudent(student);
}
public void addNewStudent(Student student){
String sql = "INSERT INTO examstudent(flowid, type, idcard, examcard, studentname, location, grade) "
+ "VALUES(?,?,?,?,?,?,?)";
JDBCTools.updata(sql, student.getFlowId(), student.getType(), student.getIdCard(),
student.getExameCard(), student.getStudentName(), student.getLocation(), student.getGrade());
}
这样基于PreparedStatement的update方法就可以被其他程序使用了。

SQL注入

SQL注入是什么呢?其实就是利用sql查询语言的局限,通过一些手段,使得不通过正常的信息就可以得到数据库中想要的数据。

sql注入需要对sql查询语言非常熟悉,才能使用sql注入方法。

例如:数据库中有信息username:yuchen和password:123456

那么正常验证该用户的查询语句为:SELECT * FROM users WHERE username = 'yuchen' AND passoword = '123456';

那么使用sql注入:SELECT * FROM users WHERE username = 'a ' OR password = ' AND password =' OR '1'='1';

其实就是用户名为a ' OR password = 和密码为 OR '1'='1

Statement 不能防止sql注入,因为sql语句是字符串与参数拼写起来的,而PreparedStatement可以防止sql注入,因为sql是占位符 以及 对象传参。

使用Statement

@Test
public void testSQLInject(){
String userName = null;
String password = null;

//		userName = "yuchen";
//		password = "123456";
userName = "a 'OR PASSWORD =" ;
password = "OR '1'='1";

String sql = "SELECT * FROM users WHERE username = '"+userName+"' and password = '"+password+"'";
Connection conn = null;
Statement state = null;
ResultSet result = null;
try{
conn = JDBCTools.getConnection();
state = conn.createStatement();
result = state.executeQuery(sql);

if(result.next()){
System.out.println("登录成功!");
}else{
System.out.println("用户或密码错误!");
}
}catch (Exception e){
e.printStackTrace();
} finally{
JDBCTools.release(result,state, conn);
}
}
输出结果为 登陆成功!

而使用PreparedStatement:

@Test
public void testSQLInject2(){
String userName = null;
String password = null;

//		userName = "yuchen";
//		password = "123456";
userName = "a 'OR PASSWORD =" ;
password = "OR '1'='1";

String sql = "SELECT * FROM users WHERE username = ? and password = ?";
Connection conn = null;
PreparedStatement preparedState = null;
ResultSet result = null;

try{
conn = JDBCTools.getConnection();
preparedState = conn.prepareStatement(sql);

preparedState.setString(1, userName);
preparedState.setString(2, password);
result = preparedState.executeQuery();

if(result.next()){
System.out.println("登陆成功!");
}else{
System.out.println("用户或密码错误!");
}
}catch(Exception e){
e.printStackTrace();
} finally{
JDBCTools.release(result, preparedState, conn);
}

}
输出结果 用户或密码错误!

6、以面向对象的方式从数据库查询信息

面向对象的方式从数据库获取信息,即从数据库查询操作,得到的信息以类对象的形式展示
利用sql语言的的别名特性,查询的列都配一个别名,该别名与类型的成员变量的名称相同
利用反射,构建类型对象,并利用反射为该类型的对象赋值。

ResultSetMetaData

使用结果集元数据类型对象ResultSetMetaData meta=resultSet.getMetaData(),获得查询操作结果集的一些信息:
列的个数columnCount= meta.getColumnCount();
列名columnLabel=meta.getColumnLabel(columnCount);
然后利用列的别名在ResultSet的结果集中得到相应的对象值
resultSet.getObject(columnCount);

利用反射为对象成员变量赋

Field field = clazz.getDeclaredField(name);name是成员变量的名称

field.setAccessible(true);设置私有变量也可以被访问

field.set(ob, value);ob是对象,value是要设置变量的值

@Test
public void testGetCustomer() throws Exception{
String sql = "SELECT id id, name name, email email, birth birth FROM customers WHERE id=?";
int id = 7;
Customer customer =  (Customer) getObject(Customer.class,sql, id);
System.out.println(customer);

String sql2 = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent "
+ "WHERE flowid =?";
//		String sql2 = "SELECT flowId, type, idCard, examCard, studentName, location, grade FROM examstudent "
//				+ "WHERE flowid =?";
int flowId = 3;
Student stu = (Student) getObject(Student.class, sql2, flowId);
System.out.println(stu);
}

public <T> Object getObject(Class<T> clazz, String sql, Object ... args) throws InstantiationException, IllegalAccessException{

Object ob=null;

Connection conn = null;
PreparedStatement preparedState = null;
ResultSet result = null;

try{
conn = JDBCTools.getConnection();
preparedState = conn.prepareStatement(sql);

for(int i =0; i<args.length; i++){
preparedState.setObject(i+1,args[i]);
}
result = preparedState.executeQuery();

//获得结果集元数据对象
ResultSetMetaData meta = result.getMetaData();
//判读是否有相应的数据,有创建类型对象;无,返回的是null
if(result.next()){
ob=clazz.newInstance();
for(int column=0; column < meta.getColumnCount();column++){
//从结果集元数据中提取出列的别名、和对应的值
String columnLabel = meta.getColumnLabel(column+1);
Object value = result.getObject(columnLabel);

//利用反射为类型对象赋值,此处可以使用BeanUtils工具包(是通用正宗做法)
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(ob, value);
}
}

} catch(Exception e){
e.printStackTrace();
} finally{
JDBCTools.release(result, preparedState, conn);
}
return ob;
}
输出结果;
Customer [id=7, name=yuchen, email=yuchen@cqupt.com, birth=1990-12-12]

Student [flowId=3, type=3, idCard=112233445566, exameCard=1212, studentName=yuchen, location=BeiJing, grade=98]

如果查询不到数据,那么返回的对象应该是null

BeanUtils的使用:

BeanUtils是Apache开源组织的一个java开源工具包,需要在http://commons.apache.org/下载
commons-beanutils-1.9.2.jar 需要与 commons-logging-1.2.jar 包搭配使用

功能是给对象的属性进行赋值,以及获得对象的特定属性值。
对象的属性:是以set 和 get方法定义的,一个类有方法setXXX 和 getXXX方法,那么该对象就有xXX属性
对象的字段:把对象的成员变量称作为字段,而非属性。
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
上述代码有set 和 get 方法,故,该类型对象有idCard属性

是以BeanUtils的SetProperty和GetProperty 可以为对象属性赋值、获得对象属性
@Test
public void GetPropertyTest() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{

Student stu = new Student();
System.out.println(stu);
//结果:Student [flowId=0, type=0, idCard=null, exameCard=null, studentName=null, location=null, grade=0]

//使用BeanUtils工具为对象属性赋值
BeanUtils.setProperty(stu, "idCard", "2233445566");

System.out.println(stu);
//结果:Student [flowId=0, type=0, idCard=2233445566, exameCard=null, studentName=null, location=null, grade=0]

String value = BeanUtils.getProperty(stu, "idCard");
System.out.println(value);
}


而当set 和 get 方法改为这样时:
public String getIdCard2() {
return idCard;
}
public void setIdCard3(String idCard) {
this.idCard = idCard;
}
SetProperty 和 GetProperty就需要改为:
BeanUtils.setProperty(stu, "idCard3", "2233445566");
String value = BeanUtils.getProperty(stu, "idCard2");
一般情况下,属性名和字段名保持一致。

那么,在根据数据库信息创建对象的时候,就可以是以BeanUtils工具了
public <T> Object getObject(Class<T> clazz, String sql, Object ... args) throws InstantiationException, IllegalAccessException{

Object ob=null;

Connection conn = null;
PreparedStatement preparedState = null;
ResultSet result = null;

try{
conn = JDBCTools.getConnection();
preparedState = conn.prepareStatement(sql);

for(int i =0; i<args.length; i++){
preparedState.setObject(i+1,args[i]);
}
result = preparedState.executeQuery();

//获得结果集元数据对象
ResultSetMetaData meta = result.getMetaData();
//判读是否有相应的数据,有创建类型对象;无,返回的是null
if(result.next()){
ob=clazz.newInstance();
for(int column=0; column < meta.getColumnCount();column++){
//从结果集元数据中提取出列的别名、和对应的值
String columnLabel = meta.getColumnLabel(column+1);
Object value = result.getObject(columnLabel);

//利用BeanUtils工具包为对象属性赋值
BeanUtils.setProperty(ob, columnLabel, value);
}
}

} catch(Exception e){
e.printStackTrace();
} finally{
JDBCTools.release(result, preparedState, conn);
}
return ob;
}

7、DAO设计模式

DAO :Data Access Object 数据访问对象,即设计一个类型,用该类型的对象作为专门访问数据库的操作。
DAO设计的好处在于,使该类型专注于数据库的操作,无关业务流程,是数据库操作独立成模块。
设计一个DAO接口类,可以对数据库进行update更新、get获取对象、getForList获取多个对象、getValue获取特定值的操作

public interface DAO {

//更新操作 update :INSERT 、DELETE、 Update
public void update(String sql, Object ... args);

//查询操作一个 get : SELECT
public <T> T get(Class<T> clazz, String sql, Object ... args);

//查询一组 getForList :SELECT
public <T> List<T> getForList(Class<T> clazz, String sql, Object ...args) ;

//返回对象的属性值 getValue,或者某个统计值
public <E> E getValue(String sql, Object ... args);
}
实现类:
public class DAOImpl implements DAO {

@Override
public void update(String sql, Object... args) {

Connection conn = null;
PreparedStatement preparedStatement = null;

try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);

for(int i = 0;i < args.length; i++){
preparedStatement.setObject(i+1, args[i]);
}

preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, preparedStatement, conn);
}
}

@Override
public <T> T get(Class<T> clazz, String sql, Object... args) {

T entity = null;

Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);

for(int i =0; i< args.length; i++){
preparedStatement.setObject(i+1, args[i]);
}

resultSet = preparedStatement.executeQuery();
ResultSetMetaData meta = resultSet.getMetaData();

if(resultSet.next()){
entity = clazz.newInstance();
for(int i=0;i<meta.getColumnCount(); i++){
String label = meta.getColumnLabel(i+1);
Object value = resultSet.getObject(i+1);
//					System.out.println(label+" "+value);
BeanUtils.setProperty(entity, label, value);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(resultSet, preparedStatement, conn);
}
return entity;
}

@Override
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
List<T> list = new ArrayList();
T entity = null;

Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);

for(int i =0; i< args.length; i++){
preparedStatement.setObject(i+1, args[i]);
}

resultSet = preparedStatement.executeQuery();
ResultSetMetaData meta = resultSet.getMetaData();

while(resultSet.next()){
entity = clazz.newInstance();
for(int i=0;i<meta.getColumnCount(); i++){
String label = meta.getColumnLabel(i+1);
Object value = resultSet.getObject(i+1);

BeanUtils.setProperty(entity, label, value);
}
list.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(resultSet, preparedStatement, conn);
}
return list;
}

@Override
public <E> E getValue(String sql, Object... args) {

E value = null;

Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);

for(int i =0; i< args.length; i++){
preparedStatement.setObject(i+1, args[i]);
}

resultSet = preparedStatement.executeQuery();

if(resultSet.next()){
value = (E) resultSet.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(resultSet, preparedStatement, conn);
}
return value;
}

}
那么,在程序编写中,就可以使用DAO的对象进行对数据库的操作,简化了对数据库的操作
创建一个测试类:
public class DAOTest {

@Test
public void testUpdate() {
String sql =null;
sql = "INSERT INTO examstudent VALUES(?,?,?,?,?,?,?)";
DAO dao = new DAOImpl();
dao.update(sql, 123,3, "12345677", "2013452","Musk", "China",99);
}

@Test
public void testGet() {
String sql =null;
sql = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent "
+ "WHERE flowid =?";
DAO dao = new DAOImpl();
Student stu = dao.get(Student.class, sql, 123);
System.out.println(stu);
//输出:Student [flowId=123, type=3, idCard=12345677, exameCard=2013452, studentName=Musk, location=China, grade=99]
}

@Test
public void testGetForList() {
String sql =null;
sql = "SELECT flowid flowId, type, idcard idCard, examcard examCard, studentname studentName, location, grade FROM examstudent "
+ "WHERE studentName = ?";
DAO dao = new DAOImpl();
List<Student> list = dao.getForList(Student.class, sql, "yuchen");
System.out.println(list);
//输出:[Student [flowId=1, type=2, idCard=123456, exameCard=1214, studentName=Yuchen, location=BeiJing, grade=98],
//Student [flowId=2, type=2, idCard=123456, exameCard=1214, studentName=Yuchen, location=BeiJing, grade=98],
//Student [flowId=3, type=3, idCard=112233445566, exameCard=1212, studentName=yuchen, location=BeiJing, grade=98]]

}

@Test
public void testGetValue() {
String sql =null;
sql = "SELECT examCard FROM examstudent "
+ "WHERE flowId = ?";
DAO dao = new DAOImpl();
String card = (String) dao.getValue(sql, 123);
System.out.println(card);
//输出:2013452
}

}

8、DatabaseMetaData数据库元数据

前边学过了结果集的元数据ResultSetMetaData,里边包含了结果集的一些信息,列的数目、列名等。
数据库元数据DatabaseMetaData,是对数据库连接进行描述的对象,里边包含了数据库连接的信息。如:数据库的类型、版本号、当前连接的用户名、数据库的名称、链接地址等:
@Test
public void testDatabaseMetaData() {
Connection conn = null;
ResultSet resultSet =null;
try {
conn = JDBCTools.getConnection();
DatabaseMetaData meta = conn.getMetaData();

System.out.println("数据库类别:"+meta.getDatabaseProductName());
System.out.println("版本号:"+meta.getDatabaseMajorVersion());
System.out.println("用户名:"+meta.getUserName());
System.out.println("数据连接地址:"+meta.getURL());

resultSet = meta.getCatalogs();
System.out.println("数据库:");
while(resultSet.next()){
System.out.println(resultSet.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(resultSet,null, conn);
}
}
输出结果:
数据库类别:MySQL

版本号:5

用户名:root@localhost

数据连接地址:jdbc:mysql://localhost:3306/cqupt

数据库:

bbs

cqupt

information_schema

jeecmsv6

mysql

phpmyadmin

phpmywind_db

test

zblog

9、在插入数据时,获得主键值

向数据库中插入一组数据,自动生成的主键值该如何获取?
通过conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)的重载方法,和preparedStatement.getGeneratedKeys()获取,返回为ResultSet类型。
通过resultSet.getObject(1)就可得到主键值。
@Test
public void testGetKey() {
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet= null;
String sql ="INSERT customers(name,email,birth) VALUES(?,?,?)";
try {
conn= JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, "Musk");
preparedStatement.setString(2, "musk@163.com");
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
preparedStatement.executeUpdate();

//获得主键的结果集,结果集中只有一列resultSet.getObject(1)
resultSet = preparedStatement.getGeneratedKeys();
if(resultSet.next()){
System.out.println(resultSet.getObject(1));
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(resultSet, preparedStatement, conn);
}
}

10、 Blob处理

Blob是一个类型,用于在数据库存储数据块时使用。
mysql的blob有四种类型,可存储的大小不同:TinyBlob(255字节)、Blob(65k)、MediumBlob(16M)、LongBlob(4G)

JDBC对于Blob两种操作,读、写
向数据库写入Blob,使用PreparedStatement的setBlob(Int, InputStream)方法,写入一张图片:
public void testInsertBlob(){
String sql = "INSERT INTO customers(name, email, birth, picture) VALUES(?,?,?,?)";
Connection conn = null;
PreparedStatement preparedStatement = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, "yuchen00");
preparedStatement.setObject(2,"yuchen00@163.com");
preparedStatement.setObject(3, "1990-1-1");

InputStream in = new FileInputStream("卡通.jpg");
preparedStatement.setBlob(4, in);

preparedStatement.executeUpdate();
} catch (ClassNotFoundException | IOException | SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
JDBCTools.release(preparedStatement, conn);
}

}
“卡通.jpg”图片在项目的根目录下。
从数据库读出Blob,使用PreparedStatement的getBlob(int)的方法,读出Blob对象,再利用InputStream向输出流OutputStream写入。
@Test
public void testReadBlob(){
String sql = "SELECT name, email, birth , picture FROM customers WHERE id = ?";
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
conn = JDBCTools.getConnection();
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, 19);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
String name = resultSet.getString(1);
String email = resultSet.getString(2);
Date birth = resultSet.getDate(3);

System.out.println(name+" "+ email+" " + birth+ " ");
Blob blob = resultSet.getBlob(4);
InputStream in = blob.getBinaryStream();
OutputStream out = new FileOutputStream("cartoon.jpg");
//写入到文件的方法
byte[] buffer = new byte[1024];
int length= 0;
while((length = in.read(buffer))!=-1){
out.write(buffer, 0, length);
}
out.close();
in.close();

}
} catch (ClassNotFoundException | IOException | SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
JDBCTools.release(resultSet,preparedStatement, conn);
}
}

11、事务处理

事务操作

所谓事务,是指一组逻辑操作单元,使数据从一种状态变为另种状态。事务由一些列的操作组成,操作过程中保持:原子性、一致性、隔离性和持久性。
mysql数据库中的表有多种类型:MyISAM(不支持事务,适合查询量大的数据表)、InnoDB(支持事务,适合插入、更新量大的数据表),使用事务时需注意表是否为InnoDB类型。
JDBC中,事务的就是通过Connection连接完成的,一个事务操作只能与一个Connection有关,通过Connection完成。
三个步骤:
1、禁止自动提交,Connection类中的setAutoCommit(bool )完成
2、提交,在一组操作的最后,由commit()完成
3、回滚,事物中出现异常,使用回滚rollback()完成回到原始状态。
@Test
public void testTransAction() {
Connection conn = null;

try{
conn = JDBCTools.getConnection();
//禁止自动提交,设置自动提交为false
conn.setAutoCommit(false);

String sql= "UPDATE users SET balance = balance+500 WHERE username = 'yuchen' ";
update(conn, sql);
int a =10/0;

sql= "UPDATE users SET balance = balance-500 WHERE username = 'jack'";
update(conn,sql);

//提交
conn.commit();
} catch (Exception e){
e.printStackTrace();
try {
//回滚操作
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}

} finally{
JDBCTools.release(null,null, conn);
}

}

public void update(Connection conn, String sql, Object ... args){
PreparedStatement preparedStatement = null;
try {
preparedStatement = conn.prepareStatement(sql);

for(int i = 0; i < args.length; i++ ){
preparedStatement.setObject(i+1, args[i]);
}

preparedStatement.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, preparedStatement, null);
}

}

事务的隔离级别

事务就像线程一样,会存在多个事务之间的互相影响问题。两个事务之间会产生的影响如下:
脏读: 事务1读取了事务2的还未提交的数据,也就是事务2的commit操作还未完成,有可能回滚,那么事务1读到的数据就是数据库不存在的。
不可重复读:事务1读取了数据,事务2紧接着对数据进行了处理,那么数据1再次读取数据,就会和上次不一样。
幻读:事务1读取了数据,事务2紧接着增加了几个字段,事务1再次读,就会读出和上次不一样的数据字段。
其中,脏读是绝不容忍出现的,不可重复读和幻读都是可以接受的。

为了应对上述情况,mysql和oracle都给出了隔离级别,其中:
oracle只有两种级别:READ COMMITTED(读已提交) 和 SERIALIZABLE(串行化),读已提交就是等待别的事务提交之后才可以读,处理过程中但未提交是不可以被别的事务读的。串行化是最严格的,隔离级别最高,但是性能十分低下。
mysql有四种隔离级别:READ UNCOMMITTED(读未提交)、 READ COMMITTED(读已提交),REPETABLE READ(可重复读)和SERIALIZBLE(串行化)。
读未提交是隔离级别最低的,会出现脏读、不可重复读、幻读的情况,一般不适用。、
读已提交,同oracle,是最经常使用的,避免了脏读,会出现不可重复读和幻读,但是可以接受。
可重复读,事务处理过程中,禁止别的事务对该字段进行更新,避免了脏读和不可重复读。

最经常使用的就是READ COMMITTED,mysql默认隔离级别是READ REPETABLE

通过Connection的setTransactionIsolation(int)可以设置隔离级别
参数是Connection的静态变量:
Connection.Transaction_READ_UNCOMMITED 读未提交
Connection.Transaction_READ_COMMITED 读已提交
Connection.Transaction_READ_REPETABLE 可重复读
Connection.Transaction_SERIALIZABLE 串行化
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

查询默认隔离级别(sql语句):SELECT @@tx_isolation
设置mysql的默认隔离级别(sql语句):
设置当前连接set transaction isolation level READ COMMITTED

设置数据库系统的全局事务隔离级别:set global transcation isolation level READ COMMITTED

12、批量处理

当需要性插入大量数据时,是比较耗费时间的。利用批处理方法,一次性提交sql,执行多次插入,可以节省时间。
主要使用PreparedStatement类中的addBatch()方法、executeBatch() 和 clearBatch()方法。
通过积攒单一操作一定数目之后,一次性执行,然后清空积攒的操作。
@Test
public void testBatch(){
Connection conn = null;
PreparedStatement preparedStatement = null;

try{
String sql = "INSERT INTO customers VALUES(?,?,?,?)";
conn = JDBCTools.getConnection();
//开始事务
conn.setAutoCommit(false);
preparedStatement = conn.prepareStatement(sql);

long begin = System.currentTimeMillis();
Date date = new Date(new java.util.Date().getTime());
for(int i=0; i<100000; i++){
preparedStatement.setInt(1, i+1);
preparedStatement.setString(2, "yuchen"+(i+1));
preparedStatement.setString(3, "yuchen"+(i+1)+"@163.com");
preparedStatement.setDate(4, date);

//积攒
preparedStatement.addBatch();

//到达数目,处理积攒的batch
if((i+1)%300 == 0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
}
//处理未完成的batch
preparedStatement.executeBatch();
preparedStatement.clearBatch();

long end= System.currentTimeMillis();
System.out.println(end - begin);

//提交事务
conn.commit();

} catch(Exception e) {
try {
//回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally{
JDBCTools.release(preparedStatement, conn);
}
}

13、数据库连接池

前边的数据库连接都是直接通过DriverManager的getConnection方法获取的,需要向数据库服务器传输用户名与密码来的得到连接。
Connection connection = DriverManager.getConnection(url, user, password);
为什么使用数据库连接池呢?主要有以下几点
1、频繁的连接与断开会消耗服务器资源,特别是连接数量比较多时,会造成服务器的崩溃。
2、数据库连接如果忘记断开的话,会一直占用着服务器资源
3、如果不限制连接数目的上限的话,也会导致服务器崩溃
基于对数据库服务器的保护以及资源的高效利用,我们在开发过程中,通常都是通过数据库连接池完成与数据库的连接,而不是直接使用DriverManager的方法代码。
数据库连接池的思想是建立一个缓冲池,用以维护数据库连接,预先存放一定数量的连接,客户端获得连接在池子里得到,用完之后再放回池子中。
允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
JDBC中,Java为数据库连接池定义了一个接口javax.sql.DateSource,连接池工具通过实现这个接口来实现连接池功能,应用程序通过调用DateSource的方法来使用数据库连接池工具。
通常使用的Java数据的数据库连接池工具有两个:DBCP 和 C3P0

DBCP

dbcp是Apache下的一个数据库连接池工具,依赖另一个开源系统Commons-pool,Tomcat就是使用的DBCP
需要两个jar包:commons-pool2-2.4.2.jar,commons-dbcp2-2.1.1.jar
BasicDataSource是实现DataSource接口的实现类,下边看如何使用BasicDataSource配置连接池,并分配连接
@Test
public void testDBPC() throws SQLException{
BasicDataSource  dataSource = null;
Connection connection = null;
//创建JDBC数据源实例
dataSource =  new BasicDataSource();

//配置必须的属性
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql://localhost:3306/cqupt");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");

//配置可选属性
//1.设置初始连接数目
dataSource.setInitialSize(10);
//2.设置数据库连接池中,同一时刻可以向数据库申请的最多的连接数
dataSource.setMaxTotal(50);
//3.指定最少空闲连接数,指在数据连接池中保持空闲的连接数目
dataSource.setMinIdle(5);
//4.等待数据库连接池分配连接的最长时间,超时抛出异常,单位毫秒
dataSource.setMaxWaitMillis(5000);

//分配连接
connection = dataSource.getConnection();
System.out.println(connection);
}
上述可以看出bdcp的实现,实际在开发中只通过DataSource接口进行配置连接池,和分配连接,是通过BasicDataSourceFactory和配置文件配置的。
@Test
public void testDBCPWithDataSourceFactory() throws Exception{
DataSource dataSource = null;
//加载配置文件到Properties类对象
InputStream inStream = ConnectionPoolTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties properties = new Properties();
properties.load(inStream);
//利用BasicDataSourceFactory创建数据源
dataSource = BasicDataSourceFactory.createDataSource(properties);

Connection connection = dataSource.getConnection();
System.out.println(connection);
}
配置文件里的配置,键名是确定的,同javaBean,实际上是通过BasicDataSource的set方法设置的
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/cqupt

initialSize=10
maxTotal=50
minIdle=5
maxWaitMillis=5000

C3P0

是hibernate指定的数据库连接数工具,性能也很不错。
导入两个jar包:c3p0-0.9.5.1.jar

mchange-commons-java-0.2.10.jar
c3p0中实现DataSource的类是ComboPooledDataSource,可以看如何通过该类配置连接池
@Test
public void testC3p0() throws PropertyVetoException, SQLException{
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/cqupt" );
cpds.setUser("root");
cpds.setPassword("root");

Connection connection = cpds.getConnection();
System.out.println(connection);
}
同dbcp一样,在实际开发中也是使用配置文件的方式进行,系统默认的配置文件名为c2p0-config.xml,直接在创建ComboPooledDataSource时指定配置名称就可以(不是文件名)
@Test
public void testC3p0() throws PropertyVetoException, SQLException{
DataSource cpds = new ComboPooledDataSource("helloc3p0");

Connection connection = cpds.getConnection();
System.out.println(connection);
}
c3p0-config.xml
<c3p0-config>

<!--指定配置的名称,在程序中使用  -->
<named-config name="helloc3p0">
<!-- 指定数据库连接的基本属性 -->
<property name="user">root</property>
<property name="password">root</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/cqupt</property>

<!-- 配置一些可选属性 -->
<!--连接不足时,一次性向服务器申请多少个连接  -->
<property name="acquireIncrement">50</property>
<!-- 初始化连接数 -->
<property name="initialPoolSize">100</property>
<!-- 最小的连接数 -->
<property name="minPoolSize">50</property>
<!-- 最大的连接数 -->
<property name="maxPoolSize">1000</property>

<!-- 数据库连接池可以维护的Statement的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的Statement的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>


那么JDBCTools中的获取连接的方法getConnection在实际开发中就是使用:
private static DataSource dataSource;
static {
dataSource = new ComboPooledDataSource("helloc3p0");
}
public static Connection getConnection() throws SQLException{
Connection connection = dataSource.getConnection();
return connection;
}
释放链接的方法不变,但是实际上的意义不同,数据连接池的Connection对象执行close操作,并不是真的关闭了连接,而是将连接归还到数据库连接池中

public static void release(ResultSet resultSet,Statement statement, Connection conn){
if(resultSet!=null){
try{
resultSet.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(statement != null){
try{
statement.close();
}catch (Exception e){
e.printStackTrace();
}
}

if(conn != null){
try{
//数据连接池的Connection对象执行close操作,并不是真的关闭了连接,
//而是将连接归还到数据库连接池中
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

14、DBUtils的使用

DBUtils是Apache的一款java开发工具,目的就是封装我们前边学习的一些底层操作,完成简单的调用就可以实现数据库的增删改查操作。
实际上就可以吧DBUtils看作是DAO编程的思想产物。

使用DBUtils,从类型QueryRunner入手,其中含有update方法可以实现增删改操作,query方法可以实现查询操作
使用update方法:
/**
* 使用QueryRunner的update(conn, sql, args)实现DELETE、INSERT、UPDATE操作
*/
@Test
public void testQueryRunnerUpdate(){
Connection conn= null;
try {
conn = JDBCTools.getConnection();
String sql = "INSERT INTO customers VALUES(?,?,?,?)";
QueryRunner queryRunner = new QueryRunner();
queryRunner.update(conn, sql, 1,"jack","jack@163.com","1991-12-12");
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}
使用query方法,实际上有多个重载的方法,常用的是query(conn, sql, rst, args)
通过传递不同的ResultSetHandler接口的实现类给rst参数,可以实现不同的查询效果:查询一行返回一个对象、查询多行返回对象链表、查询特定值
将自己的实现类传递:
class MyResultSetHandler implements ResultSetHandler{

@Override
public Object handle(ResultSet rs) throws SQLException {
Customer customer = null;

if(rs.next()){
customer = new Customer(rs.getInt(1),rs.getString(2),rs.getString(3), rs.getDate(4));
}
return customer;
}

}

/**
* 调用QueryRunner的query(conn, sql, rst, args),将实现ResultSetHandler接口的自定义类传递给rst参数
* 实际上query方法返回的值,就是返回的接口ResultSetHandler的handler方法返回值
*/
@Test
public void testQueryRunnerQueryWithMyResultSetHandler(){
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "SELECT id, name , email, birth FROM customers WHERE id =?";

QueryRunner queryRunner = new QueryRunner();
Customer customer = (Customer) queryRunner.query(conn, sql, new MyResultSetHandler(), 1);
System.out.println(customer);
//输出:Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12]
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}
传递DBUtils系统自带的实现类(五种):
/**
* dbUtils通过QueryRunner的query(conn, sql, rst, args)来实现查询
* 提供了五种查询返回类型,通过传递不同的ResultSetHandler的实现类给rst实现
* 1.BeanHandler是输出查询到的第一个类型对象,是根据sql字段别名进行set设置的;
* 2.BeanListHandler输出查询到的一组类型对象;
* 3.MapHandler是输出一个map对象,键:字段名(不是别名),值是字段对应的值;
* 4.MapListHandler是输出一个list对象,每个元素是个map对象,多组数据的键值;
* 5.ScalarHandler输出的是查询到的第一行第一列的值,可以特定查询某个量
*/
@Test
public void testQueryRunnerQuery(){
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "SELECT id, name customerName, email, birth FROM customers";

QueryRunner queryRunner = new QueryRunner();

//1.BeanHandler是输出查询到的第一个类型对象,是根据sql字段别名进行set设置的
//			Customer customer = (Customer) queryRunner.query(conn, sql, new BeanHandler(Customer.class));对泛型不熟悉时的使用
Customer customer = queryRunner.query(conn, sql, new BeanHandler<>(Customer.class))
System.out.println(customer);
//输出:Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12]

//2.BeanListHandler输出查询到的一组类型对象
//			List<Customer> customers = (List<Customer>) queryRunner.query(conn, sql, new BeanListHandler(Customer.class));
List<Customer> customers =  queryRunner.query(conn, sql, new BeanListHandler<>(Customer.class));
System.out.println(customers);
//输出:[Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12], Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01]]

//3.MapHandler是输出一个map对象,键:字段名(不是别名),值是字段对应的值
Map<String, Object> map = queryRunner.query(conn, sql, new MapHandler());
System.out.println(map);
//输出:{id=1, customerName=jack, email=jack@163.com, birth=1991-12-12}

//4.MapListHandler是输出一个list对象,每个元素是个map对象,多组数据的键值
//			List<Map<String, Object> > list = (List<Map<String, Object>>) queryRunner.query(conn, sql, new MapListHandler());
List<Map<String, Object> > list = queryRunner.query(conn, sql, new MapListHandler());
System.out.println(list);
//输出结果:[{id=1, customerName=jack, email=jack@163.com, birth=1991-12-12}, {id=2, customerName=yuchen, email=yuchen@163.com, birth=1991-01-01}]

//5.ScalarHandler输出的是查询到的第一行第一列的值,可以特定查询某个量
sql = "SELECT email From customers WHERE name = ?";
//			String obj = (String) queryRunner.query(conn, sql, new ScalarHandler(),"yuchen");
String obj = queryRunner.query(conn, sql, new ScalarHandler<String>(),"yuchen");
System.out.println(obj);
//输出:yuchen@163.com

} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}

}

15、利用DBUtils编写自己的DAO

编写自己的DAO,使用泛型,方便在程序开发中使用,主要采用DBUtils工具中的QueryRunner类,实现增删改查。
定义接口DAO--->实现类DAOImpl--->适用于特定表(类)的CustomerDAO--->程序开发中使用CustomerDAO对象操作数据库
定义DAO接口
public interface DAO<T> {

/**
* 完成DELETE、INSERT、UPDATE操作
* @param conn 连接
* @param sql sql语句
* @param args 占位符参数
* @throws SQLException
*/
public void update(Connection conn, String sql, Object ... args ) throws SQLException;

/**
* 获取一行数据对应的一个对象
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public T get(Connection conn, String sql, Object ... args) throws SQLException;

/**
* 获取多行数据的List对象
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public List<T> getForList(Connection conn, String sql, Object ... args) throws SQLException;

/**
* 获取特定值(多行多列数据的第一行第一列数据)
* @param conn
* @param sql
* @param args
* @return
* @throws SQLException
*/
public <E> E getForValue(Connection conn, String sql, Object ... args) throws SQLException;

/**
* 批处理数据,使适用于DELETE,INSERT、UPDATE
* @param conn
* @param sql
* @param args
* @throws SQLException
*/
public void batch(Connection conn, String sql, Object[] ... args) throws SQLException;
}
实现类DAOImpl:注意实现类中属性的定义,以及获取父类的泛型初始化Class<T> type
public class DAOImpl<T> implements DAO<T> {

private QueryRunner queryRunner = null;
private Class<T> type;

public DAOImpl()  {
queryRunner = new QueryRunner();
//获取父类的泛型,赋值给type
Type superClass = this.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType)superClass;
Type [] types = parameterizedType.getActualTypeArguments();
type = (Class<T>)types[0];
}

@Override
public void update(Connection conn, String sql, Object... args) throws SQLException {
queryRunner.update(conn, sql, args);
}

@Override
public T get(Connection conn, String sql, Object... args) throws SQLException {
T t =  queryRunner.query(conn, sql, new BeanHandler<T>(type), args);
return t;
}

@Override
public List<T> getForList(Connection conn, String sql, Object... args) throws SQLException {
List<T> list = new ArrayList<T>();
list = queryRunner.query(conn, sql, new BeanListHandler<>(type), args);
return list;
}

@Override
public <E> E getForValue(Connection conn, String sql, Object... args) throws SQLException {
E e = queryRunner.query(conn, sql, new ScalarHandler<E>(), args);
return e;
}

@Override
public void batch(Connection conn, String sql, Object[]... args) throws SQLException  {
queryRunner.batch(conn, sql, args);
}

}
特定类的DAO:CustomerDAO
public class CustomerDAO extends DAOImpl <Customer> {

}
使用CustomerDAO
public class CustomerDAOTest {

@Test
public void testBatch(){
CustomerDAO customerDAO = new CustomerDAO();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "INSERT INTO customers VALUES(?,?,?,?)";
Object[] arg1 = new Object[]{4,"jack4","jack4@163.com","1990-1-1"} ;
Object[] arg2 = new Object[]{5,"jack5","jack5@163.com","1990-1-1"} ;
Object[] arg3 = new Object[]{6,"jack6","jack6@163.com","1990-1-1"} ;
customerDAO.batch(conn, sql, arg1,arg2,arg3);
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}

@Test
public void testUpdate() {
CustomerDAO customerDAO = new CustomerDAO();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "INSERT INTO customers VALUES(?,?,?,?)";
customerDAO.update(conn, sql, 3,"shenghua","shenghua@163.com","1990-11-11");
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}

@Test
public void testGet() {
CustomerDAO customerDAO = new CustomerDAO();
Connection conn = null;
Customer customer = null;
try {
conn = JDBCTools.getConnection();
String sql = "SElECT id , name customerName, email, birth FROM customers WHERE name=?";
customer = customerDAO.get(conn, sql, "yuchen");
System.out.println(customer);
//Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01]
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}

}

@Test
public void testGetForList() {
CustomerDAO customerDAO = new CustomerDAO();
Connection conn = null;
List<Customer> list = new ArrayList<Customer>();
try {
conn = JDBCTools.getConnection();
String sql = "SElECT id , name customerName, email, birth FROM customers ";
list = customerDAO.getForList(conn, sql);
System.out.println(list);
//[Customer [id=1, name=jack, email=jack@163.com, birth=1991-12-12],
// Customer [id=2, name=yuchen, email=yuchen@163.com, birth=1991-01-01]]
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}

@Test
public void testGetValue() {
CustomerDAO customerDAO = new CustomerDAO();
Connection conn = null;
String obj = null;
try {
conn = JDBCTools.getConnection();
String sql = "SElECT  email FROM customers WHERE name =? ";
obj = customerDAO.getForValue(conn, sql, "yuchen");
System.out.println(obj);//yuchen@163.com
} catch (SQLException e) {
e.printStackTrace();
} finally{
JDBCTools.release(null, null, conn);
}
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: