1. 简介
JDBC(Java Data Base Connectivity) 是 Java 访问数据库的标准规范.,为 多种关系数据库提供统一访问。它由一组用Java语言编写的类和接口组成。
JDBC只是Java提供的一些接口,而这些接口的实现类是由各个数据库厂商提供的,称之为数据库连接驱动。这样一来,我们就可以面向接口编程,以一种统一的方式访问多种数据库。
2. JDBC使用流程
- 下载对应版本的数据库连接驱动,并导入项目。
- 注册驱动
通过Class.froName("Driver实现类路径");
加载Driver驱动并注册到DriverManager中。
对于MySQL而言,不同版本的 "Driver实现类路径" 也是不同的:
5.x版本为:com.mysql.jdbc.Driver
8.x版本为:com.mysql.cj.jdbc.Driver
原理为: Driver类在静态代码块中创建Driver对象,并注册到DriverManager中。
**注意:**在JDK1.6中DriverManager明确指出,在JDBC4.0及之后版本中可以不用forName显示加载驱动。这是因为在获取连接时,可以根据url判断并自动加载对应的驱动。但是一般开发中还是 要写forName,因为有些场合并不会自动加载,如Tomcat中,由于类加载器不同,省略forName就会找不到类。
- 创建连接
通过DriverManager
的getConnection方法获取连接
Connection getConnection(url,userName,password);
- url:jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTC
jdbc:mysql
是 协议 名,固定写法localhost:3306
为服务器地址 及 端口号test
是要连接的数据库名characterEncoding=UTF-8
的作用为,告诉mysql,将数据转化为 utf-8 格式之后再传给我的程序。serverTimezone=UTC
有时候不指定时区会报错。 - userName 登录MySQL的用户名
- password 密码
- 执行SQL语句
通过connection对象的createStatement()方法获取Statement对象,然后通过Statement对象的executeUpdate()
和executeQuery()
方法执行SQL语句:executeUpdate()
:方法用于执行insert update delete语句,返回受影响的行数。executeQuery()
:执行select语句, 返回ResultSet结果集对象。 - 处理结果集
ResultSet的作用为 封装查询结果,然后通过其next方法和getXxx方法遍历结果集。
方法 | 说明 |
boolean next() | 1) 游标向下一行 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false |
xxx getXxx( String or int) | 1) 通过列名,参数是 String 类型。返回不同的类型 2) 通过列号,参数是整数,从 1 开始。返回不同的类型 |
- 释放资源
释放原则为:先开的后关,一次关闭 ResultSet对象,Statement对象,Connection对象 - 示例
@Test
public void test(){
String url = "jdbc:mysql://192.168.65.131:3306/test?characterEncoding=UTF-8&serverTimezone=UTC";
String userName = "mochen";
String password = "123456";
Connection conn = null;
Statement stmt = null;
ResultSet rs = null; // 查询数据
try {
// 加载Driver类,并注册到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection(url, userName, password);
// 获取Statement对象
stmt = conn.createStatement();
// 执行SQL语句
stmt.executeUpdate("insert into jdbc_user values(null,'王蛋','abcd','2014-05-12 13:21:00')"); // 插入数据
rs = stmt.executeQuery("select * from jdbc_user");
// 处理结果集
while(rs.next()){
System.out.println(rs.getString(1)+"\t\t"+rs.getString(2)
+"\t\t"+rs.getString(3)+"\t\t"
+rs.getString(4));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally { // 释放资源
if(null!=rs){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null!=stmt){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null!=conn){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3. SQL注入 与 PreparedStatement
3.1 SQL注入
什么是SQL注入:
当要执行的SQL语句 是由用户输入的参数 拼接而来时,如果用户可以输入一些有特殊意义的内容,就可以改变原有的SQL的意义,从而进行非法操作。这种情况就称为SQL注入。
举例:
如 验证用户登录的SQL语句:
String sql = "select * from jdbc_user " +
"where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'";
如果用户输入的密码为:' or '1'=1
因为1=1肯定是真,or 又是有真即真,所以where条件的结果总为true。这就改变了SQL原有的含义。
真正的SQL注入可能要复杂的多,但原理应该都是
3.2 PreparedStatement
1. 简介
PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语 句对象。预编译:是指SQL 语句被预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。
2. 使用
// 获取 PreparedStatement
conn.prepareStatement(String sql); // Sql中可以使用 ? 作为参数的占位符,然后使用setXxx方法赋值
// 常用方法
int executeUpdate();
ResultSet executeQuery(); // 注意:这里不必填入SQL
3. 示例
PreparedStatement ps = conn.prepareStatement("select * from employee where id=? and name=?");
ps.setInt(1,1);
ps.setString(2,"孙悟空");
rs = ps.executeQuery();
while (rs.next()){
for(int i=0;i<6;i++){
System.out.print(rs.getObject(i+1)+"\t");
}
}
4. JDBC工具类
如何编写:
- 用常量记录 DRIVERNAME、URL 、USER 、PASSWORD ,也可以用配置文件
- 静态代码块中注册驱动(或者是创建数据源)
- 提供静态方法 获取连接、 关闭对象。
其他的方法(像直接执行SQL的方法)就不要提供了,因为如果这样调用者中就没有Connection和Statement的引用,而这两者又只能在处理结果后才能关闭,这就不是静态方法能够实现的功能了。 - 工具类类一般都是提供静态方法,不能创建对象。
我认为所谓工具就是要足够简单,独立,与主程序逻辑没什么关系。拿来即用,用完就不需要有什么后续操作。所以要用静态方法。为了保证它的绝对简单,最好提供private 构造方法,明确禁止创建对象。
示例:
/**
* JDBC 工具类
*/
public class JDBCUtils {
//1. 定义字符串常量, 记录获取连接所需要的信息
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
public static final String USER = "root";
public static final String PASSWORD = "123456";
//2. 静态代码块, 随着类的加载而加载
static{
try {
//注册驱动
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.获取连接的静态方法
public static Connection getConnection(){
try {
//获取连接对象
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
//返回连接对象
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
//关闭资源的方法
public static void close(Connection con, Statement st){
if(con != null && st != null){
try {
st.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement st, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(con,st);
}
}
5. 事务控制
mysql的自动事务为每一条DML语句都开启一个事务。JDBC可以通过关闭自动提交来打开事务。
相关方法:
void setAutoCommit(boolean autoCommit); // 设置为 false 关闭事务的自动提交
void commit(); // 事务提交
void rollback(); // 事务回滚
示例:
//1. 获取连接
con = JDBCUtils.getConnection();
//2. 开启事务
con.setAutoCommit(false);
//3. 获取到 PreparedStatement 执行两次更新操作
//3.1 tom 账户 -500
ps = con.prepareStatement("update account set money = money - ? where name = ? ");
ps.setDouble(1,500.0);
ps.setString(2,"tom");
ps.executeUpdate();
//模拟tom转账后 出现异常
System.out.println(1 / 0);
//3.2 jack 账户 +500
ps = con.prepareStatement("update account set money = money + ? where name = ? ");
ps.setDouble(1,500.0);
ps.setString(2,"jack");
ps.executeUpdate();
//4. 正常情况下提交事务
con.commit();
System.out.println("转账成功!");
6. 批处理
6.1 简介
批处理指的是一次操作中执行多条SQL语句,批处理相比于一次一次执行效率会提高很多。主要用途就是批量操作,如一键生成一千个用户,从提交的文件中提取并导入大批量的数据 等。
6.2 实现
Statement和PreparedStatement都支持批处理操作。
相关方法:
void addBatch(); // 将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。
int[] executeBatch(); // 提交一批命令到数据库中执行,返回的数组是每条命令所影响的行数
示例:
@Test // Statement 批处理 测试
public void test135() throws Exception{
Connection conn = DruidUtils.getConnection();
Statement stmt = conn.createStatement();
for(int i=0;i<10;i++) {
stmt.addBatch("insert into appdate values(123,'猪悟能')");
}
int[] ints = stmt.executeBatch();
System.out.println(Arrays.toString(ints));
}
@Test // PreparedStatement 批处理 测试
public void test150() throws Exception{
Connection conn = DruidUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("insert into appdate values(?,?)");
for(int i=0;i<10;i++) {
ps.setInt(1, 123);
ps.setString(2,"猪悟能");
ps.addBatch();
}
int[] ints = ps.executeBatch();
System.out.println(Arrays.toString(ints));
}
**注意:**批处理中不能进行查询
7. 获取元数据
相关类:DatabaseMetaData、ResultSetMetaData
使用方法:
DatabaseMetaData:
@Test // DatabaseMetaData 测试
public void test165() throws Exception{
Connection conn = DruidUtils.getConnection();
// 通过 Connection 获取 DatabaseMetaData 对象
DatabaseMetaData dmd = conn.getMetaData();
// 通过 DatabaseMetaData 对象(dmd) 获取 元数据
System.out.println(dmd.getDriverName()); // 驱动器名,如:MySQL Connector/J
System.out.println(dmd.getURL()); // 就是连接是传入的URL
System.out.println(dmd.getUserName()); // 登录的用户名,如:root@localhost
System.out.println(dmd.isReadOnly()); // 是否 只读
System.out.println(dmd.getDatabaseProductName()); // 获取数据库软件的名字,如:MySQL
System.out.println(dmd.getDatabaseProductVersion()); // 获取数据库软件的版本,如:8.0.21
}
ResultSetMetaData
@Test // ResultSetMetaData 测试
public void test178() throws Exception{
Connection conn = DruidUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("select * from employee");
ps.executeQuery();
// 同过 PreparedStatement 获取 ResultSetMetaData 对象,注意 Statemet 不行。
ResultSetMetaData rsmd = ps.getMetaData();
// 通过 ResultsetMetaData 对象获取元数据
System.out.println(rsmd.getColumnCount()); // 获取结果集的 列数
System.out.println(rsmd.getColumnName(2)); // 获取结果集 某一列的列名
System.out.println(rsmd.getColumnTypeName(2)); // 返回对应列的 数据类型(MySQL类型)名字,如:VARCHAR
System.out.println(rsmd.getColumnClassName(2)); // 返回对应列的 数据类型对应的java类路径,如:java.lang.String
}
8. 注意
- statement的方法使用说明
execute()
executeUpdate() 可以进行:
数据的 增 删 改、
表的 增 删 改
executeQuery() 可以查询:
表中 数据
有哪些表 和 数据库 show tables, show databases
当前表名 select database()
表结构 desc 表名 返回 表结构包括:Field、TYPE、NULL、Key、Default、Extra
暂时只能想到这些操作,其余的用到了在补充吧!
- Statement与PreparedStatement的选择
当一次同一SQL要执行很多次时,选择preparedStatement。
当涉及到查询参数时,使用PreparedStatement。
其余情况:
即 只是 简单的 执行单独一条SQL语句,也不用有什么参数限制时,使用Statement即可。
姑且这么认为吧,对不对的 总得先有个判别依据,要不然太混乱了。