一、JDBC概述
1.1 数据库驱动
这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插到计算机上面是不能用的,必须要安装相应的驱动程序之后才能够使用声卡和网卡,同样道理,我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道,如下所示:
1.2 什么是JDBC
JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。
1.3 JDBC原理
早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!
JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
当然还有第三方公司专门为某一数据库提供驱动,这样的驱动往往不是开源免费的!
1.3 JDBC连接数据库
第一步:导入mysql数据库的驱动jar包(mysql-connector-java-5.1.13-bin.jar)
第二步:准备四大参数(driverClassName、url、username、password)
driverClassName:com.mysql.jdbc.Driver
url:URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
![](data:image/png;base64,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)
常用数据库URL地址的写法:
- Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
- SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
- MySql写法:jdbc:mysql://localhost:3306/sid
如果连接的是本地的Mysql数据库,并且连接使用的端口是3306,那么的url地址可以简写为: jdbc:mysql:///数据库
第三步:获取连接
- 加载驱动类:Class.forName("com.mysql.jdbc.Driver");
- 获取Connection对象:Connection con = DriverManager.getConnection(url, username, password);
二、四大核心类
2.1 DriverManager
JDBC程序中的DriverManager用于加载驱动,并创建与数据库的连接,这个API的常用方法:
- DriverManager.registerDriver(new Driver())
- DriverManager.getConnection(url, user, password),
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次(在我们执行DriverManager.registerDriver(new Driver()的时候,静态代码块也已经执行了,相当于是实例化了两个Driver对象),也就是在内存中会有两个Driver对象。
2、这种方式会出现硬编码:代码依赖mysql的api,脱离mysql的jar包,程序将无法编译,如果将来想连接Oracle数据库,那么必须要修改代码的。
推荐方式:Class.forName("com.mysql.jdbc.Driver");
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
查看com.mysql.jdbc.Driver.java:
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } …… }
从上边可以看到,它是用静态代码块实现的。
根据类加载机制,当执行 Class.forName(driverClass) 获取其Class对象时, com.mysql.jdbc.Driver 就会被JVM加载,连接,并进行初始化,初始化就会执行静态代码块,也就会执行下边这句代码:
java.sql.DriverManager.registerDriver(new Driver());
这就和第一种方式相同了。
事实上,所有java.sql.Driver的实现类,都提供了static块,块内的代码就是把自己注册到DriverManager中。
2.2 Connection
Jdbc程序中的Connection,它用于代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
- createStatement():创建向数据库发送sql的statement对象。
- prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
2.3 Statement
Jdbc程序中的Statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。 Statement对象常用方法:
- executeQuery(String sql) :用于向数据发送查询语句。
- executeUpdate(String sql):用于向数据库发送insert、update或delete语句
- execute(String sql):用于向数据库发送任意sql语句
- addBatch(String sql) :把多条sql语句放到一个批处理中。
- executeBatch():向数据库发送一批sql语句执行。
2.4 ResultSet
Jdbc程序中的ResultSet用于代表Sql语句的执行结果。
【ResultSet之滚动结果集】
Resultset封装执行结果时,采用的类似于二维表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前。ResultSet提供了一系列的方法来移动游标:
- boolean previous():把光标向上挪一行;
- boolean next():把光标向下挪一行;
- boolean absolute(int row):绝对位移,把光标移动到指定的行上;
- void beforeFirst():把光标放到第一行的前面,这也是光标默认的位置;
- void afterLast():把光标放到最后一行的后面;
- boolean isFirst():当前光标位置是否在第一行上;
- boolean isLast():当前光标位置是否在最后一行上;
【ResultSet之获取结果集元数据】
- 得到元数据:rs.getMetaData(),返回值为ResultSetMetaData;
- 获取结果集列数:int getColumnCount()
- 获取指定列的列名:String getColumnName(int colIndex)
【ResultSet之获取列数据】
可以通过next()方法使ResultSet的游标向下移动,当游标移动到你需要的行时,就需要来获取该行的数据了,ResultSet提供了一系列的获取列数据的方法:
获取任意类型的数据:
-
- Object getObject(int columnIndex):获取指定列的Object类型的数据。
- Object getObject(string columnName):获取名称为columnName的列的Object数据;
获取指定类型的数据:
-
- String getString(int columnIndex):获取指定列的String类型数据;
- String getString(String columnName):获取名称为columnName的列的String数据;
- int getInt(int columnIndex):获取指定列的int类型数据;
- int getInt(String columnName):获取名称为columnName的列的int数据;
注意:上面方法中,参数columnIndex表示列的索引,列索引从1开始,而不是0,这第一点与数组不同。
三、使用JDBC对数据库进行CRUD
3.1 对数据库做增、删、改
/** * 对数据库做增、删、改 */ @Test public void fun1() throws ClassNotFoundException, SQLException { // 准备四大参数 String driverClassName = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; // 加载驱动类 Class.forName(driverClassName); // 使用DriverManager,以及剩下的3个参数,得到Connection Connection connection = DriverManager.getConnection(url, username, password); // 通过Connection对象创建Statement Statement statement = connection.createStatement(); // 使用statement发送sql语句 String sql = "DELETE FROM t_user"; int i = statement.executeUpdate(sql); System.out.println(i); }
3.2 对数据库进行查询
/** * 执行查询 */ @Test public void fun2() throws ClassNotFoundException, SQLException { // 准备四大参数 String driverClassName = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; // 加载驱动类 Class.forName(driverClassName); // 使用DriverManager,以及剩下的3个参数,得到Connection Connection connection = DriverManager.getConnection(url, username, password); // 通过Connection对象创建Statement Statement statement = connection.createStatement(); // 调用statement的ResultSet rs = executeQuery(String querySql) ResultSet rs = statement.executeQuery("select * from t_account"); // 解析ResultSet // 把行光标移动到第一行,可以调用next()方法完成 while (rs.next()) { //把光标向下移动一行,并判断下一行是否存在 int id = rs.getInt(1);//通过列编号来获取该列的值 String name = rs.getString("name");//通过列名称来获取该列的值 double money = rs.getDouble("money"); System.out.println(id + "," + name + "," + money); } // 关闭资源(倒关) rs.close(); statement.close(); connection.close(); }
注意:Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象,特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
四、JdbcUtils工具类
你也看到了,连接数据库的四大参数是:驱动类、url、用户名,以及密码。这些参数都与特定数据库关联,如果将来想更改数据库,那么就要去修改这四大参数,那么为了不去修改代码,我们写一个JdbcUtils类,让它从配置文件中读取配置参数,然后创建连接对象。
JdbcUtils.java
public class JdbcUtils { private static final String dbconfig = "dbconfig.properties"; private static Properties prop = new Properties(); // 只在JdbcUtils类被加载时执行一次 static{ /* 把配置文件内容加载到prop对象中。 因为是放到static块中完成的加载操作, 所有加载操作只会在JdbcUtils类被加载时完成对配置文件的加载。 */ try { // 给prop进行初始化,即加载dbconfig.properties文件到prop对象中 InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(dbconfig); prop.load(inputStream); // 加载驱动类 Class.forName(prop.getProperty("driverClassName")); } catch (Exception e) { throw new RuntimeException(e); } } // 获取连接 public static Connection getConnection() { try { return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("username"), prop.getProperty("password")); } catch (SQLException e) { throw new RuntimeException(e); } } // 释放连接 public static void release(Connection connection, PreparedStatement pstmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
dbconfig.properties
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8 username=root password=root
五、PreparedStatement
5.1 什么是SQL攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是为SQL语句的片段!下面我们通过一个案例来演示SQL攻击。
首先我们需要创建一张用户表,用来存储用户的信息。
CREATE TABLE user( uid CHAR(32) PRIMARY KEY, username VARCHAR(30) UNIQUE KEY NOT NULL, PASSWORD VARCHAR(30) ); INSERT INTO user VALUES('U_1001', 'zs', 'zs'); SELECT * FROM user;
现在用户表中只有一行记录,就是zs。
下面我们写一个login()方法!
public void login(String username, String password) { Connection con = null; Statement stmt = null; ResultSet rs = null; try { con = JdbcUtils.getConnection(); stmt = con.createStatement(); String sql = "SELECT * FROM user WHERE " + "username='" + username + "' and password='" + password + "'"; rs = stmt.executeQuery(sql); if(rs.next()) { System.out.println("欢迎" + rs.getString("username")); } else { System.out.println("用户名或密码错误!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { JdbcUtils.close(con, stmt, rs); } }
下面是调用这个方法的代码:
login("a' or 'a'='a", "a' or 'a'='a");
这行当前会使我们登录成功!因为是输入的用户名和密码是SQL语句片段,最终与我们的login()方法中的SQL语句组合在一起!我们来看看组合在一起的SQL语句:
SELECT * FROM tab_user WHERE username='a' or 'a'='a' and password='a' or 'a'='a'
知道了SQL攻击的原因后,我们可以通过一下方法来防止SQL攻击:
- 过滤用户输入的数据中是否包含非法字符;
- 分步交验!先使用用户名来查询用户,如果查找到了,再比较密码;
- 使用PreparedStatement。
5.2 PreparedStatement是什么
PreparedStatement叫预编译声明。PreparedStatement是Statement的子接口,我们可以使用PreparedStatement来替换Statement。PreparedStatement的实例对象可以通过调用Connection.preparedStatement()方法获得。相对于Statement对象而言,PreperedStatement有如下好处:
- 防止SQL攻击;
- 提高代码的可读性,以可维护性;
- 提高效率。
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
5.3 PreparedStatement的使用
第一步:给出sql模板:所有的参数使用?来替代
第二步:调用Connection的PreparedStatement prepareStatement(String sql模板),即创建它时就让它与一条SQL模板绑定;
第三步:调用PreparedStatement的setXXX()系列方法为问号设置值
第四步:调用executeUpdate()或executeQuery()方法,但要注意,调用没有参数的方法;
/** * 通过PreparedStatement对象完成对数据库的CRUD操作 */ @Test public void insert() { Connection connection = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 获取一个数据库连接 connection = JdbcUtils.getConnection(); // 要执行的SQL命令,SQL中的参数使用?作为占位符 String sql = "insert into t_user(id,name) values(?,?)"; // 通过connection对象获取负责执行SQL命令的prepareStatement对象 pstmt = connection.prepareStatement(sql); // 为SQL语句中的参数赋值,注意,索引是从1开始的 pstmt.setInt(1, 1); //id是int类型的 pstmt.setString(2, "张三"); //name是varchar(字符串类型) // 执行插入操作,executeUpdate方法返回成功的条数 int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("插入成功"); } }catch (Exception e){ e.printStackTrace(); }finally { // SQL执行完成之后释放相关资源 JdbcUtils.release(connection, pstmt, rs); } } @Test public void delete() { Connection connection = null; PreparedStatement pstmt = null; ResultSet rs = null; try { connection = JdbcUtils.getConnection(); String sql = "delete from t_user where id=?"; pstmt = connection.prepareStatement(sql); pstmt.setInt(1, 1); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("删除成功"); } } catch (SQLException e) { e.printStackTrace(); }finally { JdbcUtils.release(connection, pstmt, rs); } } @Test public void update() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "update t_user set name=? where id= ?"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, "李四"); pstmt.setInt(2, 1); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("更新成功"); } } catch (SQLException e) { e.printStackTrace(); }finally { JdbcUtils.release(conn, pstmt, rs); } } @Test public void find() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from t_user where id=?"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 1); rs = pstmt.executeQuery(); if (rs.next()) { System.out.println(rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); }finally { JdbcUtils.release(conn, pstmt, rs); } }
注意:PreparedStatement对象独有的executeQuery()方法是没有参数的,而Statement的executeQuery()是需要参数(SQL语句)的。因为在创建PreparedStatement对象时已经让它与一条SQL模板绑定在一起了,所以在调用它的executeQuery()和executeUpdate()方法时就不再需要参数了。
PreparedStatement最大的好处就是在于重复使用同一模板,给予其不同的参数来重复的使用它。这才是真正提高效率的原因。
所以,建议在今后的开发中,无论什么情况,都去需要PreparedStatement,而不是使用Statement。
5.4 预处理的原理
【服务器(数据库)的工作】
- 校验sql语句的语法!
- 编译:一个与函数相似的东西!
- 执行:调用函数
【PreparedStatement】
使用前提:连接的数据库必须支持预处理!几乎没有不支持的!
特性:每个pstmt都与一个sql模板绑定在一起,先把sql模板给数据库,数据库先进行校验,再进行编译。执行时只是把参数传递过去而已!若二次执行时,就不用再次校验语法,也不用再次编译!直接执行!
参考:https://www.cnblogs.com/xdp-gacl/p/3946207.html