目录
一、前置准备
二、JDBC连接数据库
1. 方式一
2. 方式二
3. 方式三
4. 方式四
5. 方式五(推荐)
三、ResultSet结果集
1. 获取结果集ResultSet的步骤
2. 如何获取到ResultSet结果集
3. ResultSet 底层结构
四、SQL注入问题
五、Preparment(预处理)
1. 步骤
2. 案例演示
3. Statement 和 PreparedStatement的使用对比
六、JDBC相关的API
七、工具类的编写
八、事务
1. 概念
2. 使用场景
3. 事务中需要使用到的API
4. 事务使用实例
九、批处理
1. 批处理用到的API
2. addBatch源码分析
十、总结
相关文章
Mysql | Mysql |
一、前置准备
在正式开始学习
JDBC
前,我需要去Mysql
官网下载JDBC的驱动包。我这里Mysql版本是8.0
,所以驱动版本也要与其对应。
📥Mysql驱动下载地址
二、JDBC连接数据库
1. 连接步骤
JDBC
连接数据库步骤:👇
2. JDBC的几种连接方式
2.1 方式一
// 注册驱动
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
Properties properties = new Properties();
properties.setProperty("user", "用户名");
properties.setProperty("password", "密码");
// 得到连接
Connection connect = driver.connect(url, properties);
👆因为我们
驱动是8.0版本
,所以url
后边加了时区serverTimezone=UTC
。
2.2 方式二
// 注册驱动
Class aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
Properties properties = new Properties();
properties.setProperty("user", "用户名");
properties.setProperty("password","密码");
// 得到连接
Connection connect = driver.connect(url, properties);
👆这种方式是通过
反射
来创建Driver
驱动的。8.0版本
对应的驱动类全类名是:com.mysql.cj.jdbc.Driver
。5.7版本
对应的全类名是com.mysql.jdbc.Driver
。
2.3 方式三
// 注册驱动
Class aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
// 通过DriverManager注册驱动
DriverManager.registerDriver(driver);
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
String user = "用户名";
String password = "密码";
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
👆这种方式没有创建
properties
对象,而且最后是使用DriverManager
来获取Connection
连接的。
2.4 方式四
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
String user = "用户名";
String password = "密码";
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
👆这种连接方式和第三种连接方式相比省略了些代码。但这种方式仍然可以获取到
Connection
连接。
至于为什么不需要那些省略的代码,也可以获取连接的原因如下:👇
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
} // 底层在进行类加载的时候就进行了 驱动的注册。
💡补充:
mysqL驱动5.1.6
可以无需Class.forName(“com.mysql.jdbc.Driver”)。从jdk1.5以后使用了jdbc4,不再需要显式调用class.forName()注册驱动
而是自动调用驱动jar包下META-INF.sqI.Driver文本中的类名称去注册(驱动8.0版本往后类名都是com.mysql.cj.jdbc.Driver)。但建议还是写上 Class.forName(“com.mysql.jdbc.Driver”),更加明确。
2.5 方式五(推荐)
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName(driver);
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
👆上边这种方式我们可以读取
properties
文件中配置的驱动信息
、用户名
、密码
等信息。可以很方便的通过修改配置文件
来达到连接不同数据库的目的,更加的灵活。
properties
配置文件:👇
#Mysql
driverClassNam=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/goodsSystem?serverTimezone=UTC
username=用户名
password=密码
三、ResultSet结果集
通过上边的知识了解了
如何连接到数据库
,接下来我们继续讲解,如何从数据库
查询到数据。
1. 获取结果集ResultSet的步骤
2. 如何获取到ResultSet结果集
📌演示
@Test
public void jdbc07() throws IOException, ClassNotFoundException, SQLException {
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
// 1.注册驱动
Class.forName(driver);
// 2.获得驱动
Connection connection = DriverManager.getConnection(url, user, password);
String sql = "select * from `news`";
// 3.得到Statement对象
Statement statement = connection.createStatement();
// 4.获得ResultSet结果集
ResultSet resultSet = statement.executeQuery(sql); //通过statement对象调用executeQuery方法,执行sql语句,返回一个ResultSet对象。
// 5.使用while循环取出ResultSet中的数据
while (resultSet.next()){ //相当于一个指针,开始指向整个结果集第一行数据之前,每调用一次就向后移动一次,若没有数据就返回false。
int id = resultSet.getInt(1); //getxxx()方法中的参数填的是列数,就是当前行的第几列数据,返回值就是对应位置的数据。
String name = resultSet.getString(2);
String news = resultSet.getString(3);
System.out.println(id + "\t" + name + "\t" + news);
}
// 6.关闭连接
connection.close();
statement.close();
resultSet.close();
}
3. ResultSet底层结构
ResultSet对象
里面有一个rowData
。数据实际在elementData
中,因为查询的结果只有两行,所以elementData数组
中只有两个,它们是以字节数组
的形式出现的。其中internalRowData
中出现了三个字节数组,则代表有三列。
四、SQL注入问题
有关SQL注入问题,我们通过下边的代码来说明:👇
public class testStatement{
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名"); // 1' or
String userName = scanner.nextLine(); // or '1'= '1
System.out.print("请输入密码");
String pwd = scanner.nextLine();
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
System.out.println(connection);
String sql = "select id, pwd from `actor` where id = '"
+ userName + "' and pwd = '"
+ pwd +"'" ;
ResultSet resultSet = statement.executeQuery(sql);
if (resultSet.next()){
System.out.println("成功");
}else {
System.out.println("失败");
}
connection.close();
statement.close();
resultSet.close();
}
}
📌actor
表
id | pwd |
---|---|
tom | 123 |
上边的代码,我们通过查询
actor
表来获取结果,可以看到,根据我们写的SQL语句
只有满足 id=tom 并且 pwd=123 条件的时候,他才会返回结果,并且输出成功。但由于存在SQL注入
的问题,这里我们输入id为1' or
,pwd为or '1'= '1
的时候也会返回结果,而且也输出了成功。当然出现SQL注入
的情况有很多种,但我们这里只研究引起我们这个程序出现问题的根源:这就是因为使用了Statement
,会有SQL注入
的风险。而我们想要解决这种问题就得引出我们下边要将的知识点:Preparment
。
五、Preparment(预处理)
🎈在此之前我们和前边一样,先给出使用Preparment
获取结果集的步骤。
1. 步骤
2. 案例演示
public class testPreparStatement {
public static void main(String[] args) throws Exception{
String pwd = scanner.nextLine();
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
// 1. 注册驱动
Class.forName(driver);
// 2. 获得连接
Connection connection = DriverManager.getConnection(url, user, password);
// 3. 组织sql
String sql = "select id , pwd from `actor` where id =? and pwd=?";
// 4. 得到PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5. 配置参数(给?赋值)
preparedStatement.setString(1,"值");
preparedStatement.setString(2,"值");
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()){
System.out.println("success");
}else {
System.out.println("faile");
}
// 6. 关闭连接
connection.close();
preparedStatement.close();
resultSet.close();
}
}
?
为占位符,用来代表不确定的值。PreparedStatement
中的setXxx方法
中的参数,第一个参数是取代第几个?
,第二个参数是具体的值。
3. Statement 和 PreparedStatement 的使用对比
🚫注意:
和以前举的例子不同之处在于,通过
connection
获得PreparedStatement
对象的时候,就需要填入参数(sql语句),而不是在执行executeQuery
的时候写参数(sql语句),如果此时写上sql则会报错。除非没有?占位符
。
六、JDBC相关的API
七、工具类的编写
通常我们会将
重复操作
、相同的逻辑代码
抽取出来,封装成一个类,方便我们来使用。这里我们封装一个获取Connection
和关闭Connection、关闭ResultSet、关闭Statement
的工具类。
import javax.management.RuntimeMBeanException;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author long
* @date 2022/11/7
*/
public class JdbcUtil {
private static String url;
private static String user;
private static String password;
private static String driver;
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\DateBase_select\\mysql.properties"));
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
} catch (IOException e) {
throw new RuntimeException();
}
}
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException throwables) {
throw new RuntimeException();
}
}
public static void close(ResultSet resultSet,Statement statement, Connection connection){
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException throwables) {
throw new RuntimeException();
}
}
public static void close(Statement statement, Connection connection){
try {
statement.close();
connection.close();
} catch (SQLException throwables) {
throw new RuntimeException();
}
}
}
👆工具类中关于
异常
,我们的处理方式是向调用者抛出一个运行时异常
,这种做法可以配合我们业务中的一些逻辑来完成一些东西(例如:配合ThreadLocal
来捕获这个异常来进行事务的回滚)。
细心的小伙伴还会发现一个地方,工具类close()
的参数中有一个Statement
,这里用Statement
而不用PreparedStatement
是因为:Statement
是PreparedStatement
的父接口
,Statement
可以兼容这两种不同的对象。
八、事务
1.概念
什么是事务❓
事务
是指单个逻辑工作单元执行的一系列操作,要么都做,要么都不做,是不可分割的工作单位,是数据库环境中的的最小工作单元
。
2. 使用场景
银行转账,甲给乙转账。
正常情况==>甲账户减去100,乙账户加上100。
异常情况==>甲账户减去100,中途出现异常,乙账户未加上100。
解决方案
:使用事务
处理。事务可以保证整个流程走完之后,才将最终的结果提交给数据库。
3. 事务中需要使用到的API
💡上述方法的一些说明
connection.setAutoCommit(false);
// 取消事务的自动提交connection.rollback();
// 事务回滚
参数为空的时候,回滚到事务的开始。如果有参数的时候,返回到回滚点。connection.commit();
// 提交结果给数据库
4. 事务使用实例
public class Transaction {
public static void main(String[] args) {
Connection connection = null;
String sql1 = "update `actor` set salary = salary + 100 where `name` = '小王'";
String sql2 = "update `actor` set salary = salary - 100 where `name` = '小李'";
PreparedStatement preparedStatement = null;
try {
connection = JdbcUtil.getConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
int i = 1/0;
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
connection.commit();
} catch (SQLException throwables) {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
}finally {
JdbcUtil.close(preparedStatement,connection);
}
}
}
👆当我们的语句执行到
int i=1/0;
的时候,会抛出一个异常
,try-catch
捕获到异常之后,我们就会执行connection.rollback();
来进行事务的回滚
。
九、批处理
使用批处理前,我们必须在URL
的后边加上rewriteBatchedStatements=true
批处理
顾名思义就是批量处理,就相当于一个公交车拉乘客,每条sql就是一个乘客,而批量包处理
就是公交车,等公交车上人坐满了发车,肯定效率高于一次拉一个乘客效率来的高。
1. 批处理用到的API
💡说明:
preparedStatement.addBatch();
要在设置完SQL
语句之后执行。- 在执行完
批处理包
之后,需要清空
。
2. addBatch源码分析
📌addBatch源码
public void addBatch() throws SQLException {
try {
synchronized(this.checkClosed().getConnectionMutex()) {
QueryBindings<?> queryBindings = ((PreparedQuery)this.query).getQueryBindings();
queryBindings.checkAllParametersSet();
this.query.addBatch(queryBindings.clone()); // 此处进入addBatch
}
} catch (CJException var6) {
throw SQLExceptionsMapping.translateException(var6, this.getExceptionInterceptor());
}
}
📌this.query.addBatch源码
// this.query.addBatch 源码
public void addBatch(Object batch) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList(); //判断batchedArgs是否为空,若为分配ArrayList()数组
}
this.batchedArgs.add(batch); //添加sql数据进入到批处理包。
}
批处理包
中每条sql数据实际的存储位置:preparedStatement➡query➡batchedArgs(ArrayList数组)➡elementData(对象数组)➡bindValues➡values(字节数组)。在values
中以ASCLL码
形式存储。如下图:👇
👆上图
bindValues数组
中只有两个值,这是因为我们在设置sql语句的时候使用了两个?占位符
。这里也可以看出value字节数组
中的首尾都为 39,而 39 对应的ASCLL编码为'(单引号),
我这里使用的是语句是preparedStatement.setString(1,"jack"+0)
;。从这可以看出他把给String语句自动
加上了单引号。
ArrayList数组初始空间为10,扩容为容量的1.5倍。
十、总结
以上就是我们这次要将的全部内容。从
JDBC
驱动下载,到日常需要使用到的常用操作。有关数据库连接池
的内容,我们下节接着讲!🚩
最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏