JDBC简介
一、JDBC简介
JDBC(Java Database Connectivity, java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 java 语言编写的类和接口组成。JDBC 提供了一个种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,同时,JDBC 也是个商标名。
jdbc的本质:
jdbc是sun公司制定的一套接口(interface)
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程呢?
解耦合:降低程序的耦合度,提高程序的扩展力。
思考:为什么SUN制定一套JDBc接口呢?
因为每一个数据库的底层实现原理都不一样。oracle数据库有自己的原理。
MysQL数据库也有自己的原理。
Ms sqlserver数据库也有自己的原理。
每一个数据库产品都有自己独特的实现原理。
二、JDBC 的开发步骤
1)注册驱动:主要告诉 JVM 我们的程序将要使用哪一种数据库
注册驱动的方法:1.DriverManager.registerDriver(new com.mysql.jdbc.Driver());
2.利用反射机制(常用):Class.forName(“com.mysql.jdbc.Driver”);
常用原因:参数是一个字符串,字符串可以写到xxxx.properties文件中
这个方法不需要返回值,因为我们只想用他的类加载动作
2)获取连接:使用 JDBC 中的类,获得数据库的连接对象 Connection
要知道数据库URL,用户名,密码
URL:包括协议,IP地址,端口,资源名 http://39.156.66.14/index.html
就像mysql的URL一样 url=jdbc:mysql://127.0.0.1:3306/database
3)获取数据库 *** 作对象:通过 Connection 可以获取执行者对象,Statement、PreparedStatement.
4)执行 SQL 语句:使用执行者对象,向数据库中执行 SQL 语句,然后可以得到对应的接口,有单个结果,也可能有结果集 ResultSet。
5)处理查询结果集(只有当第四步执行的是select语句的时候,才有这一步)
6)释放对象:关闭顺序:rs -> stmt 、ptmt -> conn
关闭顺序要遵循从小到大依次释放,分别对其try…catch
package com.jdbc; import java.sql.*; public class JDBCTest03 { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.连接数据库 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/user","root","root"); //3.获取数据库 *** 作对象 stmt = conn.createStatement(); //4.执行SQL语句 String sql = "select username,age,password from user.user"; rs = stmt.executeQuery(sql); //5.处理查询结果集 //Boolean flag = rs.next(); while (rs.next() ){ //next()方法的特点:如果有数据则返回true 没数据就返回false //光标指向的行有数据 //取数据 //getString的特点是:不管数据库中的数据是什么类型的,都以String类型取出 String username1 =rs.getNString("username");//得到的类型也可以是多样的,如getInt,getDouble等 Int age1 = rs.getInt("age"); Double pwd = rs.getDouble("password"); System.out.println(username1+","+age1+","+pwd); } }catch(Exception e){ e.printStackTrace(); }finally { //6.释放资源 从小到大释放 在final语句中写 确保能执行 //1.先释放查询结果集 2. 释放数据库 *** 作对象 3. 释放连接 if (rs !=null){ try{ rs.close(); }catch (Exception e){ e.printStackTrace(); } if (stmt != null){ try{ stmt.close(); }catch (Exception e){ e.printStackTrace(); } } if (conn != null){ try{ conn.close(); }catch (Exception e){ e.printStackTrace(); } } } } } }
解决SQL注入问题:
只要用户提供的信息部参与SQL语句的编译过程,问题就得到解决了。
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。
想要用户信息部参与SQL语句的编译,那么就必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.statement
PreparedStatement是属于预编译的数据库 *** 作对象。
PreparedStatement 的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值。
解决SQL注入的关键是:即使输入的信息中含有SQL语句中的关键字,但是这些关键字并没有参与编译,不起作用。
使用PreparedStatement防注入
package com.jdbc; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class JDBCTest05 { public static void main(String[] args) { //1.初始化一个界面 MapuserInfo = initUI(); //2.验证用户名和密码 boolean loginSuccess = login(userInfo); System.out.println(loginSuccess ? "登录成功":"登录失败"); } private static boolean login(Map userInfo) { //设置一个标记值 Boolean loginSuccess = false; //String name = userInfo.get("name"); //String psw = userInfo.get("psw"); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //1.注册数据库驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接 conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/user","root","root"); // SQL语句的框子,其中一个?代表一个占位符,一个?将来会接收一个‘值’。注意占位符不能用单引号括起来,别人会被认为是一个字符串 //3.编写SQL语句 String sql = "select * from t_user where loginName = ? and loginPwd = ?"; //4.创建数据库 *** 作对象 ps = conn.prepareStatement(sql); ps.setString(1,userInfo.get("name"));//因为设置的值是String类型的,所以在什么SQL语句中会自动添加单引号 ps.setString(2,userInfo.get("psw")); //5.执行SQL语句 注意要传完值之后再执行SQL语句 rs = ps.executeQuery(); //6.释放资源 if (rs.next()){ loginSuccess = true; } } catch (Exception e) { e.printStackTrace(); } if (rs!=null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps!=null){ try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } return loginSuccess; } private static Map initUI() { Scanner s = new Scanner(System.in); System.out.println("请输入用户名:"); String name = s.nextLine(); System.out.println("请输入密码:"); String psw = s.nextLine(); Map userInfo = new HashMap<>(); userInfo.put("name",name); userInfo.put("psw",psw); return userInfo; } }
对比Statement和PreparedStatement
1.Statement存在SQL注入问题。PreparedStatement解决了SQL注入问题
2.Statement执行效率低,编译一次执行一次 。PreparedStatement是编译一次课执行n次。因为执行SQL语句的特点,如果第二次执行相同的SQL语句,会直接执行,而Statement的语句每次都不一样,PreparedStatement的SQL语句是用占位符的,每次执行都一样。
3.PreparedStatement会在编译阶段进行安全检查。
综上所述:大多的情况下都使用PreparedStatement,只有极少数情况下用statement
5.使用statement的情况:业务要求要进行SQL注入的情况下才用。例如商品的下拉框的选项,升序或降序的情况下用Statement进行SQL注入
例如
package com.jdbc; import java.sql.*; import java.util.Scanner; public class JDBCTest06desc { public static void main(String[] args) { Scanner s = new Scanner(System.in); System.out.println("请输入升序desc或降序asc:"); String kw = s.nextLine(); Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //1.注册连接驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/user","root","root"); //3.获取数据库 *** 作对象 stmt = conn.createStatement(); //4.执行SQL 这样写SQL会有SQL注入问题 如在输入用户名时输入abc' or '1' = 1' String sql = "select loginName from t_user order by loginName " + kw; //5.处理结果集 rs = stmt.executeQuery(sql); while (rs.next()) { System.out.println(rs.getString("loginName")); } } catch (Exception e) { e.printStackTrace(); }finally { //6.关闭资源 if( rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if ( stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
三、JDBC事务
1.JDBC中的事务是自动提交的
自动提交:只要执行任意一条DML语句。则自动提交一次。这是JDBC默认的事务行为。但在实际的业务中,通常都是n条DML语句共同联合才能完成的,必须保证他们这些DML语句在通过一个事务中同时成功或同时失败。
conn.setAutoCommit(false);//开启事务 关闭 JDBC的自动提交事务 在获取数据库连接之后开启 //在事务结束时 提交事务 conn.commit();//提交事务 if(conn !=null) try { conn.rollback();//事务回滚 遇到异常就回滚 } catch (SQLException throwables) { throwables.printStackTrace(); } e.printStackTrace(); }
ACID
原子性:要么同时成功,要么同时失败
一致性:结果总数不变。就像转账,转账之前和转账之后的总数不变
持久性:一旦sql执行就不可更改
隔离性:事务间互不影响
会出现三种问题 脏读:一个事务读到了另一个没有提交的事务 不可重复读:在同一个事务中多次读取同一条记录,读出的结果不同 虚读( 幻读):在一个事务中读到了别的事务中插入的数据,导致前后读出的结果不一致
四、悲观锁和乐观锁
行级锁: 在SQL语句后面加 for update 查出来的数据就锁住了,不能修改 又称为悲观锁
事务1—>读取到版本号1.1
事务2—>读取到事务1.1
其中事务1先修改了,修改之后看了版本号是1.1,于是提交修改的数据,并将版本号改成1.2
其中事务2后修改的,修改之后准备提交的时候,发现版本号是1.2,和它最初读的版本号不一致,于是就回滚。
悲观锁特点:事务必须排队执行。数据锁住了,不允许并发。
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)