JDBC

本文深入解析Java数据库连接(JDBC)技术,涵盖概念、基本操作步骤、CRUD实现、元数据处理、批处理、事务管理及单例设计模式等核心内容。同时,探讨了PreparedStatement对象如何提升安全性与执行效率,以及JDBC的高级封装技巧。

1.JDBC的概念及步骤

java数据库连接技术:通过java语言来操作数据库(CRUD)

①发展过程

ODBC:开放数据库互联技术,微软公司用C语言写的,JAVA语言出现
JDBC-ODBC:桥接技术,既能操作java又能连接C语言,但是效率低,java已经成熟
JDBC:纯java编写

②连接约定

约定:接口,现在接口是由各个数据库厂商给java提供,jar包
连接:IP port端口号(3306) username password db

package edu.hm.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCStep {
	
	public static void main(String[] args) {
		
		step();
	}
	/*
	 * JDBC步骤
	 * 1、导入相关数据库的jar包(驱动程序)
	 * 2、注册驱动
	 * 3、创建连接
	 * 4、编写业务SQL
	 * 5、创建Statement传送SQL对象 
	 * 6、执行SQL并返回相应的处理结果
	 * 7、对结果进行处理
	 * 
	 * 注意:jdbc涉及到的类要么统一使用java.sql包下的对象
	 * 或者com.mysql下的,避免强制类型转换
	 */
	
	// 设置URL时,要注意代码的编写,否则会出现异常java.sql.SQLException: No suitable driver found for jdbc://mysql://localhost:3306/c1809
	private static final String URL = "jdbc:mysql://localhost:3306/c1809"; // 连接本地数据库的端口号
	private static final String USERNAME = "root";  // 以root身份登录
	private static final String PASSWORD = "123456";  // 登录密码
	
	
	
	public static void step() {
		Connection conn = null;
		Statement stmt = null;
		String sql = "";
		ResultSet rs = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");  // 注册驱动:DriverManager.registerDriver(new Driver());,如果要连接数据库一定要有该行代码
			conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); // 创建连接
			stmt = conn.createStatement(); // 创建Statement传送SQL对象(执行SQL语句,返回执行结果)
			
			// 编写业务SQL,执行SQL并返回相应结果
			sql = "select sid,name,age from students";
			// 修改 返回 int
			rs = stmt.executeQuery(sql);
			// 1、判断当前行的后面是否还有数据  2、true 则把rs的cursor指向下一行
			while(rs.next()) {
				
				int sid = rs.getInt("sid");
				String name = rs.getString(2);
				int age = rs.getInt("age");
				
				
				System.out.println(sid+"\t"+name+"\t"+age);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
				// 资源关闭
				try {
					if(null != rs) {
						
					rs.close();
					
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}finally {
					try {
						if(null != stmt) {
							stmt.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}finally {
						try {
							if(null != conn) {
								conn.close();
							}
						} catch(SQLException e) {
							e.printStackTrace();
						}
					}
				}
		}
	}
}

运行结果:

1	irving	22
2	kobe	32
3	paul	28
4	durant	33
5	curry	29
6	james	39
7	hardon	29
8	libiyu	21
9	xiaoyu	20
11	byrant	21

需要注意的是: 导入的包需要一致。也就是说在创建连接的时候有两个包,一个是java提供的,另外一个是各大数据库厂商提供的,所以在这里导包的时候需要统一导入,不能一个是java的,而另一个是其他数据库厂商的,否则在传送SQL对象时就需要强制类型转换。

2.JDBC实现CRUD操作

package edu.hm.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import edu.hm.vo.Students;


public class JDBCStep1_2 {
	
	private static final String URL = "jdbc:mysql://localhost:3306/c1809";
	private static final String USER = "root";
	private static final String PASSWORD = "123456";
	
	public static void main(String[] args) {
		
		int i = updateStu();
		System.out.println(i == 1 ? "success" : "failure");
		
	}
	
	public static int updateStu() {
	
		Connection conn = null;
		Statement stmt = null;
		String sql = "";
		int i = 0;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			
			conn = DriverManager.getConnection(URL, USER, PASSWORD);
			
			stmt = conn.createStatement();
			
			sql = "update student set sex = '女' where sid = 19";
			i = stmt.executeUpdate(sql);
			
		} catch (Exception e) {
			
			e.printStackTrace();
		}finally {
				try {
					if(null != stmt) {
						
					stmt.close();
					
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}finally {
					try {
						if(null != conn) {
							conn.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
		}	
		return i;
	}
}

运行结果:

success

运行结束后,对比数据库修改前后的内容:
在这里插入图片描述
另外insert、delete原理类似

3.ResultSet元数据

元数据:描述数据的数据
而ReslutSet元数据就是对数据库以及对应表的字段进行操作的

package edu.hm.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import edu.hm.vo.Student;

public class JDBCMetadata {
	
	
	private static final String URL = "jdbc:mysql://localhost:3306/c1809";
	private static final String USER = "root";
	private static final String PASSWORD = "123456";
	
	public static void main(String[] args) {
		
		List<Student> list = getAllStu();
		
	}
	
	public static List<Student> getAllStu() {
		
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		List<Student> list = null;
		String sql = "";
		
		try {
			
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection(URL, USER, PASSWORD);
			stmt = conn.createStatement();
			sql = "select sid,name familyname,age from students";
			rs = stmt.executeQuery(sql);
			
			// 获取rs对象的元数据对象
			ResultSetMetaData rsmd = rs.getMetaData();
			
			String cn = rsmd.getCatalogName(1);   // 数据库名
			String ccn = rsmd.getColumnClassName(2); // 对应字段类型在java中的包装类名
			int cc = rsmd.getColumnCount();  // 返回列数
			int cds = rsmd.getColumnDisplaySize(2); // 返回指定列的类型在数据库中定义的长度
			String cl = rsmd.getColumnLabel(2);  // 返回指定列修改后的字段名
			String con = rsmd.getColumnName(2);   // 返回指定列原始表中的字段名
			int ct = rsmd.getColumnType(1);  // 返回sql类型,具体整数对应类型请查看java.sql.Types
			String ctn = rsmd.getColumnTypeName(1);  // 返回指定列的数据类型
			int gp = rsmd.getPrecision(2);
			String tn = rsmd.getTableName(1);  // 获取表名
			
			System.out.println(cn+"\n"+ccn+"\n"+cc+"\n"+cds+"\n"
								+cl+"\n"+con+"\n"+ct+"\n"+ctn+"\n"+gp+"\n"+tn);
		} catch (Exception e) {
			e.printStackTrace();
		// 关闭资源
		}finally {
			try {
				if(null != rs) {
					rs.close();
				}
				
			} catch (SQLException e) {
				e.printStackTrace();
			} finally {
				try {
					if(null != stmt) {
						stmt.close();
					}
					
				} catch (SQLException e) {
					e.printStackTrace();
				} finally {
					try {
						if(null != conn) {
							conn.close();
						}
						
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
				
			}
		}
		return list;	
	}
}

运行结果:

c1809
java.lang.String
3
20
familyname
name
4
INTEGER
20
students

4.SQL注入

客户可以通过参数传递的方式来改写原始的业务SQL,Statement对象组合SQL语句时只会把参数放入到SQL字符串中组合一个新的SQL字符串,原始参数中的SQL代码就会被看做是业务SQL中的一部分,执行时把原本为参数的值作为了SQL语句的一部分。

package edu.hm.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import edu.hm.utils.DBUtil;

public class JDBCPstmt {

	// 主方法
	public static void main(String[] args) {
		
		int id = updateStu("kerr",8);
		
		System.out.println(id > 0 ? "success" : "failure");
		
	}
	
	// 该函数的两个形参就是SQL注入
	public static int updateStu(String name,int sid) {
		
		Connection conn = DBUtil.getConn();
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		int id = 0;
		try {
			// SQL注入
//			String sql = "select * from students where name = '"+name+"' and password = '"+password+"'";
			// 防SQL注入
			String sql = "update students set name = ? where "
					+ "sid = ?";
			/*
			 *  对每一条sql会做一个执行计划(预编译)并且存入到pstmt对象中
			 * 下次传入同样sql时,就直接从内存中取出已经编译好的执行计划直接赋值执行即可 
			 */
			
			pstmt = conn.prepareStatement(sql);
			
			// 给参数赋值:把传入的参数直接当做值处理,而不会重新组合SQL,这样就能防止sql注入
			pstmt.setString(1, name);
			pstmt.setInt(2, sid);	
			id = pstmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			// 资源关闭
			DBUtil.close(conn, null, pstmt, rs);
		}
		return id;
	}
}

5.PrepareStatement对象

①防SQL注入,提升了安全性

pstmt对象防SQL注入:

// 把特殊字符转义成普通字符
select sid from students where name='张三' and password='23456\' or \'1=1'
②预编译

预编译:v4.1前不支持,v5.x支持,但是默认是关闭的
开启预编译:
在URL地址后加上: c1809?useServerPrepStmts=true&cachePrepStmts=true"
每一条SQL在执行前都会做相应的执行计划

③可实现动态SQL:通过改变参数的值可以多次执行相同的SQL而无需重新编译
④能够提升SQL的执行效率

6.MySQL的日志

查看日志信息:–>show variables like ‘log%’;
配置:
my.ini文件中最后配置日志路径重启服务即可
log=D:\mysqlslowlog\mysql_log.log
log=D:\mysqlslowlog\mysql_error.log
log:基本信息日志
log_error:错误日志

7.JDBC中的MySQL事务处理

package edu.hm.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Savepoint;
import java.sql.Statement;

import edu.hm.utils.DBUtil;

public class JDBCTransaction {
	
	public static void main(String[] args) {
		
		testDTL();
	}
	
	public static void testDTL() {
		
		Connection conn = DBUtil.getConn();
		PreparedStatement pstmt = null;
		Statement stmt = null;
		ResultSet rs = null;
		Savepoint sp = null;
		try {

			stmt = conn.createStatement();
			// 关闭SQL自动提交事务模式
			conn.setAutoCommit(false);
			String sql1 = "update account set balance = balance - 100 where aid = 2";
			// 设置还原点
//			sp = conn.setSavepoint("p1");
			String sql2 = "update account set balance = balance + 100 where aid = 3";
			
			int a = stmt.executeUpdate(sql1);
//			int i = 10/0;
			int b = stmt.executeUpdate(sql2);
			
			System.out.println((a>0 && b>0) ? "success" : "failure");
			
			
			
//			 conn.rollback(sp); 
			 
			 conn.commit();
			 
			 String sql3 = "update account set balance = balance + 10000 where aid = 1";
			 int c = stmt.executeUpdate(sql3);
			 System.out.println(c>0?"successful":"failure");
			 conn.commit();
			 
		} catch (Exception e) {
			e.printStackTrace();
			
		}finally {
			DBUtil.close(conn, null, pstmt, rs);
		}
		
	}

}

需要注意的是
①如果在一个事务中执行了ddl,则当前事务会立即提交;如果一个事务在未提交状态下直接退出,事务不会提交
②当一个多条sql语句组成一个事务提交后,再开启另外一个事务的情况下,需要再一次调用conn.commit方法,否则该事务不会提交。

8.JDBC的简单封装

封装:给你什么,你进行相应的处理后你给我什么
JDBC操作中
①Connection对象只需要创建一次,CRUD操作可以基于同一个conn对象进行相应的操作,就可以把创建Connection对象进行封装,对外提供一个方法返回Connection对象
函数/方法: public static Connection getConn(){}
②JDBC创建的相应对象可以让一个方法进行同一关闭
函数/方法:public static void close(){}

package edu.hm.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBUtil {
	
	private static final String URL = "jdbc:mysql://localhost:3306/c1809";
	private static final String USER = "root";
	private static final String PASSWORD = "123456";
	private static Connection conn = null;
	
	static {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Gets the JDBC-MySQL connection CONNECTION object
	 * @return Connection
	 */
	public static final Connection getConn() {
		try {
			// 当conn对象不等于null且对象未关闭时,共用同一个对象
			if(null != conn && !conn.isClosed()) { // 这里 null != conn 与 !conn.isClosed()不能调换位置,虽然在语法结构上是合理的;但是上面
				return conn;
			}else {
				conn = DriverManager.getConnection(URL, USER, PASSWORD);
			}
				
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	
	}	
	/**
	 * Close the resource of JDBC
	 * @param conn
	 * @param stmt
	 * @param pstmt
	 * @param rs
	 */
	public static final void close(Connection conn,Statement stmt
							,PreparedStatement pstmt,ResultSet rs) {
		
			try {
				if(null != rs) {
					rs.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			} finally {
				try {
					if(null != pstmt) {
						pstmt.close();
					}
				} catch (SQLException e) {
					e.printStackTrace();
				} finally {
					try {
						if(null != stmt) {
							stmt.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					} finally {
						try {
							if(null != conn) {
								conn.close();
							}
						} catch (SQLException e) {
							e.printStackTrace();
						}
					}
				}
			}
	}
}

9.单例设计模式

多次调用同一个方法对外产生同一个对象

package edu.hm.pattern;

public class Student {
	
	private String name;
	private int age;
	private static Student stu = null;
	
	// 私有的构造器,使其对外不能创建对象
	private Student () {
		
	}
	
	/*
	 * 单例模式
	 * 1.私有的构造器
	 * 2.对外提供一个静态返回当前对象实例的方法
	 * 3.私有的静态全局对象属性
	 */
	public static Student getInstance() {
		if(null != stu) {
			return stu;			
		}
		stu = new Student();
		return stu;
		
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
}

测试类:

package edu.hm.pattern;

public class Test {
	
	public static void main(String[] args) {
		
		Student stu = Student.getInstance();
		Student stu1 = Student.getInstance();
		Student stu2 = Student.getInstance();
		Student stu3 = Student.getInstance();
		System.out.println(stu);
		System.out.println(stu1);
		System.out.println(stu2);
		System.out.println(stu3);
		
	}
}

运行结果:

edu.hm.pattern.Student@15db9742
edu.hm.pattern.Student@15db9742
edu.hm.pattern.Student@15db9742
edu.hm.pattern.Student@15db9742

由运行结果可以得出的结论:单例模式下创建的对象都是同一个。

还有一个需要介绍的是:工厂模式

10.JDBC的批处理

批处理:一次性处理多条SQL

package edu.hm.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

import edu.hm.utils.DBUtil;

public class JDBCBatch {
	
	/*
	 * 一次性插入三条记录
	 */
	@Test
	public void test1() {
		
		Connection conn = DBUtil.getConn();
		PreparedStatement pstmt = null;
		try {
			
			String sql = "insert into address(num,bunum) values(?,?)";
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setString(1, "10");
			pstmt.setString(2, "1002");
			
			// 将上面的sql作为一个批处理
			pstmt.addBatch();
			
			// 执行这两个SQL语句,代码执行后,数据库插入了后面这个数据。这可以理解为是后面把前面这一条覆盖掉了
			pstmt.setString(1, "11");
			pstmt.setString(2, "1003");
			
			// 将上面的sql作为一个批处理
			pstmt.addBatch();
			String sql1 = "insert into address(num,bunum) values('13','1004')";
			
			// 新插入一条sql语句
			pstmt.addBatch(sql1);
			
			int[] i = pstmt.executeBatch();
			
			for(int ii : i) {
				System.out.println(ii);
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			DBUtil.close(conn, null, pstmt, null);
		}
	}
}

运行结果:

1
1
1

11.JDBC的properties配置

①理解properties文件
class目录:存放编译文件的根目录

package edu.hm.config;

import java.io.IOException;
import java.util.Properties;

public class Config {
	
	/*
	  * 把DB.properties文件加载到jvm中
	  * 从文件获取对应的key,从而得到key对应的值
	 */
	
	// Properties对象能够加载和管理属性文件
	private static Properties PROP = new Properties();
	
	static {
		try {
			
			// 加载src根路径下的文件
			
			
//			PROP.load(Config.class.getClassLoader()
//							.getResourceAsStream("DB.properties"));
			 
			// 包路径下:/src/edu/hm/config/db1.properties
			PROP.load(Config.class.getClassLoader()
							.getResourceAsStream("edu/hm/config/db.properties"));
			
			// 第二种获取ClassLoader类加载器的方法
//			Thread.currentThread().getContextClassLoader().getResourceAsStream("edu/hm/config/db.properties");
			
			// 当前路径下的配置文件
//			PROP.load(Config.class.getResourceAsStream("db.preporties"));
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	public static final Integer FLAG = Integer.parseInt(PROP.getProperty("FLAG"));
	public static final String MYSQL_DRIVERCLASS_PATH = PROP.getProperty("MYSQL_DRIVERCLASS_PATH");
	public static final String MYSQL_CONN_URL = PROP.getProperty("MYSQL_CONN_URL");
	public static final String MYSQL_USERNAME = PROP.getProperty("MYSQL_USERNAME");
	public static final String MYSQL_PASSWORD = PROP.getProperty("MYSQL_PASSWORD");
	
}

配置文件:

KEY = 123
NAME = 张三
# 0:关闭  1:开启
FLAG = 1

# mysql
MYSQL_DRIVERCLASS_PATH=com.mysql.jdbc.Driver
MYSQL_CONN_URL=jdbc:mysql://localhost:3306/c1809
MYSQL_USERNAME=root
MYSQL_PASSWORD=123456

# oracle
ORACLE_DRIVERCLASS_PATH=oracle.jdbc.driver.OracleDriver
ORACLE_CONN_URL=jdbc:oracle:thin:@localhost:1521:orcl
ORACLE_USERNAME=itstudy
ORACLE_PASSWORD=Oracle01

12.BeanUtils包(后续时间更新)

13.JDBC的高级封装(后续时间更新)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值