java.sq
l和javax.sql
包下);然后各个数据库厂商去实现这套接口,并提供相应的数据库驱动jar包;这样我们可以使用这套接口(JDBC)编程,其实真正执行的代码是驱动jar包中的实现类。1、创建一个普通的空项目
2、配置JDK版本
3、创建一个子模块(jdbc快速入门的程序在这里面写)
4、导入jar包
JDBC操作数据库步骤如下:
1、创建数据库和表:
CREATE DATABASE `jdbc_test` DEFAULT CHARSET utf8mb4;
CREATE TABLE `account`(
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',
`name` varchar(20) NOT NULL COMMENT '姓名',
`salary` int(11) COMMENT '薪资',
);
2、编写Java程序:
package com.baidou.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* JDBC快速入门
*
* @author 白豆五
* @version 2022/7/18 19:56
* @since JDK8
*/
public class JDBCDemo {
public static void main(String[] args) throws Exception {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://127.0.0.1:3306/jdbc_test?useSSL=false";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
String sql = "insert into account(name,salary) values('王强',10000)";
// 4、获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
// 5、执行sql
int count = stmt.executeUpdate(sql);
// 6、处理结果
// 打印受影响的行数
System.out.println(count);
System.out.println(count>0?"插入成功":"插入失败");
// 7、释放资源
stmt.close();
conn.close();
}
}
控制输出结果如下:
表中的数据:
DriverManager
:驱动管理类。1、注册驱动
例如: MySQL数据库驱动类 com.mysql.jdbc.Driver
,实现了java.sql.Driver接口。
查看源码发现底层是通过DriverManager.registerDriver()
来注册驱动的。
但是我们直接使用DriverManager.registerDriver(new Driver())
注册驱动属于硬编码方式(如果我不想用mysql,换成SqlServer 我还得导入别的厂商提供的包而且每次都得修改源代码),另外它注册了两次驱动(一次是类加载的时候、一次是new Driver对象的时候)。
所以我们可以通过反射的方式来加载这个数据库驱动类Class.forName(xxx.Driver)
。
扩展:
Class.forName("xxx");
的步骤。(但是能写还是尽量写上,避免版本兼容问题)META-INF/services/java.sql.Driver
文件中的驱动类。2、获取数据库的连接
getConnection()
方法中需要传递3个参数,分别是url
、user
、password
:
(1)url
url,统一资源定位符(网络中某个资源的绝对地址)。
url由哪几个部分组成:协议,ip,port,资源名。
比如我们访问百度的主页:
http://39.156.66.18:80/index.html
http:// 通信协议
39.156.66.18 服务器ip地址
80 端口,80是Web服务的默认端口
index.html 是服务器上某个资源名
mysql5.7和8.0驱动中url:
// mysql5.7
// userSSL=false: 禁用安全连接方式,关闭警告问题。
// useUnicode=true&characterEncoding=utf-8: 字符集编码
url=jdbc:mysql://127.0.0.1:3306/mydatabase?useSSL=false&useUnicode=true&characterEncoding=utf-8
// mysql8.0,
// 需要配置时区:serverTimezone=GMT%2B8 东八区GMT+8
url=jdbc:mysql://127.0.0.1:3306/mydatabase?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
(2)user:用户名
(3)password:密码
扩展:
jdbc:mysql:///db
。userSSL=false
,禁用安全连接方式,解决警告问题。
jdbc:mysql://127.0.0.1:3306/mydatabase?useSSL=false
,如果后面还要拼接参数如要用 &
隔开。a. 普通执行SQL对象:
Statement createStatement()
b. 预编译的执行SQL对象:(防止SQL注入)
PreparedStatement prepareStatement(String sql)
c. 执行存储过程的对象
CallableStatement prepareCall(String sql)
作用:保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行。
a. MySQL 事务管理:
开启事务:BEGIN; 或者 START TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;
# MySQL默认自动提交事务
b. JDBC事务管理:Connection接口中定义了3个对应的方法:
开启事务: setAutoCommit(boolean autoCommit): true为自动提交事务;false为手动提交事务,即为开启事务。
提交事务:commit()
回滚事务: rollback()
示例:
package com.baidou.jdbc;
import java.sql.*;
/**
* Connection,事务操作
*
* @author 白豆五
* @version 2022/7/18 21:50
* @since JDK8
*/
public class JDBC_Connection {
public static void main(String[] args) throws Exception {
// 1、注册驱动
// Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql:///jdbc_test?useSSL=false";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
String sql1 = "insert into account(name,salary) values('李四',5000)";
String sql2 = "insert into account(name,salary) values('王五',4004)";
// 4、获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
try {
/* 开启事务 */
conn.setAutoCommit(false); // 手动提交事务
// 5、执行sql
int count1 = stmt.executeUpdate(sql1);
// 6、处理结果
System.out.println(count1);// 打印受影响的行数
// 5、执行sql
int count2 = stmt.executeUpdate(sql2);
// 6、处理结果
System.out.println(count2);// 打印受影响的行数
/*提交事务*/
conn.commit();
} catch (Exception e) {
/*回滚事务*/
conn.rollback();
e.printStackTrace();
}
// 7、释放资源
stmt.close();
conn.close();
}
}
Statement:执行SQL的对象
作用:执行SQL语句
1、执行DML、DDL语句
int executeUpdate(sql)
返回值: (1)DML语句影响的行数
(2)DDL语句执行后,执行成功也可能返回0
2、执行DQL语句
ResultSet executeQuery(sql)
返回值: ResultSet结果集对象
获取查询结果:
// next功能: (1)将游标从当前位置向下移动一行(2)判断当前行是否有效
boolean next()
返回值: true:有效行,当前行有数据
false: 无效行,当前行无数据
xxx getXxx(参数) //获取指定类型数据
xxx是数据类型,例如 int getInt(参数) 、String getString(参数)
参数(重载方法): int: 列的编号,从1开始
String: 列的名称
例如获取表中id的数据:
int id = getInt("id");
int id = getInt(1);
ResultSet使用步骤:
// 通过循环遍历结果集对象
while(rs.next()){ // rs.next()将游标从当前位置向下移动一行并判断当前行是否有效
// 获取数据
rs.getXxx(参数);
}
示例:
/**
* ResultSet的使用
*
* @author 白豆五
* @version 2022/7/19 7:42
* @since JDK8
*/
public class JDBC_ResultSet {
public static void main(String[] args) throws Exception {
// 1、注册驱动
// Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql:///jdbc_test?useSSL=false";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
String sql = "select * from account";
// 4、获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
// 5、执行sql并返回结果集
ResultSet rs = stmt.executeQuery(sql);
// 6、处理结果
while (rs.next()) { // 通过循环取出ResultSet中的所有数据
System.out.print("id:" + rs.getInt("id") + "\t");
System.out.print("name:" + rs.getString("name") + "\t");
System.out.print("salary:" + rs.getInt("salary") + "\t");
System.out.println();
}
/*
// 进行读取一次 判断是否有数据
if (rs.next()) {
System.out.println("id:" + rs.getInt(1) );
}
*/
// 7、释放资源
rs.close();
stmt.close();
conn.close();
}
}
通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
示例:模拟sql注入问题
数据库SQL:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(20) NOT NULL COMMENT '密码',
PRIMARY KEY (`id`)
);
insert into user(username,password) values('张三','123');
db.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/jdbc_test?useSSL=false&useUniCode=true&characterEncoding=utf-8
user=root
password=123456
Java程序:
/**
* 模拟SQL注入问题
*
* @author 白豆五
* @version 2022/7/19 8:14
* @since JDK8
*/
public class JDBC_SQLinjection {
/*
功能:实现用户登录
需求:接收用户输入的用户名和密码;
验证用户名和密码;
返回给用户提示信息
*/
public static void main(String[] args) {
Map<String, String> userLoginInfo = initUI();
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess?"登录成功":"用户名或密码有误");
}
/**
* @param userLoginInfo 用户登录信息
* @return false表示失败, true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 定义一个红绿灯表示用户登录和失败
boolean loginSuccess = false;
try {
// 创建配置类对象
Properties properties = new Properties();
// 通过类加载器获取类路径下的db.properties这个资源
InputStream is = ClassLoader.getSystemResourceAsStream("db.properties");
// 加载资源到配置类中
properties.load(is);
// 关闭流
is.close();
// 通过属性获取配置文件里的书
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
// 注册驱动
Class.forName(driver);
// 获取连接
conn = DriverManager.getConnection(url, user, password);
// 获取数据库操作对象
stmt = conn.createStatement();
// 定义sql
String sql = "select * from user where username='" + userLoginInfo.get("username")
+ "' and password='" + userLoginInfo.get("password") + "'";
// 执行sql
rs = stmt.executeQuery(sql);
// 如果结果集中有数据表示登录成功
if (rs.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 是否资源
close(rs,stmt,conn);
}
return loginSuccess;
}
/**
* 初始化界面
*
* @return 用户名和密码等信息
*/
public static Map<String, String> initUI() {
Scanner sc = new Scanner(System.in);
System.out.println("用户名:");
String username = sc.nextLine();
System.out.println("密码:");
String password = sc.nextLine();
// 定义一个Map,存储用户信息
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("username", username);
userLoginInfo.put("password", password);
return userLoginInfo;
}
/**
* 释放资源
*
* @param rs 结果集对象
* @param stmt 执行sql的对象
* @param conn 数据库连接对象
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
分析:只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。
解决方案:
1、可以利用正则表达式来防止用户输入一些非法字符,例如(‘=’,‘or’等)
2、将Statment换成PreparedStatement(预编译的数据库操作对象);
PreparedStatement的原理:首先将SQL语句传入PreparedStatement中,然后把传入到SQL语句中的参数用?
(占位符)来代替,然后该SQL语句会进行检查和编译(让mysql操作,耗时操作),之后将获取的参数通过PreparedStatement中的set()方法传入编译后的SQL语句中,这样SQL语句就会先被编译再进行传值,最后在执行一下,就解决了SQL注入问题。
示例:使用PreparedStatement解决SQL注入问题
/**
* 使用PreparedStatement解决SQL注入问题
*
* @author 白豆五
* @version 2022/7/19 8:14
* @since JDK8
*/
public class JDBC_SQLinjection2 {
/*
功能:实现用户登录
需求:接收用户输入的用户名和密码;
验证用户名和密码;
返回给用户提示信息
*/
public static void main(String[] args) {
Map<String, String> userLoginInfo = initUI();
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess?"登录成功":"用户名或密码有误");
}
/**
* @param userLoginInfo 用户登录信息
* @return false表示失败, true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
// 定义一个红绿灯表示用户登录和失败
boolean loginSuccess = false;
try {
// 创建配置类对象
Properties properties = new Properties();
// 通过类加载器获取类路径下的db.properties这个资源
InputStream is = ClassLoader.getSystemResourceAsStream("db.properties");
// 加载资源到配置类中
properties.load(is);
// 关闭流
is.close();
// 通过属性获取配置文件里的书
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
// 注册驱动
Class.forName(driver);
// 获取连接
conn = DriverManager.getConnection(url, user, password);
// 定义sql
String sql = "select * from user where username=? and password=?";
// 获取数据库操作对象
ps = conn.prepareStatement(sql); // 先编译后传值
// 给占位符传值
ps.setString(1,userLoginInfo.get("username"));
ps.setString(2,userLoginInfo.get("password"));
// 执行sql
rs = ps.executeQuery();
// 如果结果集中有数据表示登录成功
if (rs.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 是否资源
close(rs,ps,conn);
}
return loginSuccess;
}
/**
* 初始化界面
*
* @return 用户名和密码等信息
*/
public static Map<String, String> initUI() {
Scanner sc = new Scanner(System.in);
System.out.println("用户名:");
String username = sc.nextLine();
System.out.println("密码:");
String password = sc.nextLine();
// 定义一个Map,存储用户信息
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("username", username);
userLoginInfo.put("password", password);
return userLoginInfo;
}
/**
* 释放资源
*
* @param rs 结果集对象
* @param ps 执行sql的预编译对象
* @param conn 数据库连接对象
*/
public static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
try {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
PreparedStatement:预编译的SQL执行对象。
作用:预编译SQL并执行SQL语句
1、获取PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
//通过Connection对象获取,并传入对应的sql语句,然后进行预编译操作
PreparedStatement pstmt = conn.prepareStatement(sql);
2、设置参数值
ps.setXxx(参数1,参数2) //给?赋值
Xxx:数据类型; 如 ps.setInt (参数1,参数2)、ps.setString (参数1,参数2)
设置参数:
参数1: ?的位置编号,从1开始 (index)
参数2: ?的值 (value)
3、执行SQL
ps.ecuteUpdate(); // 不需要再传递sql
import org.junit.Test;
import java.sql.*;
/**
* @ClassName: JDBCTest05
* @Description: 使用PreparedStatement完成增删改查功能
* @Author: baidou
* @Date: 2022/2/2 09:06
* Version: 1.0
*/
public class JDBCTest05 {
Connection conn;
PreparedStatement pstmt;
/**
* 获取数据库连接对象
*
* @return connection
* @throws SQLException, ClassNotFoundException
*/
public static Connection getConnection() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false&useUniCode=true&characterEncoding=utf-8", "root", "123456");
return connection;
}
/**
* 获取数据库的预处理对象
*
* @param connection 数据库连接对象
* @param sql 我们传入的sql语句
* @param obj 占位符的参数
* @return
* @throws SQLException
*/
public static PreparedStatement getPreparedStatement(Connection connection, String sql, Object[] obj) throws SQLException {
PreparedStatement pstmt = connection.prepareStatement(sql);
if (obj != null) {
for (int i = 0; i < obj.length; i++) {
pstmt.setObject(i + 1, obj[i]);
}
}
return pstmt;
}
/**
* 释放资源
*
* @param rs
* @param ps
* @param conn
* @throws SQLException
*/
public static void close(ResultSet rs, PreparedStatement ps, Connection conn) throws SQLException {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
@Test
public void add() throws SQLException, ClassNotFoundException {
conn = JDBCTest05.getConnection();
String sql = "insert into user(name,password,vocation) values(?,?,?)";
Object[] obj = {"大灰狼", "2333", "战士"};
pstmt = JDBCTest05.getPreparedStatement(conn, sql, obj);
int count = pstmt.executeUpdate();
System.out.println(count > 0 ? "添加成功" : "添加失败");
JDBCTest05.close(null, pstmt, conn);
}
@Test
public void delete() throws SQLException, ClassNotFoundException {
conn = JDBCTest05.getConnection();
String sql = "delete from user where id=?";
pstmt = JDBCTest05.getPreparedStatement(conn, sql, new Object[]{4});
int count = pstmt.executeUpdate();
System.out.println(count > 0 ? "删除成功" : "删除失败");
JDBCTest05.close(null, pstmt, conn);
}
@Test
public void update() throws SQLException, ClassNotFoundException {
conn = JDBCTest05.getConnection();
String sql = "update user set name=?,password=? where name=?";
Object[] obj = {"铠甲勇士", "0000", "zhangsan"};
pstmt = JDBCTest05.getPreparedStatement(conn, sql, obj);
int count = pstmt.executeUpdate();
System.out.println(count > 0 ? "修改成功" : "修改失败");
JDBCTest05.close(null, pstmt, conn);
}
@Test
public void list() throws SQLException, ClassNotFoundException {
conn = JDBCTest05.getConnection();
String sql = "select * from user";
pstmt = JDBCTest05.getPreparedStatement(conn, sql, null);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println("id:" + rs.getObject(1) + "\t用户名:" + rs.getObject(2) + "\t密码:" + rs.getObject(3) + "\t职业:" + rs.getObject(4));
}
JDBCTest05.close(rs, pstmt, conn);
}
}
扩展:通过日志查看PreparedStatement原理
1、开启预编译功能
我们之前使用PreparedStatement只是预防SQL注入问题,并没有达到预编译的效果,如果想开启预编译功能在数据库url后面拼接如下参数即可。(这样sql模板相同,会提升性能效率)
useServerPrepStmts=true
2、在my.ini中配置MySQL执行日志(重启mysql服务后生效)
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
D:\mysql.log
文件。示例:查看preparedstatment执行原理
sql脚本:
use test;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'张三',1000);
insert into account values(null,'李四',1000);
测试代码:
package com.baidou.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author 白豆五
* @version 2022/11/28 22:29
* @since JDK8
*/
public class JDBCTest1 {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取数据库连接对象
String url = "jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
// System.out.println(conn);
// 定义SQL语句
// 查看指定用户的账户信息
String sql = "select * from account where name=?";
// 获取预编译对象PrepareStatement
PreparedStatement pstmt = conn.prepareStatement(sql);//预编译
pstmt.setString(1, "张三");
// 延迟加载方便查看日志的预编译结果
Thread.sleep(1000);
// 执行sql并返回结果集
ResultSet rs = pstmt.executeQuery();
// 判断指定的账户信息是否存在
if (rs.next()){
System.out.println("姓名:"+rs.getString("name")+"\t余额:"+rs.getDouble("money"));
}else {
System.out.println("账户信息不存在");
}
System.out.println("------------------");
// sql模板相同,就不用再次进行预编译了
pstmt.setString(1, "李四五");
rs = pstmt.executeQuery();
// 判断指定的账户信息是否存在
if (rs.next()){
System.out.println("姓名:"+rs.getString("name")+"\t余额:"+rs.getDouble("money"));
}else {
System.out.println("账户信息不存在");
}
}
}
总结:
数据库连接池的实现
1、标准接口:javax.sql.DataSource
。
Connection getConnection()
,获取连接。2、常见的数据库连接池:
Druid使用步骤:
1、导入jar包 druid-1.1.12.jar。
2、定义配置文件。
3、加载配置文件。
4、获取数据库连接池对象。
5、获取连接。
druid.properties
配置文件:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_test?useSSL=false&useServerPrepStmts=true
username=root
password=123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
测试代码:
package com.baidou.druid;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* Druid的使用
*
* @author 白豆五
* @version 2022/7/19 10:28
* @since JDK8
*/
public class DruidDemo {
public static void main(String[] args) throws Exception {
//加载properties配置文件
Properties prop = new Properties();
// System.out.println(System.getProperty("user.dir"));
prop.load(new FileInputStream("jdbc_demo2/src/druid.properties"));
// 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);//工厂模式
// 获取数据库连接
Connection conn = dataSource.getConnection();
System.out.println("conn = " + conn);
conn.close(); //归还(装饰者模式/代理模式)
}
}