?? ?1. JDBC直接访问数据库
?? ?2. JDO(java Data Object)技术
?? ?3. 第三方O/R工具:?? ??? ?如hibernate、spring等
JDBC(Java DataBase Connectivity:Java语言连接数据库):
好处
缺点
JDBC的结构可划分为两层:一个是面向底层的JDBC Driver Interface(驱动程序管理器接口),另一个是面向程序员的JDBC API。
1. 加载数据库驱动:在Java应用程序中,首先需要加载适当的数据库驱动程序。
2. 连接数据库:使用Java程序中的getConnection()方法与数据库建立连接。
3. 创建操作对象:使用Java程序中的Statement对象或者PreparedStatement对象来执行SQL语句。
4. 执行SQL语句:使用Statement对象或者PreparedStatement对象来执行SQL语句,在执行SQL语句之前,需要对SQL语句进行预编译。
5. 处理查询结果:使用ResultSet对象来处理从数据库返回的查询结果。
6. 释放资源:使用Java程序中的close()方法释放资源(ResultSet对象、Statement对象、Connection对象)。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
@SuppressWarnings({"all"})
public class PreparedStatement_ {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
//让用户输入管理员名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 说明,如果希望看到SQL注入,这里需要用nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到PreparedStatement
//3.1 组织SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd from admin where name =? and pwd = ?";
//3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//3.3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4. 执行 select 语句使用 executeQuery
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这里执行 executeQuery ,不要在写 sql
ResultSet resultSet = preparedStatement.executeQuery(sql);
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//4.关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
}
}
包含在?java.sql,javax.sql包;
特定数据库的连接(会话)。在连接上下文中执行SQL语句并返回结果。
Statement接?: 编译执?静态SQL指令
//Conection接口源码
Statement createStatement() throws SQLException;
创建Statement对象
Statement statement = connection.createStatement();
//释放资源
connection.close
PreparedStatement接?:继承了Statement接?,预编译动态SQL指令(解决SQL注?问题)
//Conection接口源码
PreparedStatement prepareStatement(String sql)throws SQLException;
//创建PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//释放资源
connection.close
CallableStatement接?:继承了PreparedStatement接?,可以调?存储过程
//Conection接口源码
CallableStatement prepareCall(String sql) throws SQLException;
创建callableStatement对象
CallableStatement callableStatement = connection.prepareCall(sql);
//释放资源
connection.close
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务;? ?每次执行一个SQL语句时,若执行成功,则会向数据库自动提交,而不能回滚;
若将多个SQL语句作为一个整体执行,需要使用事务;
//事务的实现流程:
获取数据库连接
conn = JDBCUtils.getConnection();
开启事务(关闭事务?动提交)
conn.setAutoCommit(false);
进行数据库操作
xxx
若有异常,则回滚事务
connection.rollback();
若没有异常,则提交事务
conn.commit();
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //开启了事务
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
//这里提交事务
connection.commit();
} catch (SQLException e) {
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的sql");
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
Statement对象:
//String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
//String sql = "update actor set name='周星驰' where id = 1";
String sql = "delete from actor where id = 1";
//statement 用于执行静态 SQL 语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
// 执行DML(insert\delete\update)语句,DDL(create\drop\alter)的SQL指令(返回值是受影响行数)
int row = statement.executeUpdate(sql);
// 执?DQL操作的SQL指令(返回结果集)
ResultSet rs = statement.executeQuery(sql);
//可以执行任意的sql
boolean execute(String sql);
//释放资源
statement.close
缺点:
-- SQL
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1
//4. 组织SqL
String sql = "select name , pwd from admin where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'"; //admin_name和admin_pwd为拼接到SQL中的变量
//使?字符串拼接变量的形式来设置SQL语句中的数据,可能会导致因变量值的改变引起SQL指令的原意发?改变,这就被称为SQL注?。
//SQL注?问题是需要避免的
当admin_name为1' or ;admin_pwd为or '1'= '1,SQL指令为:select name , pwd from admin where name ='1' or ' and pwd = ' or '1'= '1'; //此时SQL中的条件则是?个恒等式(sql指令发生变化)
基本介绍
?? ?执行的Sql 语句中的参数用( ?)表示, ?相当于占位符
相较于Statetment的好处
//得到PreparedStatement
//1 组织SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd from admin where name =? and pwd = ?";
//2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
//在获取PreparedStatement对象时,将SQL语句发送给mysql服务器进行检查,编译;若sql模板一样,则只需要进行一次检查、编译;
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4. 执行 select 语句使用 executeQuery(); 如果执行的是 dml(update, insert ,delete) executeUpdate(),无需传递SQL语句;
ResultSet resultSet = preparedStatement.executeQuery();
int row = preparedStatement.executeUpdate();
//释放资源
preparedStatement.close
操作Blob类型的数据:
写入
setBlob(int index,InputStream is)
读取
Blob blob = getBlob(String columnLabel);
InputStream is = blob.getBinaryStream()
批处理:
使用批处理功能
java 的批量更新机制
应用场景
Connection connection = JDBCUtils.getConnection();
// 1.设置为不自动提交数据
connection.setAutocommit(false);
//实际上执行JDBC时,因为只有占位符参数不同,SQL实际上是一样的
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (int i = 0; i < 5000; i++) {// 5000执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
// 将sql 语句加入到批处理包中 -> 看源码
// 1.“攒”sql
preparedStatement.addBatch();
// 当有1000条记录时,在批量执行
if ((i + 1) % 1000 == 0) {// 满1000条sql
//2. 执行batch:
int[] ns = preparedStatement.executeBatch();
//循环int[]数组即可获取每组参数执行后影响的结果数量。
// 3.清空批处理包
preparedStatement.clearBatch();
}
}
// 2. 提交数据
connection.commit();
// 关闭连接
JDBCUtils.close(null, preparedStatement, connection);
用于执行 SQL 存储过程。
基本介绍
细节
缺点
ResultSet resultSet = statement.executeQuery(sql);
//5. 使用while取出数据
while (resultSet.next()) { // 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,返回false,否则返回true
int id = resultSet.getInt(1); //获取该行的第1列
//int id1 = resultSet.getInt("id"); 通过列名来获取值, 推荐
String name = resultSet.getString(2);//获取该行的第2列
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//释放资源
resultSet.close
ResultSet底层结构:
ResultSet对象
里面有一个rowData
。数据实际在elementData
中,因为查询的结果只有两行,所以elementData数组
中只有两个,它们是以字节数组
的形式出现的。其中internalRowData
中出现了三个字节数组,则代表有三列。
驱动管理器
)java.sql.Driver 接口:
DriverManager类:负责管理JDBC驱动程序。使用JDBC驱动程序之前,必须先将驱动程序加载并注册后才可以使用,同时提供方法来建立与数据库的连接。
?针对MySQL数据库,需要去Mysql
官网下载JDBC的驱动包,驱动版本要与Mysql版本一一对应。Mysql驱动下载地址;
导入jar包
//1.导入驱动jar包
// 在项目下创建一个文件夹比如 libs,将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//(在同一个项目中只需要导入一次驱动jar包即可)
如果是maven项目就更简单了,直接在pom配置文件中添加依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<mysql.version>8.0.28</mysql.version>
</dependency>
</dependencies>
注册驱动
在Driver类中的静态初始化块中,注册驱动:DriverManager.registerDriver(new?Driver());
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
在我们的应?程序中?动注册驱动的代码也可以省略;【Class.forName(“com.mysql.cj.jdbc.Driver”);】
如果我们没有?动注册驱动,驱动管理器在获取连接的时候发现没有注册驱动 则读取 驱动jar/META-INF/servicesjava.sql.Driver?件中配置的驱动类路径进?注册 不推荐
JDBC URL:用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。jdbc:子协议:子名称;
常见的数据库URL
jdbc:oracle:thin:@localhost:1521:testdb
jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=testdb
jdbc:mysql://localhost:3306/testdb
静态加载Driver类;
//1. 注册驱动
Driver driver = new Driver(); //创建driver对象
//获取数据库连接
String url = "jdbc:mysql://localhost:3306/db_01?serverTimezone=UTC";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "root123"); //密码
//3. 得到连接
Connection connect = driver.connect(url, properties);
mysql驱动是8.0版本,因此在url后边加了时区serverTimezone=UTC。
使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性;
//1. 注册驱动
//使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//获取数据库连接
String url = "jdbc:mysql://localhost:3306/db_01";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "root123"); //密码
//3. 得到连接
Connection connect = driver.connect(url, properties);
8.0版本
对应的驱动类全类名是:com.mysql.cj.jdbc.Driver
。
5.7版本
对应的全类名是:com.mysql.jdbc.Driver
。
使用DriverManager 替代 driver 进行统一管理;? ? 便于应对需要连接多个数据库的场景;
//1. 注册驱动
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//获取数据库连接
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
DriverManager.registerDriver(driver);//注册Driver驱动
//3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);
这种方式没有创建
properties
对象,而且最后是使用DriverManager
来获取Connection
连接的。
使用Class.forName 自动完成注册驱动,简化代码;
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
//3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第4种方式~ " + connection);
在方式4的基础上改进,增加配置文件,让连接mysql更加灵活;
// 通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);// 建议写上
// 3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式5 "+connection);
}
//配置文件mysql.properties
user = root
password=root123
url=jdbc:mysql:// localhost:3306/db_01?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
有关数据库操作的异常。
创建JDBCUtils类,用于实现连接逻辑,交互配置文件等等
因为实际使用场景中,几乎数据库连接信息都不会写死在代码中,采用了配置文件的形式,使得灵活配置;
对于配置文件的实现:
- 创建Properties对象
- 创建流
- 加载流
- 通过Properties对象读取文件中的内容
- 关闭资源
配置文件mysql.properties
user=root
password=root123
url=jdbc:mysql://localhost:3306/db_01?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
//rewriteBatchedStatements表示使用PreparedStatement的批处理功能
JDBCUtils工具类详解:
- 在JdbcUtils的静态代码static代码块中读取db.properties中的相关配置,并加载驱动
- 每次使用JDBC之前都要获取getConnection连接,而获取连接的代码都是固定的,因此可以提取成一个公共方法getConnection;用户代码可以通过调用JdbcUtils.getConnection()来获取连接。
- 使用JDBCUtils工具类的优点,在我们有大量使用mysql的数据库的情况下,可以通过更改jdbc.properties配置文件就可以灵活修改数据库的配置,而不是寻找代码然后在一次次更改代码中的数据
- 查询操作中可以通过调用JdbcUtils.close(rs, stmt, conn)来关闭释放资源,而更新操作中可以通过调用JdbcUtils.close(stmt, conn)来释放资源
JDBCUtils.java代码示例:
?
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
//工具类,完成 mysql的连接和关闭资源
public class JDBCUtils {
//定义相关的属性(4个), 因为只需要一份,因此,我们做出static
private static String user; //用户名
private static String password; //密码
private static String url; //url
private static String driver; //驱动名
//在static代码块去初始化
static {
try {
// 1.创建Properties对象
Properties properties = new Properties();
// 2.创建并加载流到Properties中
properties.load(new FileInputStream("src\\mysql.properties"));
//3.读取相关的属性值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}finally{
//5.关闭资源
if(properties !=null){
try{
properties.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
//连接数据库, 返回Connection对象
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(Connection connection, PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, PreparedStatement ps, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(connection, ps);
}
}