JDBC
学习完了mysql之后,下一步就是用Java去连接数据库,实现具体的操作,那么最基础的就是JDBC了,于是学习了JDBC,放在博客上,一方面方便自己之后忘记来快速回顾,另一方面希望帮助小伙伴们。大家如果有什么问题,或者对本博客的代码由异议或者有更好的看法与建议,欢迎评论。我们一起学习,一起进步(如果有幸能得到大佬访问,那就更好了)
``在开始之前,还是想约定一下环境:
- 数据库版本:mysql5.7
- mysql驱动的依赖版本:5.1.27
- 使用工具:IDEA
一、数据库驱动
- 数据库驱动实际上就是各大数据库实现了Java中的JDBC接口。所以数据库的驱动就是JDBC的实现类
- 实现驱动需要加载一个jar包
- 在Maven仓库中下载:https://mvnrepository.com/artifact/mysql/mysql-connector-java
- 下载的时候注意版本和使用的人数(关系到是否稳定的问题)
二、第一个JDBC程序
2.1 创建一个新的项目
- 创建一个Java项目
- 创建待会执行sql语句要用到的表
- 可以基于命令行
- 也可以基于SQLyog等图形化界面
2.2 加载数据库驱动
- 在项目文件中创建一个lib目录
- 将jar包导入
- 右键单击lib,选择Add as library ,添加到项目文件当中
- 当导入的jar包可以在lib目录下展开的时候,说明加载数据库驱动成功
2.3 编写测试代码
-
首先要先连接数据库
- 点击最右面的Database,加载mysql,将创建的表导入
- 测试导入是否成功
-
编写测试代码
- 步骤:
- 1、加载驱动
- 2、填写用户信息
- 3、连接数据库
- 4、执行SQL对象
- 5、执行SQL的对象 去 执行SQL , 可能存在结果,查看返回的结果
- 6、释放资源(先定义后释放)
package com.zhang.jdbc.demos; import java.sql.*; public class demo2 { public static void main(String[] args) throws ClassNotFoundException, SQLException { // 1、加载驱动 Class.forName("com.mysql.jdbc.Driver"); /** * 加载驱动不需要返回一个class的结果,因为后面用不到 * 在Driver当中本身就有一个注册驱动的语句,所以不需要自己注册 * */ // 2、用户信息和url // useUnicode=true //可以使用中文编码 // &characterEncoding=UTF8 // 设置中文编码格式 // &useSSL=true // 使用安全的连接 String url = "jdbc:mysql://localhost:3306/db3?useUnicode=true&characterEncoding=UTF8&useSSL=false"; String username = "root"; String password = "20zzy100588A"; /** * url的格式: * 协议//主机地址:端口号/数据库名称?设置1&设置2&设置3 * username 和 password 是你自己的数据库的登录账号和密码 --- 需要匹配起来 * */ // 3、连接成功,数据库对象 Connection 代表数据库 Connection conn = DriverManager.getConnection(url , username , password); /** * Connection可以执行事务,代表数据库可执行的操作 * * conn.commit(); * conn.isReadOnly(); * conn.rollback(); * */ // 4、执行SQL的对象 Statement statement = conn.createStatement(); // 5、执行SQL的对象 去 执行SQL , 可能存在结果,查看返回的结果 // 查询 String sql = "select * from db3 " ; ResultSet resultSet = statement.executeQuery(sql); // 返回的结果集,封装了查询出来的全部结果 while (resultSet.next()){ System.out.println("id = " + resultSet.getObject("id")); System.out.println("NAME = " + resultSet.getObject("NAME")); System.out.println("balance = " + resultSet.getObject("balance")); } // 改 String sql1 = "update db3 set balance = 500 where id = 1 "; int count = statement.executeUpdate(sql1); System.out.println(count); /** * statement.executeUpdate(); // 增删改都使用这个 , 返回对应的数据,如果返回的结果大于0 ,说明执行成功 * statement.execute() ; // 可以执行所有的SQL语句 * statement.executeQuery(); // 返回查询的ResultSet结果集 * * Result的结果集 , 封装了所有的查询结果 * * // 返回结果集 * resultSet.getObject(); // 不知道数据类型的时候 * // 已知数据类型的时候 * resultSet.getByte(); * resultSet.getShort(); * resultSet.getInt(); * resultSet.getLong(); * resultSet.getFloat(); * resultSet.getDouble(); * resultSet.getBoolean(); * resultSet.getString(); * * // 遍历 * resultSet.beforeFirst(); // 移动到最前面 * resultSet.afterLast(); // 移动到最后面 * resultSet.next(); // 移动到下一个数据 * resultSet.previous(); // 移动到前一行 * resultSet.absolute(row); // 移动到指定行 * */ // 6、释放连接 (后定义先释放) resultSet.close(); statement.close(); conn.close(); } }
- 步骤:
2.4 JDBC中对象的解释
- 参见上方的代码的注释
三、Statement对象
3.1 Statement对象简述
- 对statement对数据库进行增删改查,相当于对表内容的修改
- connection 是对数据库的表的结构进行操作
3.2 工具类的提取
-
提取工具类JdbcUtils
-
第一步是配置依赖包(db.properties)
# 此处是存放jdbc的通用配置 #注意:里面不能有;和空格 driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/db3?useUnicode=true&characterEncoding=UTF8&useSSL=false username = root //下面是写自己的密码 password = *******
-
第二步是书写工具类的代码
package com.zhang.jdbc.utils; import java.io.InputStream; import java.sql.*; import java.util.Properties; // 工具类 public class JdbcUtils { public static String driver = null; public static String url = null ; public static String username = null ; public static String password = null ; static{ try { //获取到db.properties中的资源 InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties"); // 定义一个依赖 Properties properties = new Properties(); // 将加载到的资源封装到依赖当中去 properties.load(in); // 将从代码当中获取到的值赋给此工具类当中的相应的值 driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); // 1、驱动只用加载一次,所以可以直接写在这个工具类之中 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 , 自定义方法 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url , username , password); } // 释放连接 , 自定义方法 public static void release(Connection conn , Statement st , ResultSet rs){ if (rs != null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(st != null ){ try { st.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } }
-
-
于是代码得到了简化:
package com.zhang.jdbc.demos; import com.zhang.jdbc.utils.JdbcUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; // 封装 public class demo3 { public static void main(String[] args){ Connection connection = null ; Statement st = null ; ResultSet rs = null ; try { connection = JdbcUtils.getConnection(); String sql = "delete from db3 where id = 3 "; String sql1 = "insert into db3 (id , NAME , balance) values(3 , 'wangwu', 1500 )"; st = connection.createStatement(); int j = st.executeUpdate(sql); if(j >= 0){ System.out.println("删除成功!"); } int i = st.executeUpdate(sql1); if(i >= 0 ){ System.out.println("插入成功!"); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally{ JdbcUtils.release(connection , st , rs); } } }
3.3 SQL注入
package com.zhang.jdbc.sqlhack;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
// 演示sql注入
public class SqlHack {
public static void main(String[] args){
// 正常登录
// login("root","123456");
// SQL注入
/**
* SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,
* 攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,
* 在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,
* 从而进一步得到相应的数据信息
* */
login(" 'or ' 1=1 "," 'or ' 1=1 ");
}
public static void login(String username , String password){
Connection connection = null ;
Statement statement = null ;
ResultSet resultSet = null ;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
//select * from user where NAME= '' or '1=1' and PASSWORD= ''or '1=1';
String sql = "select * from user where NAME='"+ username +"' and PASSWORD='"+ password + "'" ;
resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println(resultSet.getString("NAME"));
System.out.println(resultSet.getString("PASSWORD"));
System.out.println("=======================================================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
JdbcUtils.release(connection,statement,resultSet);
}
}
}
3.4 PreparedStament
- 可以防止SQL注入,效率更高
- 步骤:
- 利用工具类创建对象
- 先写SQL语句,进行预编译
- 后填写,然后执行
package com.zhang.jdbc.sqlhack.solvesqlhack;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestDelete {
public static void main(String[] args){
Connection conn = null ;
PreparedStatement st = null ;
ResultSet rs = null ;
try {
conn = JdbcUtils.getConnection();
String sql = "delete from user where NAME = 'zhaoliu' or NAME = 'zhaoqi'";
st = conn.prepareStatement(sql);
int res = st.executeUpdate();
if(res >= 0){
System.out.println("删除成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
JdbcUtils.release(conn , st , rs);
}
}
}
防止SQL注入
package com.zhang.jdbc.sqlhack;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SqlHack2 {
public static void main(String[] args){
// 正常登录
//login("root" , "123456");
// SQL注入
login(" 'or '1=1 " , " 'or ' 1=1 ");
/**
* 采用先编译 ,之后运行的方法对SQL注入进行截断,从而防止了SQL的注入
* */
}
public static void login(String username , String password){
Connection conn = null ;
PreparedStatement st = null ;
ResultSet rs = null ;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from user where NAME = ? and PASSWORD = ?" ;
st = conn.prepareStatement(sql);
st.setString(1,username);
st.setString(2,password);
rs = st.executeQuery();
while(rs.next()){
System.out.println(rs.getObject("NAME"));
System.out.println(rs.getObject("password"));
System.out.println("======================================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
JdbcUtils.release(conn , st , rs);
}
}
}
四、 JDBC操作事务
- 满足ACID原则:
- 原子性:或者全部成功,要么全部失败
- 一致性:总数不变
- 隔离性:多个进程互不干扰
- 脏读:见Mysql数据库
- 不可重复读:见Mysql数据库
- 虚读(幻读):见Mysql数据库
- 持久性:一旦提交不可逆,持久化到数据库
package com.zhang.jdbc.transaction;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TransactionStart {
public static void main(String[] args){
Connection conn = null ;
PreparedStatement st = null ;
ResultSet rs = null ;
try {
conn = JdbcUtils.getConnection();
String sql = "update db3 set balance = ?";
st = conn.prepareStatement(sql);
st.setInt(1 , 1000);
int i = st.executeUpdate();
if(i >= 0 ){
System.out.println("事务开启的准备工作已经完成");
System.out.println("初始化完成!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
JdbcUtils.release(conn , st , rs);
}
}
}
package com.zhang.jdbc.transaction;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@SuppressWarnings("all")
public class TestTransaction {
public static void main(String[] args){
Connection conn = null ;
PreparedStatement st = null ;
ResultSet rs = null ;
try {
conn = JdbcUtils.getConnection();
// 关闭系统的自动提交功能, 相当于开启事务
conn.setAutoCommit(false);
// 事务的主体
String sql1 = "update db3 set balance = (balance-500) where id = ?";
st = conn.prepareStatement(sql1);
st.setInt(1 , 1);
int i = st.executeUpdate();
String sql2 = "update db3 set balance = (balance + 500) where id = ?";
st = conn.prepareStatement(sql2);
st.setInt(1 , 3);
int j = st.executeUpdate();
if(i >= 0 && j >= 0){
conn.commit();
System.out.println("转账成功!");
}
} catch (SQLException throwables) {
try {
// 如果失败会自动执行回滚
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
}finally {
JdbcUtils.release(conn , st , rs);
}
}
}
五、数据库连接池
5.1 连接池简介
- 数据库连接 — 执行完毕 — 释放
- 连接和释放的过程十分浪费资源
- 池化技术:准备一些预先的资源,过来就连接已经准备好的
- 最小链接数:一般是常用的连接数
- 最大连接数:最高承载上限
- 等待超时: *** ms
- 编写连接池实现一个接口 DateSource
- 开源数据源的实现
- DBCP
- C3P0
- Druid:阿里巴巴
- 使用了这些数据库连接池之后,我们就不需要编写连接数据库的代码了
5.2 DBCP连接池
- 需要用到的jar包:
- commons-dbcp-1.4.jar
- commons-pool-1.6.jar
- 将上述包添加到相应项目的lib目录下
package com.zhang.jdbc.sqlhack.solvesqlhack;
import com.zhang.jdbc.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestInsert {
public static void main(String[] args){
Connection conn = null ;
PreparedStatement st = null ;
ResultSet rs = null ;
try {
conn = JdbcUtils.getConnection();
// 增加
String sql = "insert into user (NAME , password) values (?,?)";
st = conn.prepareStatement(sql); // 预编译
// 手动赋值
st.setString(1,"zhaoliu");
st.setString(2,"123456");
int res = st.executeUpdate();
if(res >= 0){
System.out.println("插入成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
JdbcUtils.release(conn , st , null);
}
}
}
5.3 C3P0连接池
- 需要用到的jar包:
- c3p0-0.9.5.5.jar
- mchange-commons-java-0.2.19.jar
- 将上述包添加到相应项目的lib目录下