JDBC
数据库的访问过程
1.建立连接
协议、ip、端口号、用户名、密码
2.客户端向MySQL服务器发送请求
即SQL语句
3.服务器接收到请求,执行语句,得到一个结果
4.服务器将结果返回给客户端
5.客户端接收到结果后将结果展示出来
6.执行完毕后断开连接
JDBC的定义
JDBC:Java Database Connection
Java数据库连接。Java语言需要访问多个数据库,但流程是一样的,于是就制定了一套统一的接口方便开发者使用,具体的驱动程序由各个数据库的厂商来提供
JDBC所有的接口都在 java.sql
以及 javax.sql
这两个包下面。
JDBC程序的创建
1.新建项目
2.导入驱动程序包
-
把驱动程序包下载下来https://mvnrepository.com/
jar包是java虚拟机可以识别的压缩格式,里面放的class文件
-
把文件放到项目的根目录下
-
右键添加为library
-
编写程序
public static void main(String[] args) throws SQLException{
//1.注册驱动
DriverManager.registerDriver(new Driver());
//2.获取连接 返回的是Connection接口的实现类
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/36th_rev?useSSL=false","root","123456");
//3.获取statement对象 这个对象是用来包装SQL语句为网络请求
Statement statement = connection.createStatement();
//4.通过statement对象发送SQL语句
int affectedRows = statement.executeUpdate("");
//5.解析结果集
//6.关闭资源
statement.close();
connection.close();
}
public static void main(String[] args) throw SQLException{
//1.注册驱动
DriverManager.registerDriver(new Driver());
//2.获取连接 返回的是Connection接口的实现类
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/36th_rev>useSSL=false","root","123456");
//3.获取statement对象
Statement statement = connection.createStatement();
//4.通过statement对象发送语句
ResultSet rs = statement.executeQuery("select * from student");
//5.解析结果集
while(resultSet.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String aClass = rs.getString("class");
int score = rs.getInt("score");
}
//6.关闭资源
rs.close();
statement.close();
connection.close();
}
API
DriverManager
用于注册驱动和获取连接
注册驱动:
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
获取连接:
Connection connection = DriverManager.getConnection(String url,String username,String password)
这里获取到的连接对象是
com.mysql.jdbc.JDBC4Connection
这个类的对象,对象是根据我们上面注册的驱动来变化的,所以在设计工具类的时候就可以使用配置文件
Connection
这个是JDBC中提供的一个接口,这个接口的实现类代表一个数据库连接。在MySQL的操作中,实现类是com.mysql.jdbc.JDBC4Connection
,通常我们使用连接来获取statement对象,用于控制事务
获取statement对象,返回的是StatementImpl,是Statement接口的实现类
Statement statement = connection.createStatement();
Statement
这个是一个JDBC中提供的一个接口,这个接口的实现类的具体作用是帮助我们把SQL语句包装成网络请求,发送给MySQL服务器
执行SQL语句
// 执行增删改的API,返回的是影响的行数
int affectedRows = statement.executeUpdate(String sql);
// 执行查询的API,返回的是查询语句执行完之后的结果集
ResultSet rs = statement.executeQuery(String sql);
ResultSet
表示结果集,使用API来解析结果集
有一个类似于迭代器的游标,初始的时候指向第一个数的前一个位置,然后向下迭代,返回值是boolean类型,与迭代器不同,无法使用for each
// 向后移动游标,如果ret是true,说明移动了,如果ret是false,表示这个游标已经移到了结果集的尾部,这个游标默认指向第一行之前
Boolean ret = resultSet.next();
// 移动到末尾
resultSet.afterLast();
// 往前移动
Boolean ret = resultSet.previous();
// 移动到第一行之前
resultSet.beforeFirst();
// 获取值 传入结果集的列名,根据字段的类型来选择对应的API,如果有别名,要使用别名
resultSet.getInt(String columnName);
resultSet.getString(String columnName);
resultSet.getDate(String columnName);
//循环调用
while(resultSet.next){
resultSet.getInt(String columnName);
resultSet.getString(String columnName);
resultSet.getDate(String columnName);
}
释放资源
finally{
//注意先后顺序,应该是resultSet、statement、connection
try{
resultSet.close();
}finally{
try{
statement.close();
}finally{
connection.close();
}
}
}
好处:
- 确保connection.close()能够被执行
- 严格保证关闭的执行顺序
指令重排:volatile
java在执行程序的时候如果代码的顺序并不影响执行的结果的话,那么有可能导致执行的顺序错乱
int i = 0;
int b = 1;
i++;
b++;
JDBC的优化
使用工具类来简化JDBC的使用
package day4.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils { //JDBC工具类,用于封装方法
static Connection connection;
static String url;
static String username;
static String password;
static String driverClassName;
static {
//静态代码块加载配置文件
Properties properties = new Properties();
try {
properties.load(new FileInputStream("jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
driverClassName = properties.getProperty("driverClassName");
try {
Class.forName(driverClassName); //注册驱动
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
connection = DriverManager.getConnection(url, username, password); //获取连接
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() { //外部调用的获取连接的方法
return connection;
}
//关闭资源
public static void closeResource(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} finally {
try {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
}
package day4.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class MyUtils { //用于增删改查的类
static Statement statement = null;
static Connection connection = JDBCUtils.getConnection();
public static void createData(String createSql){ //创建数据的方法
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
statement.executeUpdate(createSql);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static ResultSet selectData(String sql){ //用于查找数据,返回一个结果集
ResultSet rs = null;
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
rs = statement.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
public static void closeResource(){ //用于关闭资源
JDBCUtils.closeResource(null,statement,connection);
}
public static void closeResource(ResultSet resultSet){ //用于关闭资源的方法重载,提供给带有resultSet的使用
JDBCUtils.closeResource(resultSet,statement,connection);
}
}
数据库的注入问题
在用户登录的过程中,如果输入的SQL语句带有关键字的话,那么就有可能造成安全问题
login("","xxx' or '1=1");
如上,sql会判定or为关键字,那么1=1永远返回的是true,这样就会造成安全问题
解决办法:
- 不让用户自己输入(登录的案例不太适用)
- 审查用户的登录的时候传递过来的用户名和密码中是否有关键字
- 适用SQL语句预编译的方式来做,可以解决数据库的注入问题,PrepareStatement就可以解决数据库的注入问题
PrepareStatement
statement的一个子接口,可以帮助我们防止注入的问题
//获取prepareStatement对象,需要传入一个SQL语句的模板,使用?来占位
//执行这一步的时候会把SQL语句发送到服务器,然后服务器进行预编译,并返回一个对象
PreparedStatement preparedStatement = connection.prepareStatement("select * from user where name = ? and password = ?")
//设置参数,下标对应的是?,从1开始
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
//执行SQL语句,到这一步时需要再次与服务器进行通信,客户端将参数传给服务器后,服务器执行指令,返回一个结果集
ResultSet resultSet = preparedStatement.executeQuery();
批处理
批量执行SQL语句
循环
//循环来处理
public static void batchForeach() throws SQLException{
//获取statement对象
Statement statement = connection.createStatement;
for(int i = 0;i<1000;i++){
statement.executeUpdate("insert into user values(null,'带土','神威','莫毅几多')")
}
//关闭资源
JDBCUtils.closeResource(null,statement,null)//正常来说连接都要关闭,但这里的代码是因为要使用3个不同的批处理对比,但共用同一个连接,如果断开的话会报错,因此等待执行完毕一起关闭
}
statement
public static void batchByStatement() throws SQLException{
//获取连接
Connection connection = JDBCUtils.getConnection();
//获取Statement对象
Statement statement = connection.createStatement();
//添加到批处理
for(int i = 0;i<100;i++){
statement.addBatch("insert into user values(null,'naruto','螺旋丸','哒嘚吧哟')")
}
//执行批处理
statement.executeBatch();
//关闭资源
JDBCUtils.closeResource(null,statement,null);
}
PrepareStatement
需要在url后面添加参数rewriteBatchedStatements=true才能使其生效
public static void batchBypreparedStatement() throws SQLException{
//获取连接
Connection connection = JDBCUtils.getConnection();
//获取对象
PreparedStatement preparedStatement = connection.prepareStatement("insert into user values(null,?,?,?)");
for(int i = 0;i<100;i++){
preparedStatement.setString(1,'sasigi');
preparedStatement.setString(2,'千鸟');
preparedStatement.setString(3,'naruto');
//添加到批处理
preparedStatement.addBatch();
}
//执行批处理
preparedStatement.executeBatch();
//关闭资源
JDBCUtils.closeResource(null,preparedStatement,null);
}
服务器只需要预编译一次,然后每次我们输入不同的参数,服务器执行,这样效率大大的提高了
通信1000次的对比
通信次数 | 编译次数 | 执行时间 | |
---|---|---|---|
for循环 | 1000 | 1000 | 1213 ms |
statement | 1 | 1000 | 876 ms |
prePrepareStatement | 2 | 1 | 35 ms |
prePrepareStatement的速度显然最快,但statement不限定SQL语句的格式,在使用的时候要根据实际情况来进行考虑