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();
批处理
循环
//循环来处理 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语句的格式,在使用的时候要根据实际情况来进行考虑