一、JDBC是什么?二、JDBC开发前的准备工作三、JDBC编程六步四、使用powerDesigner工具进行建模(数据表设计)五、SQL注入六、JDBC事务自动提交机制七、JDBC工具类的封装八、JDBC实现模糊查询九、悲观锁和乐观锁
一、JDBC是什么?
JDBC(Java DataBase Connectivity,java语言连接数据库),是SUN公司制定的一套接口(interface),定义在java.sql.*这个包下面。
面向接口编程
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合(降低程序耦合度,提高程序的扩展力)
多态机制就是非常典型的面向抽象编程(不要面向具体编程)
e.g. 把类型、参数类型声明成父类,这样方法的通用程度更高,抽象程度也更高 建议: Animal a = new Cat(); Animal b = new Dog(); // 喂养方法 public void feed(Animal a) {} 不建议: Dog c = new Dog(); Cat d = new Cat();
思考:为什么SUN要制定一套JDBC接口呢?
因为每个数据库的底层实现原理都不一样,Oracle、MySQL、MS SqlServer...都要最近的实现原理。Sun定义了这套接口,各大数据库公司对这套接口分别负责实现,而我们Java程序员就可以统一调用这套接口,而不需要没一个数据库一套代码。
二、JDBC开发前的准备工作
- 从官网下载对应的驱动jar包;
- 如果是开发工具是文本编辑器,需要在环境变量classpath中添加jar包的路径;
如果是IDEA,在设置里面填写路径;
三、JDBC编程六步
- 注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
- 获取连接(表示JVM的进程和数据库进程之间的通道打开了,属于进程间通信,重量级的,使用完后一定要关闭)
- 获取数据库操作对象(专门执行sql语句的对象)
- 执行SQL语句(DQL DML...)
- 处理查询结果集(只有当第四步执行的是select语句的时候,才有第五步处理结果查询集)
- 释放资源(使用完资源后一定要关闭资源。Java和数据库属于进程间通信,开启后一定要关闭)
import java.sql.*; public class JDBCTest { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 1.注册驱动 // 方式一 // DriverManager.registerDriver(new com.mysql.jdbc.Driver()); // 方式二.常用该方式,调用后会加载com.mysql.jdbc.Driver类, // 加载过程中会执行类中的static代码块,代码块中就包含了方式一中的注册代码 Class.forName("com.mysql.jdbc.Driver"); // 2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/node", "root", "333"); // 3.获取数据库操作对象 stmt = conn.createStatement(); // 4.执行sql // int executeUpdate(insert/delete/update) 返回修改的记录数量 // ResultSet executeQuery(select) 返回结果集 rs = stmt.executeQuery("select empno, ename, sal from emp"); // 5.处理查询结果集 while(rs.next()) { String empno = rs.getString(1); String ename = rs.getString(2); String sal = rs.getString(3); System.out.println(empno + "," + ename + "," + sal); } } catch(SQLException e) { e.printStackTrace(); } finally { // 6.释放资源 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(); } } } } }
四、使用powerDesigner工具进行建模(数据表设计)
五、SQL注入
注入
抓住sql拼接的漏洞,在输入的loginPwd后添加
or '1'='1'
,那么拼接的sql就会被扭曲原意,查询无论如何都能成功,这样就能在没有密码的情况下登录成功,这种现象就是SQL注入。解决
数据库操作对象Statement改为使用其子类PreparedStatement(预编译的数据库操作对象,java.sql.PreparedStatement),PreparedStatement会预先对SQL语句的框架进行编译,然后再给SQL语句传值,这样即使用户提供的信息中包含了sql语句的关键字,但是这些关键字不参与编译,也就无法篡改SQL语义了。
Statement 和 PreparedStatement 对比
- Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
- Statement每次都要重新编译再执行,PreparedStatement编译一次后可执行N次。所以PreparedStatement效率较高一些。
- PreparedStatement在编译阶段会做类型安全检查。
小知识:连续执行两条一模一样的SQL语句时,第二次不需要DBMS编译就能执行
Statement 和 PreparedStatement 使用场景
- 当业务需要SQL语义拼接时,必须使用Statement,
e.g.1
升序或降序查看某个数据列表时,降序(desc)和升序(asc)需要修改SQL语句才行,只能用Statement
- 只需要对SQL语句传值时使用PreparedStatement
六、JDBC事务自动提交机制
JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。
但在实际的业务中,通常都是N条DML语句共同联合才能完成的,必须保证他们在同一个事务中同时成功或同时失败。
以下程序验证了JDBC的事务自动提交机制
e.g.1
分别修改两个部门的名称
该程序中执行完对deptno=30的部门修改代码,对应的数据库就被修改了,其次才执行对deptno=20的修改。在这个例子中两次修改没有一起执行完再修改数据库对业务并没有影响,下面举另一个例子展示自动提交机制对业务的影响。
e.g.2
转账业务
里面添加了三段关键代码
conn.setAutoCommit(false);// 创建连接conn后关闭conn的事务自动提交机制 conn.commit();//转账者和被转账者的值都被改后提交事务修改数据库 conn.rollback();//如果失败了要回滚数据
七、JDBC工具类的封装
工具类的构造方法都是私有的,因为工具类的方法一般都是静态的,不需要new对象,直接使用类名调用
public class JDBCUtil { // 工具类不需要new对象,构造方法私有化 private JDBCUtil(){} // 静态代码仅在类加载时执行且只执行一次 static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获取数据库连接对象 * @return 连接对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "333"); } /** * 关闭资源 * @param Connection conn 连接对象 * @param Statement stmt 数据库操作对象 * @param ResultSet rs 结果集 */ public static void close(Connection conn, Statement stmt, ResultSet rs) { 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(); } } } }
八、JDBC实现模糊查询
九、悲观锁和乐观锁
什么是行级锁?
查询emp表中job='MANAGER'的记录,如果在查询语句后面加上
for update
,即在事务结束之前添加for update
,那么其他的事务将不能对查询出来的这几条记录进行修改,这就是行级锁(即悲观锁)。悲观锁:事务必须排队执行,数据被锁住了,不允许并发。
乐观锁:支持并发,事务也不需要排队,但是会检查版本号,某个事务如果在提交时发现这条记录的版本号跟开始事务时一样,说明这段时间没有被其他事务修改,所以提交;否则回滚。比如,事务1、事务2同时修改一条表记录,开始时读取到的这条记录的版本号都是1.1,事务1快一点,执行完并把这条记录的版本号改成了1.2,这时候事务2还在执行,等到事务2提交的时候,发现版本号是1.2而不是开始时的1.1,那么事务2认为有其他事务修改了这条记录,那么不能继续操作,取消提交并回滚数据。