1、什么是数据库驱动
通常一台电脑上都会有显卡驱动,声卡驱动,而驱动的作用是使应用程序与硬件匹配和连接。数据库也是如此,不同的数据库厂商有各自的数据库驱动去连接其数据库。
2、什么是 JDBC
在一个程序中,如果需要连接多个数据库,甚至多个不同厂商的数据库,就需要编写多个数据库驱动的程序,这显然不符合需求。于是,JDBC 就出现了。
JDBC 是 SUN 公司为了简化开发人员对数据库的统一操作,提供的一个 Java 操作数据库的规范,这些规范的实现由具体的厂商去做。对于开发人员,只需要掌握 JDBC 接口的操作即可。
JDBC:Java Database Connectivity。
3、Hello JDBC
3.1、第一个JDBC程序
①数据库准备
CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;
USE jdbcStudy;
CREATE TABLE `users`(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);
INSERT INTO `users`(id,NAME,PASSWORD,email,birthday)
VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'),
(2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04');
②导入依赖
架包下载地址:https://dev.mysql.com/downloads/connector/j/
mysql-conneter-java
③编写代码
// 我的第一个JDBC程序
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.地址和用户信息
// useUnicode=true&characterEncoding=utf8&useSSL=true
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username = "root";
String password = "123456";
// 3.创建连接,返回数据库对象
Connection connection = DriverManager.getConnection(url, username, password);
// 4.执行SQL语句的对象,statement
Statement statement = connection.createStatement();
// 5.执行SQL语句,返回结果集
String sql = "SELECT * FROM users";
ResultSet resultSet = statement.executeQuery(sql); // 结果集,封装了全部的查询出来的结果
while(resultSet.next()){
System.out.print("id=" + resultSet.getObject("id"));
System.out.print("name=" + resultSet.getObject("NAME"));
System.out.print("pwd=" + resultSet.getObject("PASSWORD"));
System.out.print("email=" + resultSet.getObject("email"));
System.out.print("birth=" + resultSet.getObject("birthday"));
System.out.println();
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
折腾一个晚上的错误总结:
- 使用老版5.1 ,如果报错 ,改成Url后面那里改成useSSL=false;
2.使用8.0新版,加载驱动那改成Class.forName("com.mysql.cj.jdbc.Driver"); ,然后url后面加一串&serverTimezone=Asia/Shanghai
④步骤总结
- 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
固定语句
- 连接数据库
DriverManager.getConnection()
- 获取执行sql的对象statement
createStatement()
- 获取返回结果(只有查询语句有返回结果)
executeQuery
- 释放连接
⑤对象解释
DriverManager
// 通过反射技术加载驱动,建议使用
Class.forName("com.mysql.jdbc.Driver");
// 加载驱动的原生写法,不建议使用
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
查看 Driver 类的源码可以发现在静态代码块中已经注册了驱动,所以可以通过反射技术直接获取,没有必要重新注册。
Connection connection = DriverManager.getConnection(url, username, password);
// connection代表数据库对象,数据库的一些操作都可以通过对象.方法执行
// 事务回滚
connection.rollback();
// 事务提交
connection.commit();
// 数据库设置自动提交
connection.setAutoCommit();
url
// 协议://主机地址:端口号/数据库名?参数1&参数2&参数3
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
statement/PreparaStatement(执行sql的对象)
String sql = "SELECT * FROM users"; // 编写SQL语句
statement.executeQuery(); // 查询操作,返回结果集
statement.execute(); // 执行任何SQL语句
statement.executeUpdate(); // 更新、插入、删除,返回受影响的行数
statement.executeBatch(); //可以执行多条sql语句
resultSet (查询的结果集:封装了所有的查询结果)
ResultSet resultSet = statement.executeQuery(sql);
resultSet.getObject();// 在不知道类型的情况下使用
// 如果知道类的类型就使用指定的类型
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();
// 遍历查看返回的结果集
resultSet.beforeFirst(); // 移动到最前面
resultSet.afterLast(); // 移动到最后面
resultSet.next(); // 移动到下一个数据
resultSet.previous(); // 移动到前一行
resultSet.absolute(row); // 移动到指定行
释放资源
resultSet.close();
statement.close();
connection.close();
4、Statement对象
JDBC中的statement对象用于想数据库发送sql语句,想要完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
Statement
对象的executeIpdate
方法,用于向数据库发送增、删、改的sql语句,executeUpdate
执行完后,将会返回一个整数(即增删改查语句导致数据库几行数据发生了变化)
Statement.executeQuery
方法用于向数据库发送查询语句,executeQuery
方法返回代表查询结果的ResultSet对象。
以添加数据为例
Statement st = conn.createStatement();
String sql = "insert into user(...) values(...)"
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("插入成功...")
}
5、CRUD
5.1、提取工具类
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
JdbcUtiles
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try{
// 读取配置文件的信息
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
// 将配置信息加载到配置类中
Properties properties = new Properties();
properties.load(in);
// 获取具体配置信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
// 加载驱动
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
class.getClassLoader().getResourceAsStream(String name) 默认从classpath中找文件,name不能带“/”,否则会抛空指针 与class.getResourceAsStream(String name)不同
5.2、insert
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();// 获取数据库连接
st = conn.createStatement();// 获得SQL的执行对象
String sql = "INSERT INTO users(id,`NAME`,`PASSWORD`,`email`,`birthday`)" +
"VALUES(4,'xiaopeng','123456','334405060@qq.com','2020-01-01')";
int i = st.executeUpdate(sql);
if(i > 0){
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn,st,rs);
}
}
}
5.3、delete
public class TestDelete {
...
String sql = "DELETE FROM users WHERE id = 4";
...
}
5.4、update
public class TestUpdate {
...
String sql = "update users set `NAME`='xiaopeng',`email`='334405060@qq.com' where id=1";
...
}
5.5、select
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "SELECT * FROM users where id = 1";
rs = st.executeQuery(sql);// 查询完毕会返回一个结果集
while(rs.next()){
System.out.println(rs.getString("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
6、SQL 注入
6.1、什么是 SQL 注入
SQL 注入是指 web 应用程序对用户输入数据的合法性没有判断或过滤不严。简单来说就是 SQL 语句存在漏洞,会被攻击,导致数据泄露。
百度百科:https://baike.baidu.com/item/sql%E6%B3%A8%E5%85%A5/150289?fr=aladdin
6.2、SQL 注入测试
正常登录
public class SQLIn {
public static void main(String[] args) {
login("user1","123456") ;
}
// 登录业务
public static void login(String username,String password){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "SELECT * FROM users where `NAME`='"+username+"' AND `PASSWORD`='"+password+"'";
rs = st.executeQuery(sql);
while(rs.next()){
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("PASSWORD"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
非正常登录
login(" 'or '1=1"," 'or '1=1"); // 运行会将数据库中所有的用户信息都展示出来
SQL 语句对比
login("user1","123456") ;
SELECT * FROM users WHERE `NAME` = 'user1' AND `password` = '123456';
login(" 'or '1=1"," 'or '1=1");
SELECT * FROM users WHERE `NAME` = '' or '1=1' AND `password` = '' or '1=1'';
6.3、防止 SQL 注入
产生 SQL 注入问题的本质就是字符串拼接。通过 PreparedStatement
对象,使用 ? 占位符
代替参数,预编译 SQL 语句,手动给参数赋值,再执行即可防止 SQL 注入的问题。
PreparedStatement 对象防止 SQL 注入的本质:把传递进来的参数当做字符。假设其中存在转义字符就直接忽略,比如’会被直接转义。
PreparedStatement
- 与Statement对象不同需要预先编译sql语句
利用PreparedStatement实现增删改查代码演示:
插入
package com.peng.Demo03;
import com.peng.Demo03.JdbcUtils02.JdbcUtils02;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
public class TestInsert {
public static void main(String[] args){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
//创建连接
conn = JdbcUtils02.getConnection();
String sql = "insert into users values(?,?,?,?,?)";
st = conn.prepareStatement(sql);
st.setInt(1, 4);
st.setString(2,"小鹏");
st.setString(3,"123456");
st.setString(4,"1@1.com");
//此处应注意java.sql.Date 为sql中的时间
//java.util.Date 为java中的
st.setDate(5,new java.sql.Date(new Date().getTime()));
int i = st.executeUpdate();
if (i>0) System.out.println("插入成功");
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
JdbcUtils02.release(conn, st,null);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
修改和删除类似就不写了
注意java.sql.Date 为sql中的实践 java.util.Date 为java中的
查找:
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
//创建连接
conn = JdbcUtils02.getConnection();
String sql = "select * from users where id = ?";
st = conn.prepareStatement(sql);
st.setInt(1, 4);
ResultSet resultSet = st.executeQuery();
while(resultSet.next()){
System.out.println(resultSet.getString("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
JdbcUtils02.release(conn, st,null);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
登录业务防注入
public class TestSqlZhuRu {
public static void main(String[] args) throws Exception {
login("gong", "123456");
}
public static void login(String username,String password){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils02.getConnection();
String sql = "select * from users where name = ? and password = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, username);
statement.setString(2, password);
resultSet = statement.executeQuery();
while(resultSet.next()){
System.out.println("name:"+resultSet.getString("NAME"));
System.out.println("psw:"+resultSet.getInt("PASSWORD"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
JdbcUtils02.release(connection, statement, resultSet);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
7、IDEA 连接数据库
IDEA 工具的右侧边栏有 Database 和 Maven 等选项。
连接数据库
接时如果出现此错误,需要手动增加 MySQL 驱动(第三节导入依赖包)
修改版本
连接数据库表
双击数据库表查看数据
修改更新数据
SQL 编辑器
测试执行 SQL
-- 创建账户表
CREATE TABLE account(
id int primary key auto_increment,
name varchar(40),
money float
);
-- 插入测试数据
insert into account(name,money) values('A',1000);
insert into account(name,money) values('B',1000);
insert into account(name,money) values('C',1000);
8、JDBC 操作事务
回顾事务
事务:要么都成功,要么都失败。
事务的 ACID 原则:
- 原子性:要么全部完成,要么都不完成。
- 一致性:总数不变。
- 隔离性:多个进程互不干扰。
- 持久性:一旦提交不可逆,持久化到数据库了。
隔离性的问题:
- 脏读:一个事务读取了另一个没有提交的事务。
- 不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变。
- 虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来结果不一致。
方法:
conn.setAutoCommit(false);
开启事务关闭数据库的自动提交conn.commit();
提交conn.rollback();
回滚
测试事务
public class TestTransaction {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// 关闭数据库的自动提交,相当于自动开启事务
conn.setAutoCommit(false);
String sql1 = "UPDATE account SET money = money - 100 WHERE NAME ='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
// int x = 1/0; // 模拟失败
String sql2 = "UPDATE account SET money = money + 100 WHERE NAME ='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
// 业务完毕,提交事务
conn.commit();
System.out.println("成功!");
} catch (SQLException e) {
try {
// 事务失败默认就会回滚,这里是显示定义
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
运行结果:
事务过程
- 开启事务
setAutoCommit(false);
- 一组事务完成,提交事务
- catch中显示定义回滚语句,默认失败就会回滚
9、数据库连接池
9.1、什么是数据库连接池
SQL 执行步骤
SQL 执行会有三个步骤:数据库连接 → 执行完毕 → 释放,连接 → 释放这个过程十分浪费系统资源。
池化技术
池化技术就是准备一些预先的资源,执行时直接连接预先准备好的资源。类似于银行的多个业务窗口,安排多个业务员给客户服务。
最小连接数
最小连接数 = 常用连接数。连接池中的最小连接数就类似于银行最少安排多少个业务员。
最大连接数
最大连接数 = 业务最高承载上限。连接池中的最大连接数就类似于银行所有窗口都安排了业务员。
等待超时
如果客户等待了设定的超时时间,就通知客户不要再等待。
9.2、使用数据库连接池
使用一个数据库连接池,只需要实现 DataSource 接口即可。
市面上的开源数据源实现:DBCP、C3P0、Druid(阿里)。
使用了数据库连接池之后,在项目开发中就不需要编写连接数据库的代码了。
9.3、DBCP
本质和上面自己写的工具是差不多的
需要用到的 jar 包:commons-dbcp-1.4.jar
,commons-pool-1.6.jar
,commanns-logging.jar
dbcp下载地址:https://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
pool下载地址:https://commons.apache.org/proper/commons-pool/download_pool.cgi
logging下载地址:https://commons.apache.org/proper/commons-logging/download_logging.cgi
下载选二进制
DBCP 配置文件
#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
DBCP踩坑
- 踩坑1:用DBCP进行数据连接池连接的时候出现
java.lang.NoClassDefFoundError: Could not initialize class com.sujianbo.util.Jdbc...
- 解决方法:解决方案:导入
commanns-logging
包
- 解决方法:解决方案:导入
工具类
public class JdbcUtils_DBCP {
private static DataSource dataSource = null;
static {
try{
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);
// 创建数据源,使用工厂模式创建对象
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace(J);
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
9.4、C3P0
需要用到的 jar 包:c3p0-0.9.5.5.jar
、mchange-commons-java-0.2.19.jar
下载:https://sourceforge.net/projects/c3p0/
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--
c3p0的缺省(默认)配置
如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource();"这样写就表示使用的是c3p0的缺省(默认)
-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&uesSSL=true&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquiredIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
c3p0的命名配置
如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource("MySQL");"这样写就表示使用的是name是MySQL的配置信息来创建数据源
-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&uesSSL=true&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquiredIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
工具类
public class JdbcUtils_C3P0 {
private static ComboPooledDataSource dataSource = null;
static {
try{
// 代码版配置写法
// dataSource = new ComboPooledDataSource();
// dataSource.setDriverClass();
// dataSource.setUser();
// dataSource.setPassword();
// dataSource.setJdbcUrl();
//
// dataSource.setMaxPoolSize();
// dataSource.setMinPoolSize();
// 导入配置文件写法
dataSource = new ComboPooledDataSource("MySQL");
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 预处理sql语句,解决上面statement出现的问题