JDBC概述
- JDBC的全称为:Java DataBase Connectivity(Java数据库连接);
- 早期没有JDBC的时候与各种数据库进行交互的模式;
- 现在有了JDBC之后与各种数据库进行交互的模式,Java程序员就不需要了解每一个数据库的工作模式,只需要了解JDBC接口的使用方法就可以了。
JDBC入门
- 搭建开发环境;
- 编写程序,在程序中加载数据库驱动;
- 建立连接;
- 创建用于向数据库发送SQL的Statement对象;
- 从代表结果集的ResultSet中取出数据;
- 断开与数据库的连接,并释放相关资源。
JDBC的API详解
DriverManager
驱动管理类;
主要作用:
注册驱动
DriverManager.registerDriver(new Driver()); //会导致驱动注册两次 //在源代码中已经有静态代码块注册一次驱动了 //实际注册驱动方式,这是实际开发使用的代码, //这个代码的意思是加载一个叫com.mysql.jdbc.Driver的类, //在加载类之后,静态代码块就被执行一次了 Class.forName("com.mysql.jdbc.Driver");
获得连接
Connection conn = DriverManager.getConnection(String url,String username,String password); //url写法: jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT //jdbc: 协议 //mysql: jdbc的子协议 //localhost: 主机名 //3306: 端口号 //jdbc: 数据库名 //serverTimezone=GMT: 时区设置 //如果连接的是本机的数据库,url简写: jdbc:mysql:///jdbc?serverTimezone=GMT
Connection
连接对象;
主要作用:
创建执行SQL语句的对象
//下面三个方法产生的对象都可以用来执行SQL语句 Statement createStatement() //用来执行SQL语句,会有SQL注入的漏洞存在 PreparedStatment prepareStatement(String sql) //预编译SQL语句,解决了SQL注入的漏洞 CallableStatement prepareCall(String sql) //执行SQL中的存储过程
进行事物的管理
setAutoCommit(boolean autoCommit) //设置事务是否自动提交 commit() //事务提交 rollback() //事务回滚
Statement
执行SQL;
主要作用:
执行SQL语句
boolean execute(String sql) //执行SQL //执行SELECT语句返回true,否则返回false ResultSet executeQuery(String sql) //只能执行SQL中的SELECT语句 //返回查询的结果 int executeUpate(String sql) //执行SQL中的insert/update/delete语句 //返回一个影响的行数
执行批处理操作
addBatch(String sql) //将操作添加到批处理中 executeBatch() //执行批处理 clearBatch() //清空批处理
ResultSet
结果集:查询(SELECT)语句所查询到的结果的封装;
主要作用:
结果集获取到查询的结果
next() //用来判断是否有下一行记录,如果有指向下一行 //针对不同类型的数据可以使用getXXX()的方法来获取数据 //通用获取数据的方法 getObject(String columnLabel) //参数是传入字符串类型值,表示字段名称
JDBC的资源释放
- JDBC程序运行完之后,一定要记得释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是
ResultSet
,Statement
和Connection
对象 Connection
往往是有限的,如果没有释放掉Connection
,那么被人有可能无法连接到数据库。Connection
对象是非常稀有的资源,用完之后必须马上释放,如果Connection
不能及时、正确地关闭,极易导致系统宕机。Connection
使用的原则是尽量晚创建,尽量早释放。
//一种比较好的释放资源的办法,需要将这一段放在finally代码块中
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
stmt = null; //这么写可以让垃圾回收机制更早地回收con对象
}
JDBC的CRUD操作
向数据库中保存记录
Class.forName("com.mysql.cj.jdbc.Driver"); //注册驱动
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT","root","vbe200"); //创建连接
stmt = conn.createStatement(); //创建Statement
String sql = "insert into user values (null,'zhbw','123','张三')"; //保存记录
int i = stmt.executeUpdate(sql); //执行保存操作
if(i>0){
System.out.println("保存成功!"); //获取结果
}
修改数据库中的记录
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200");
stmt = conn.createStatement();
String sql = "update user set username = 'qqq',password='456',name='赵六' where uid=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
System.out.println("修改成功!");
}
删除数据库中的记录
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200");
stmt = conn.createStatement();
String sql = "delete from user where uid=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
System.out.println("删除成功!");
}
查询数据库中的记录
查询所有记录
Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT","root","vbe200"); stmt = conn.createStatement(); String sql = "select * from user"; rs = stmt.executeQuery(sql); while(rs.next()){ int uid = rs.getInt("uid"); String username = rs.getString("username"); String password = rs.getString("password"); String name= rs.getString("name"); System.out.println(uid+" "+username+" "+password+" "+name); }
查询某一条特定的记录
Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200"); stmt = conn.createStatement(); String sql = "select * from user where uid=2"; rs = stmt.executeQuery(sql); if (rs.next()) { int uid = rs.getInt("uid"); String username = rs.getString("username"); String password = rs.getString("password"); String name = rs.getString("name"); System.out.println(uid + " " + username + " " + password + " " + name); }
JDBC工具类的抽取
- 由于很多代码都是重复的,所以为了简化JDBC开发,将一些重复的代码进行提取,创建一个工具类。
package io.github.zbw0520.jdbc.utils;
import java.sql.*;
/**
* JDBC的工具类
*
* @author zhbw
*/
public class JDBCUtils {
//静态常量
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
//静态代码块
static {
driverClass = "com.mysql.cj.jdbc.Driver";
url = "jdbc:mysql:///jdbctest?serverTimezone=GMT";
username = "root";
password = "vbe200";
}
/**
* 注册驱动的方法
*/
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverClass);
}
/**
* 获得连接的方法
*/
public static Connection getConnection() throws Exception {
loadDriver();
return DriverManager.getConnection(url,username,password);
}
/**
* 资源的释放
* 做增删改的方法
*/
public static void release(Statement stmt,Connection conn){
if(stmt !=null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
stmt = null;
}
if(conn !=null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
conn = null;
}
}
/**
* 资源的释放
* 做查询操作的方法
*/
public static void release(Statement stmt, Connection conn, ResultSet rs){
if(stmt !=null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
stmt = null;
}
if(conn !=null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
conn = null;
}
if(rs !=null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
rs = null;
}
}
}
- 使用工具类的保存记录的实例代码
@Test
public void demo(){
Statement stmt = null;
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
stmt = connection.createStatement();
String sql = "insert into user value(null,'qwq','123456','老王')";
int i = stmt.executeUpdate(sql);
if(i > 0){
System.out.println("保存成功");
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.release(stmt,connection);
}
}
- 注意,在之前的工具类文件中,我们将参数保存在源代码中,每一次需要修改参数的时候都需要修改源代码,这是不合理的,我们应该将参数放在源代码之外的一个文件中。我们选择将参数放在
jdbc.properties
这样一个属性文件中,文件中的键值对语法如下。
driverClass = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///jdbctest?serverTimezone=GMT
username = root
password = vbe200
- 这时候需要修改工具类源代码来实现读取属性文件中的参数值,我们可以使用如下代码实现这一操作。
static {
//加载属性文件并解析
Properties prop = new Properties();
//如何获得属性文件的输入流?
//通常情况下采用类的加载器的方式进行获取:
//通常不采用FileInputStream("src/jbdc.properties"),因为web应用没法适配
InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = prop.getProperty("driverClass");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
}
JDBC的SQL注入漏洞
只需要知道用户名,不需要知道密码就可以对数据库进行修改,这成为SQL注入漏洞。
产生原因:在输入用户名的时候输入了SQL的关键字,使其不能正常解析用户名和密码。
解决方案:不能使用Statement对象,必须要使用PreparedStatement类的对象,这种对象会把可以通过调用Connection.PreparedStatment(sql)的方法获得,相对于Statement对象而言:
PreparedStatment可以避免SQL注入问题;
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可以对SQL进行预编译,从而提高数据库的执行效率;
并且PreparedStatement对于SQL中的参数,允许使用占位符(
?
)的形式进行替换,简化SQL语句的编写。如下代码是一个使用PreparedStatement类对象的示例代码,下面这个代码块就可以解决漏洞问题。String username = "zhbw"; String password = "vbe200"; //编写SQL: String sql = "select * from user where username = ? and password = ?"; //预处理SQL: pstmt = conn.prepareStatement(sql); //设置参数: pstmt.setString(1,username); pstmt.setString(2.password); //执行SQL语句,因为已经进行预处理了,所以在这里就不需要传入语句参数了 pstmt.executeQuery();
JDBC中的PreparedStatement
数据库连接池C3P0
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库。C3P0是常用的开源连接池
手动设置连接池
//连接池需要引入两个jar包:
//c3p0-0.9.5.5.jar、mchange-commons-java-0.2.12.jar
//创建连接池:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置连接池相关参数:
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///jdbctest?serverTimezone=GMT");
dataSource.setUser("root");
dataSource.setPassword("vbe200");
dataSource.setMaxPoolSize(20);
dataSource.setInitialPoolSize(3);
//获得连接:
conn = dataSource.getConnection();
//编写SQL:
String sql = "select * from user";
//预编译SQL
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
使用文件设置连接池
- 可以使用XML文件来设置连接池,进一步简化代码的复杂程度。