认识JDBC
先了解什么是数据库连接驱动
数据库驱动:是连接应用程序和数据库的关键,我们的程序会通过数据库驱动,来和数据库打交道!!!
SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,俗称JDBC
那么这些规范的实现由具体的厂商去做
对于开发人员来说,我们只需要掌握JDBC接口的操作即可!
下载和导入数据库驱动
一、下载
1、下载地址:https://dev.mysql.com/downloads/
2、点击Connerctor/J,选择框起来的选项,表示与平台无关
3、下载这个压缩文件
4、弹出来的界面选择如下:
5、将解压后的文件放在指定的位置
二、导入IDRA
1、在新建的项目下建一个lib目录
2、将我们下载好的jar包复制进来
3、右键lib目录,点击 Add as Library
4、点击OK
5、发现这个jar包是可以展开的,说明我们已经成功将jar包导入到项目中
第一个JDBC程序(标准的操作步骤)
一、操作步骤
1、pom.xml中导入连接是数据库的jar包,类中加载JDBC驱动;
2、编写用户的账号和密码以及URL路径;
3、驱动连接数据库,返回连接的对象connection;
4、连接对象connection创建执行SQL的对象statement;
5、执行SQL对象调用查询或者更新的方法(executeQuery)执行SQL语句,并且返回一个结果集;
6、释放连接
二、测试代码(其中第一步和第二步的代码是固定的,背过!!!)
数据库表:
IDEA代码:
导入依赖:
<!-- 导入数据库连接驱动的jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
具体代码如下:
import java.sql.*;
// 我的第一个JDBC程序
public class jdbcFirst {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1、加载驱动,记住这个加载方式,这是固定的写法
Class.forName("com.mysql.cj.jdbc.Driver");
//2、用户信息和URL,记住这个路径,也是固定写法!!!!!!!!!!
/*useUnicode=true 表示支持中文编码
* characterEncoding=utf8 表示设置字符集编码
* useSSL=true 表示使用安全的连接
* "?" 的作用是连接参数
* "&"表示and 和
* URL 表示唯一定位:
* MySQL公式:协议://主机地址:端口号(默认3306)/数据库名?参数1&参数2&参数3;
* oralce公式: jdbc:oralce:thin@localhost:1521(默认1521):sid
* */
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username = "root";
String password = "root";
//3、连接数据库,返回数据库对象 Connection代表数据库,同样还可以在这个级别上设置自动提交事务,回滚等操作
Connection connection = DriverManager.getConnection(url, username, password);
/*//数据库级别的操作常见如下:
connection.commit(); //提交事务
connection.setAutoCommit(true);// 设置是否自动提交
connection.rollback(); //回滚事务*/
//4、执行SQL对象 statement是用来执行SQL的对象
Statement statement = connection.createStatement();
//5、执行SQL的对象 去执行SQL,可能存在结果,查看返回的结果
String sql1 = "SELECT * FROM users"; //定义查询语句
//注意:statement调用的只有Query 和 update 方法,因为insert 和 delete 都归在了update下,executeUpdate()返回受影响的行数
//statement.execute();//增删改查的语句都可以执行
ResultSet resultSet = statement.executeQuery(sql1);// resultSet只有查询的时候会有,返回一个结果集,链表的形式,结果集中封装了全部的查询的结果
while (resultSet.next()) { //resultSet.next()返回值是一个布尔类型,如果结果集中后面没有数据就返回false
System.out.println("id = " + resultSet.getObject("id"));// getObject是在不知道列数据类型的时候使用,resultSet还可以调用特定类型的方法
System.out.println("name = " + resultSet.getObject("NAME"));
System.out.println("pwd = " + resultSet.getObject("PASSWORD"));
System.out.println("email = " + resultSet.getObject("email"));
System.out.println("birthday = " + resultSet.getObject("birthday"));
System.out.println("=============================================");
}
//6、释放连接,必须要做(连接很占内存,必须释放资源)
resultSet.close();
statement.close();
connection.close();
}
}
抽取JDBC为一个工具类
1、新建一个包,名字叫做utils
2、工具类中将加载驱动的方法写到static静态代码块中,随着类的加载而加载,这样只需要将加载驱动的方法执行一次即可
然后将连接数据库和释放资源的功能写成一个方法,将来直接调用即可
import com.mysql.cj.protocol.Resultset;
import javax.xml.stream.events.StartDocument;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
//将我们用到的变量提升一下作用域
private static String driver;
private static String url;
private static String username;
private static String password;
static {
try {
// 1、通过工具类,获取他自己的反射对象,然后获取反射对象的类加载器,调用类加载器的获取资源的方法
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
//2、创建读取配置文件的对象
Properties properties = new Properties();
//3、调用读取配置文件的方法
properties.load(in);
//4、获取配置文件中的value
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//加载JDBC的Driver驱动,因为是放在了静态代码块中,所以只需要加载一次即可
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
//连接数据库方法
public static Connection getConnection() throws SQLException {
//连接数据库
return DriverManager.getConnection(url, username, password);
}
//释放资源方法
public static void release(Connection conn, Statement sta, ResultSet rs) throws SQLException {
if (rs != null) {
rs.close();
}
if (sta != null) {
sta.close();
}
if (conn != null) {
conn.close();
}
}
}
运用工具类完成增删改查
在IDEA中,增删改都直接调用Update方法,查询调用excuteQuery方法
测试表:
一、增删改
public class testInsert {
public static void main(String[] args) throws SQLException {
Connection conn = null;
Statement sta = null;
ResultSet rs = null;
try {
//获取连接,因为我们将JDBC封装成一个工具类,这里直接调用工具类的方法即可
conn = JdbcUtils.getConnection();
//获取执行sql的对象
sta = conn.createStatement();
//编写需要的SQL语句
String sql = "INSERT INTO users (id,`NAME`,`PASSWORD`,`email`,birthday)" +
"VALUES (4,'张三','1232456445','12345@qq.com','2023-01-23');";
//凡是增、删和改都用Update这个方法,返回一个int类型受影响的行数
int i = sta.executeUpdate(sql);
if (i != 0){
System.out.println(i + "条数据插入成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//当操作结束后,调用释放资源的方法
JdbcUtils.release(conn,sta,rs);
}
}
}
二、查询
public class testQuery {
public static void main(String[] args) {
Connection conn = null;
Statement sta = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
sta = conn.createStatement();
String sql = "SELECT * FROM users";
rs = sta.executeQuery(sql);
while (rs.next()){
System.out.println("id:" + rs.getInt("id"));
System.out.println("NAME:" + rs.getString("NAME"));
System.out.println("PASSWORD:" + rs.getInt("PASSWORD"));
System.out.println("email:" + rs.getObject("email"));
System.out.println("birthday:" + rs.getObject("birthday"));
System.out.println("============================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
JdbcUtils.release(conn,sta,rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
SQL注入
SQL语句可以实现拼接(也就存在漏洞),如果我们 写的业务中没有屏蔽这种SQL注入问题,程序就会被攻击
下面演示一段SQL注入代码:
public class testLogin {
public static void main(String[] args) {
//主方法中调用登陆的业务,写一个"'or '1 = 1", "'or '1 = 1"的username h和password 这样就会让where条件判断一直保持true
login("'or '1 = 1", "'or '1 = 1");
}
//登陆业务
public static void login(String username, String password) {
Connection conn = null;
Statement sta = null;
ResultSet rs = null;
try {
//获取连接,因为我们将JDBC封装成一个工具类,这里直接调用工具类的方法即可
conn = JdbcUtils.getConnection();
//获取执行sql的对象
sta = conn.createStatement();
//编写需要的SQL语句
String sql = "select * from users where `NAME` = '" + username + "'AND PASSWORD = '" + password + "'";
//凡是增、删和改都用Update这个方法,返回一个int类型受影响的行数
rs = sta.executeQuery(sql);
while (rs.next()) {
System.out.println("id:" + rs.getInt("id"));
System.out.println("NAME:" + rs.getString("NAME"));
System.out.println("PASSWORD:" + rs.getInt("PASSWORD"));
System.out.println("email:" + rs.getObject("email"));
System.out.println("birthday:" + rs.getObject("birthday"));
System.out.println("============================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//当操作结束后,调用释放资源的方法
try {
JdbcUtils.release(conn, sta, rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
输出的结果:
写一段拼接的SQL就会将所有的信息全部查询出来
PreparedStatement对象
这个对象可以防止SQL注入,并且效率会更高
一、PreparedStatement对象增删改的代码
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement sta = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//value中的问号表示占位符,用prepareStatement方法的优势就是可以最后添加value
String sql = "INSERT INTO users (id,`NAME`,`PASSWORD`,`email`,birthday) VALUES(?,?,?,?,?)";
//区别于 statement
/*1、prepareStatement对象可以直接被数据库conn对象创建,而statement对象还需要conn对象调用createStatement()方法才能创建
* 2、prepareStatement()方法需要传入一个参数,这个参数是预编译的SQL语句,通俗讲是先写SQL,暂时不执行
* 3、提前将写好的SQL放进方法中
* */
sta = conn.prepareStatement(sql);
sta.setInt(1, 4);
sta.setString(2, "10086");
sta.setString(3, "123456789");
sta.setString(4, "244562@qq.com");
//注意:这里的date是先调用SQL的date
//然后调用的是java中的 until下的date,getTime是获取当前时间戳
sta.setDate(5, new java.sql.Date(new Date().getTime()));
//真正的执行方法,区别于statement调用这个方法还需要往里面传入sql,而prepareStatement不需要往里面传SQL语句了
int i = sta.executeUpdate();
if (i > 0 ){
System.out.println(i + "行被插入!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
JdbcUtils.release(conn, sta, rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
二、查询代码
public class TestQuery {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement sta = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//value中的问号表示占位符,用prepareStatement方法的优势就是可以最后添加value
String sql = "select * from users where id = ?";
//区别于 statement
/*1、prepareStatement对象可以直接被数据库conn对象创建,而statement对象还需要conn对象调用createStatement()方法才能创建
* 2、prepareStatement()方法需要传入一个参数,这个参数是预编译的SQL语句,通俗讲是先写SQL,暂时不执行
* 3、提前将写好的SQL放进方法中
* */
sta = conn.prepareStatement(sql);
sta.setInt(1,3);
//真正的执行方法,区别于statement调用这个方法还需要往里面传入sql,而prepareStatement不需要往里面传SQL语句了
rs = sta.executeQuery();
if (rs.next()){
System.out.println(rs.getString("NAME"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
JdbcUtils.release(conn, sta, rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
三、防止SQL注入的原因
见下面的代码以及注释
public class testLogin {
public static void main(String[] args) {
//主方法中调用登陆的业务
//1、正常的参数
login("10086","123456789");
//2、SQL注入的参数
login("'or '1 = 1", "'or '1 = 1");
}
//登陆业务
public static void login(String username, String password) {
Connection conn = null;
PreparedStatement sta = null;
ResultSet rs = null;
try {
//获取连接,因为我们将JDBC封装成一个工具类,这里直接调用工具类的方法即可
conn = JdbcUtils.getConnection();
//编写需要的SQL语句
String sql = "select * from users where `NAME` = ? and `PASSWORD` = ?";
//获取执行sql的对象,进行预编译
//prepareStatement防止SQL注入的本质在于,他将传进来的参数都当作字符,假设其中存在‘’这种转义字符,会被直接转义
sta = conn.prepareStatement(sql);
//给value赋值
sta.setString(1,username);
sta.setString(2,password);
rs = sta.executeQuery();
while (rs.next()) {
System.out.println("id:" + rs.getInt("id"));
System.out.println("NAME:" + rs.getString("NAME"));
System.out.println("PASSWORD:" + rs.getInt("PASSWORD"));
System.out.println("email:" + rs.getObject("email"));
System.out.println("birthday:" + rs.getObject("birthday"));
System.out.println("============================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//当操作结束后,调用释放资源的方法
try {
JdbcUtils.release(conn, sta, rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
Statement对象
使用statement 对象存在SQL注入的问题,不安全,一般多用PreparedStatement对象!