一、JDBC概念
JDBC(Java数据库连接)就是使用Java语言操作关系型数据库的一套API,定义了一套操作所有的关系型数据库的规则,也就是一套标准接口,而不同的数据库对于JDBC有不同的实现类(驱动),真正执行的代码是驱动jar包中的实现类。
二、JDBC快速入门
1、创建工程,导入驱动jar包
2、注册驱动
3、获取连接
4、定义SQL语句
5、获取执行SQL对象
6、执行SQL
7、处理返回结果
8、释放资源
三、具体过程
1、创建一个project
intellij IDEA——File——New——Project——Empty Project——Next——起一个名字
2、配置
File——Project Structure——Project——Project SDK中改为1.8版本,下面选8
3、创建模块
File——Project Structure——Modules——点+号——New Module——Java——起个名
4、导入驱动jar包
创建一个新的文件夹名为lib——将mysql的jar包复制粘贴到lib下
需要让当前工程识别jar包:右键——Add as Library——选择当前jar包生效的范围(Global Library:全局有效 Project Library:当前工程有效 Module Library:模块有效),选择模块有效即可
5、创建类
src下创建java class,包名.类名
6、代码
(psvm主方法快捷)
(sout打印快捷键)
package com.itexam.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//1、创建驱动,将driver类加载进内存
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=GMT";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//3、定义sql
String sql = "UPDATE account set money = 2000 where id = 1";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
int count = stmt.executeUpdate(sql);
//6、处理结果
System.out.println(count);
//7、释放资源
stmt.close();
conn.close();
}
}
四、JDBC API详解
(一)DriverManager(驱动管理类)
1、注册驱动
Class.forName(“com.mysql.cj.jdbc.Driver”);
MYSQL5之后的驱动包可以省略注册驱动的步骤,因为会自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
2、获取数据库连接
Connection conn = DriverManager.getConnection(url,username,password);
(1)url:连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
注意:
如果连接的是本机mysql服务器,并且mysql服务器默认端口是3306,则url可以简写为jdbc:mysql:///数据库名称?参数键值对1&参数键值对2…
配置useSSL=false 参数,禁用安全连接方式,解决警告提示。
(2)user:用户名
(3)password:密码
(二)Connection
1、获取执行SQL的对象
(1)普通执行SQL对象
Statement stmt = conn.createStatement();
(2)预编译SQL的执行SQL对象,防止SQL注入
PreparedStatement preparedStatement(sql)
(3)执行存储过程的对象(不常用)
CallableStatement prepareCall(sql)
2、事务管理
(1)MYSQL事务管理
开启事务:BEGIN; / START TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;
注:MySQL默认自动提交事务
(2)JDBC事务管理:Connection接口中定义了3个对应的方法
开启事务:setAutoCommit(boolean autoCommit); true为自动提交事务;false为手动提交事务,一般设置为false。
提交事务:commit()
回滚事务:rollback()
成功就全成功,失败就全失败
【测试】
进行事务处理
这里对mysql有两条操作语句,在事务中,两个操作要么全成功,要么全失败。
全成功情况
//3、定义sql
String sql1 = "UPDATE account set money = 2000 where id = 1";
String sql2 = "UPDATE account set money = 3000 where id = 2";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
/*在执行SQL之前开事务,在这个事务中(从开启到提交),如果出现异常就应该回滚事务,用java异常处理机制try-catch (快捷键ctrl+alt+t)*/
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 throwables) { //抓大异常,不仅是SQLException
//出现异常回滚事务
conn.rollback();
throwables.printStackTrace();
}
/*执行成功后提交事务,可放进try-catch块里*/
原表:
IDEA执行结果:
MYSQL修改结果:
两条语句全部执行成功。
全失败情况:
手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行
//3、定义sql
String sql1 = "UPDATE account set money = 2000 where id = 1";
String sql2 = "UPDATE account set money = 3000 where id = 2";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
/*在执行SQL之前开事务,在这个事务中(从开启到提交),如果出现异常就应该回滚事务,用java异常处理机制try-catch (快捷键ctrl+alt+t)*/
try {
/*开启事务*/
conn.setAutoCommit(false);
/*第一个任务*/
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
System.out.println(count1);
/*手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,
所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行*/
int i = 3/0;
/*第二个任务*/
//5、执行sql
int count2 = stmt.executeUpdate(sql2);
//6、处理结果
System.out.println(count2);
/*执行成功后提交事务*/
conn.commit();
} catch (Exception throwables) { //抓大异常,不仅是SQLException
//出现异常回滚事务
conn.rollback();
throwables.printStackTrace();
}
/*执行成功后提交事务,可放进try-catch块里*/
原表:
IDEA执行结果:
原表未被修改:
不进行事务处理
把事务处理的代码都注释掉,保留两个操作中间的异常,那么第一个操作会成功,第二个操作会失败
try {
/*开启事务*/
//conn.setAutoCommit(false);
/*第一个任务*/
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
System.out.println(count1);
/*手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,
所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行*/
int i = 3/0;
/*第二个任务*/
//5、执行sql
int count2 = stmt.executeUpdate(sql2);
//6、处理结果
System.out.println(count2);
/*执行成功后提交事务*/
//conn.commit();
} catch (Exception throwables) { //抓大异常,不仅是SQLException
//出现异常回滚事务
//conn.rollback();
throwables.printStackTrace();
}
原表:
IDEA执行结果:
代码执行后的表中,第一条从1000改为了2000,第二条未发生变化:
(三)Statement
1、执行SQL语句
(1)int executeUpdate(sql):执行DML(对数据增删改)、DDL(对表和库的增删改查)语句
返回值:A.DML语句影响的行数
//3、定义sql
String sql1 = "UPDATE account set money = 2000 where id = 1";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
System.out.println(count1);
在上述代码中,sql1就是一条DML语句,count1就是执行后所影响的行数,运行结果为1。如果将DML语句改为UPDATE account set money = 2000 where id = 10,而原表中并没有id=10的记录,运行结果会返回0。
但用户想要看到的结果是“成功”或者是“失败”,而不是输出0或1,可以通过影响的行数判断是否执行成功,使用if-else语句,如果大于0输出“执行成功”,否则输出“执行失败”,代码如下:
//3、定义sql
String sql1 = "UPDATE account set money = 2000 where id = 1";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
//System.out.println(count1);
if(count1>0){
System.out.println("执行成功");
}else {
System.out.println("执行失败");
}
运行结果:
B.DDL语句执行后,执行成功也可能返回0
下面代码是创建一个数据库db2,执行成功,输出结果为1,在数据库内也会成功创建db2数据库。
//3、定义sql
String sql1 = "create database db2";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
System.out.println(count1);
下面代码是删除一个数据库db2,IDEA输出结果为0,但数据库db2成功被删除了,说明此DDL语句执行成功也可能会返回0:
//3、定义sql
String sql1 = "drop database db2";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
int count1 = stmt.executeUpdate(sql1);
//6、处理结果
System.out.println(count1);
执行结果:
因此,执行DDL语句时,不能通过影响行数的结果判断是否执行成功。
(2)ResultSet executrQuery(sql): 执行DQL语句(查询语句)(比较常用)
(四)ResultSet
返回值:ResultSet 结果集对象(封装了DQL查询语句的结果)
【获取查询结果】
boolean next():
1)将光标从当前位置向下移动一行,开始默认在属性行;
2)判断当前行是否为有效行
返回值:
- true:有效行,当前行有数据;
- false:无效行,当前行没有数据。
xxx getXxx(参数):获取数据
xxx:数据类型,如:int getInt(参数),String getString(参数)
参数:int:列的编号,从1开始 ; String:列的名称
获取查询结果过程:
最开始光标指在id-name-money一行,从当前位置往下一行,指在id=1的行,判断这一行是否有效,当前行有效,返回true,获取数据;从当前位置往下一行,指在id=2的行,判断这一行是否有效,当前行有效,返回true,获取数据;从当前位置往下一行,判断这一行是否有效,当前行无效,没有数据,返回false,执行结束;
这个过程是一个重复执行的过程,可以用一个循环:
//循环判断游标是否是最后一行末尾
while(rs.next()){
//获取数据
rs.getXxx(参数);
}
以下两种方法都可以实现,第一种使用getXxx(int(列的编号,从1开始)),另一种是getXxx(String(列的名称)),记得加引号!
//3、定义sql
String sql1 = "select * from account";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
ResultSet rs = stmt.executeQuery(sql1);
//6、处理结果,遍历rs中的数据
//6.1光标向下移动一行,并判断当前行是否有数据
// while(rs.next()){
// //6.2 获取数据 getXxx()
// int id = rs.getInt(1);
// String name = rs.getString(2);
// double money = rs.getDouble(3);
//
// System.out.println(id);
// System.out.println(name);
// System.out.println(money);
//
// System.out.println("-----------------------");
// }
while(rs.next()){
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("-----------------------");
}
//7、释放资源
stmt.close();
conn.close();
两种方式运行结果相同:
【ResultSet案例】
我们已经从上述代码中获取了数据库中的数据,每一行就是一个java对象,形成很多个java对象。在实际应用中,我们希望以一个表的形式来展现,就需要对这些java对象进一步处理,放到一个容器内,而集合就是一个专门装对象的容器,然后把这个容器给页面,页面就可以展示这些数据。
需求:查询account账户表数据,封装为Account对象中,并存储到ArrayList集合中。
第一步:定义实体类Account
新建一个包pojo(这个名称一般代表存放一些实体类)
1、成员变量的名称要与数据库里字段的名称和类型一样。
2、提供setter和getter方法,快捷键alt+insert。
3、为了方便查看,使用toString()方法,同样alt+insert
注意2和3都要全选
public class Account {
//成员变量的名称要与数据库里字段的名称和类型一样
private int id;
private String name;
private double money;
//提供setter和getter方法,快捷键alt+insert
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//为了方便查看,使用toString()方法,同样alt+insert
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
第二步:创建对象,并将获取的数据封装到对象中
使用@Test代替主函数去运行测试
//6、处理结果,遍历rs中的数据
//6.1光标向下移动一行,并判断当前行是否有数据
while(rs.next()){
//创建对象
Account account = new Account();
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
//将获取的数据赋值给对象
account.setId(id);
account.setName(name);
account.setMoney(money);
}
第三步:把对象存到集合内
如果在while循环里创建集合,那么每一次循环都会有一个新的集合,我们只需要将所有对象存到一个集合内,所以创建集合应在while循环体外。
//创建集合
List<Account> list = new ArrayList<>();
//6、处理结果,遍历rs中的数据
//6.1光标向下移动一行,并判断当前行是否有数据
while(rs.next()){
//创建对象
Account account = new Account();
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
//将获取的数据赋值给对象
account.setId(id);
account.setName(name);
account.setMoney(money);
//存入集合
list.add(account);
}
System.out.println(list);
运行结果:
(五)PreparedStatement
PreparedStatement是一个接口,继承自Statement,表示预编译SQL语句的对象。
作用:
1.预编译SQL语句,性能更高;
2.预防SQL注入问题,将敏感字符进行转义
- SQL注入:SQL注入是通过操作输入来修改事先定义好的SQL语句。用以达到执行代码对服务器进行攻击的方法。
以下是一个简单的登录代码:
//接受用户输入的用户名和密码
String input_name = "Mike";
String input_pwd = "123";
//3、定义sql
String sql = "select * from login_user where Login_name = '"+input_name+"' and Login_pwd = '"+input_pwd+"'";
//4、获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//5、执行sql
ResultSet rs = stmt.executeQuery(sql);
//6、处理结果,判断登录是否成功,只要rs里面有数据,就说明登录成功
if(rs.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
因为在数据库的表中能查到用户输入的登录名和密码,所以运行显示登录成功。
如果用户名随便写,密码也随便写,如下:
String input_name = “1vshupasse”;
String input_pwd = “’ or ‘1’='1”;
仍然会显示执行成功。打印一下sql,select * from login_user where Login_name = ‘1vshupasse’ and Login_pwd = ’ ’ or ‘1’=‘1’,修改了SQL原来的含义,or前面返回false,但or后面是恒等式,所以where后面的语句就没有起到作用,就会把所有的数据全部查出来。这就是SQL注入。
为了解决SQL注入问题,可以使用PreparedStatement对象解决,有以下步骤:
(1)获取PreparedStatement对象
- //定义sql,SQL语句中的参数值,使用?占位符替代,避免了拼字符串:String sql = “select * from login_user where Login_name = ? and Login_pwd = ?”;
- //通过Connection获取pstmt对象,并传入对应的sql语句:
PreparedStatement pstmt = conn.prepareStatement(sql);
(2)设置参数值
PreparedStatement对象:setXxx(参数1,参数2),给占位符?赋值
- Xxx:数据类型,如setInt(参数1,参数2),要跟表中属性的数据类型对应
- 参数:参数1:?的位置编号,从1开始;
参数2:?的值
例如:
//设置?的值
pstmt.setString(1,input_name);
pstmt.setString(2,input_pwd);
(3)执行SQL
executeUpdate(); 或者executeQuery(); 此时括号内不需要再传递sql。
以下是完整代码:
@Test
public void testPreparedStatement() throws Exception {
//1、创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=GMT";
String uname = "root";
String pwd = "123456";
Connection conn = DriverManager.getConnection(url,uname,pwd);
//接受用户输入的用户名和密码
String input_name = "Mike";
String input_pwd = "1234";
//3、定义sql,SQL语句中的参数值,使用?占位符替代,避免了拼字符串
String sql = "select * from login_user where Login_name = ? and Login_pwd = ?";
//通过Connection获取pstmt对象,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置?的值
pstmt.setString(1,input_name);
pstmt.setString(2,input_pwd);
//5、执行sql
ResultSet rs = pstmt.executeQuery();
//6、处理结果,判断登录是否成功,只要rs里面有数据,就说明登录成功
if(rs.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
//7、释放资源
rs.close();
pstmt.close();
conn.close();
}
PreparedStatement原理
(1)PreparedStatement预编译功能默认是关闭的,开启: &useServerPrepStmts=true
(2)配置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
- 将上述代码复制到mysql安装路径的my.ini文件下,重启mysql,在IDEA中开启预编译功能,执行后,可在log文件中看到整个执行的过程
(3)原理
- 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查编译
- 执行时就不用在进行以上步骤,速度更快
- 如果sql模板一样,则只需要进行一次检查、编译。
五、数据库连接池
(一)数据库连接池简介
- 数据库连接池是个容器,负责分配、管理数据库连接
- 它允许应用程序重复使用一个现有的数据库连接,而不是在重新建立一个;
- 当容器内的连接都用完了,又来了一个新的用户,这是会自动检查分配出去的连接在干什么,会强制收回不使用的连接,放回到连接池,再分配给新的用户。
- 好处:(1)资源复用;(2)提升系统响应速度;(3)避免数据库连接遗漏
在系统启动之前,初始化一个容器,在这个容器里提前申请很多个数据库连接,用户使用时,会分配给用户一个数据库连接,用户使用完毕,这个连接不会被释放,而是重新回到这个容器内,实现资源复用。
(二)数据库连接池实现
1、标准接口:DataSource
- 官方(SUN)提供的数摇库连接池标准接口,由第三方组织实现此接口。
- 功能:获取连接 Connection getConnection()
2、常见的数据库连接池:
DBCP
C3Po.
Druid.
(三)Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一;
Druid使用步骤
1、导入jar包,druid-1.1.12.jar
2、定义配置文件
3、加载配置文件
4、获取数据库连接池对象
5、获取连接