操作数据库的方式(三种)
方式1:命令行形式操作数据库
方式2:使用图形化界面的客户端,如Navicat
方式3: 通过java程序连接MySQL数据库,也就是使用JDBC技术
什么是JDBC
- JDBC(Java Data Base Connectivity),是 Java连接数据库的技术。
- 是一种执行SQL的API, 可以为多种关系型数据库提供统一的访问。
- 它是由一组用java语言编写的类和接口组成, 是Java访问数据库的标准规范。
JDBC的原理
数据库驱动
-
数据库驱动就是直接操作数据库的一个程序
-
不同数据产品的数据库驱动名字有差异
-
在程序中需要依赖数据库驱动来完成对数据库的操作
JDBC实现
JDBC在进行操作之前,要先引入MySQL的数据库驱动(.jar)
引入步骤:
1.在当前项目的根目录下创建lib目录
2.将需要的jar包放入到创建好的lib目录下
3.在lib目录下右键jar包,选择 Add As Library 选项将jar添加到依赖库中
实现查询
1.加载驱动/注册驱动
加载注册驱动的方式 | 描述 |
---|---|
Class.forName(数据库启动实现类) | 加载和注册数据库驱动,数据库驱动由数据库厂商MySQL提供:"com.mysql.cj.jdbc.Driver" |
代码实现:
Class.forName("com.mysql.cj.jdbc.Driver");
先加载驱动的目的:进行MySQL的Driver类的类加载(执行静态代码块,将创建的对象交给DriverManager管理)
2.获取数据库连接
获取连接的静态方法 | 说明 |
---|---|
Connection getConnection(String url, String user, String password) | 通过数据库url、数据库用户名和数据库密码来获取数据库连接 |
(url:连接数据库的地址,MySQL的格式为:jdbc:mysql://ip地址:端口号/数据库名)
url主要组成部分的详细说明
JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔
String url = "jdbc:mysql://localhost:3306/test?useSSL=false
&useUnicode=true
&characterEncoding=utf8
&serverTimezone=Asia/Shanghai";
- 第一部分是协议 jdbc,这是固定的
- 第二部分是子协议,就是数据库名称,连接mysql数据库,第二部分当然写的是mysql
- 第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求,mysql的第三部分分别由数据库服务器的IP地址(localhost)、端口号(3306),以及要使用的数据库名称组成
3.获取SQL执行对象/获取SQL执行器
Connection接口中的方法 | 说明 |
---|---|
Statement createStatement() | 创建SQL语句执行对象 |
代码实现
Statement statement = connection.createStatement();
4.使用SQL执行对象/SQL执行器将要执行的SQL语句发送到MySQL,由MySQL负责执行,获取SQL执行后的结果集(查询结果的集合)
Statement类常用方法 | 说明 |
---|---|
int executeUpdate(String sql) | 执行insert、delete、update语句,返回int类型,代表受影响的函数 |
ResultSet executeQuery(String sql) | 执行select语句,返回ResultSet结果集对象 |
String sql = "select * from user";
ResultSet rs = statement.executeQuery(sql);
5.处理结果集
查询结果会放到结果集中,通过处理结果集获取查询到的结果
ResultSet接口方法 | 说明 |
---|---|
boolean next() | 1.游标向下一行 2.返回boolean类型,如果有下一条记录,返回true,否则返回false |
xxx getXxx(String or int) | 1.通过列名获取数据 2.通过列索引获取数据,索引从1开始 |
结果集的使用原理及特点
默认的 ResultSet 对象仅有一个向前移动的指针。最初,指针被置于第一行之前。next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false, 所以可以在 while 循环中使用它来迭代结果集。因为仅有一个,所以只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。
while(resultSet.next()){
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password1 = resultSet.getString("password");
int age = resultSet.getInt("age");
System.out.println("id:" + id + ", username:" + username + ",
password:" + password1 + ", age:" + age);
}
6.资源的释放
释放原则: 先开的后关, 后开的先关 Statement ==> Connection。
-
需要释放的对象:ResultSet结果集,Statement语句,Connection连接
-
ResultSet结果集: 当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭
代码实现
statement.close();
connection.close();
操作时出现的一些常见异常
注意事项:
1.常见异常:
1.java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
缺少Driver的.jar
2.java.sql.SQLSyntaxErrorException: Unknown database 'zqwl044'
连接的库不存在
3.java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
用户名,密码有误。
4.java.sql.SQLSyntaxErrorException: Table 'zqwl04.userr' doesn't exist
操作的表不存在
5.java.sql.SQLSyntaxErrorException: Unknown column 'uname' in 'field list'
字段不存在,sql中的字段不存在
6.java.sql.SQLException: Column 'idd' not found.
获取字段值时,字段名不正确
7.java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'
添加的主键值为重复的
针对JDBC查询的小总结
操作步骤的总结
-
加载驱动(可以省略)
-
获取连接
-
创建SQL执行器
-
发送SQL并执行, 拿到返回结果集
-
处理结果集(查询需要)
-
资源的释放
实现增删改
操作步骤同查询来说只少了一步处理结果集的操作
1.插入记录(增)
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 注册数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获取数据库连接
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true" +
"&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
Connection connection = DriverManager.getConnection(url, "root", "root3306");
//3. 创建执行语句对象
Statement statement = connection.createStatement();
String sql = "insert into jdbc_user values(default,'tom','123',20)";
//4. 执行SQL并得到结果
int i = statement.executeUpdate(sql);
System.out.println("受影响的行数是:" + i);
//5. 释放资源
statement.close();
connection.close();
}
}
2.删除记录(删)
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 注册数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获取数据库连接
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true" +
"&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
Connection connection = DriverManager.getConnection(url, "root", "root3306");
//3. 创建执行语句对象
Statement statement = connection.createStatement();
String sql = "delete from jdbc_user where id=4";
//4. 执行SQL并得到结果
int i = statement.executeUpdate(sql);
System.out.println("受影响的行数是:" + i);
//5. 释放资源
statement.close();
connection.close();
}
}
3.修改记录(改)
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 注册数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获取数据库连接
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true" +
"&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
Connection connection = DriverManager.getConnection(url, "root", "root3306");
//3. 创建执行语句对象
Statement statement = connection.createStatement();
String sql = "update jdbc_user set username='jack' where id=4";
//4. 执行SQL并得到结果
int i = statement.executeUpdate(sql);
System.out.println("受影响的行数是:" + i);
//5. 释放资源
statement.close();
connection.close();
}
}
ORM编程思想
ORM(Object Relational Mapping,简称ORM,或O/R mapping )对象关系映射,是一种为了解决面向对象语言与关系数据库存在的互不匹配的现象。
statement对象存在的问题
针对用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,但是改变了原有 SQL真正的意义,以上问题称为SQL注入。
为了解决这一问题,可以使用PreparedStatement预处理对象来代替相对来说不安全的statement对象进行操作
PreparedStatement接口介绍
PreparedStatement 是Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句对象。
PreparedStatement的特点(为什么相对来说安全)
-
因为有预编译的功能,提高SQL的执行效率
-
可以有效的防止SQL 注入的问题,安全性更高
(预编译:是指SQL 语句被预编译, 并存储在PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。)
获取PreparedStatement对象
通过Connection创建PreparedStatement对象。
Connection接口中的方法 | 说明 |
---|---|
PreparedStatement prepareStatement(String sql) | 指定预编译的SQL语句,SQL语句中使用占位符?创建一个语句对象 |
PreparedStatement常用方法
常用方法 | 说明 |
---|---|
int executeUpdate() | 执行insert、delete、update语句 |
ResultSet executeQuery() | 执行select语句,返回ResultSet结果集对象 |
PreparedStatement使用
1.编写SQL语句,对于未知的内容使用 " ? " 占位符来占位,如
String sql = "select * from jdbc_user where username=? and password=?";
2.获取PreparedStatement对象
3.设置实际参数: setXxx(占位符的位置, 真实的值)或者说给占位符进行赋值,
setXxx重载方法 | 说明 |
---|---|
void setDouble(int parameterIndex, double x) | 给指定位置设置double类型的值 |
void setInt(int parameterIndex, int x) | 给指定位置设置int类型的值 |
void setString(int parameterIndex, String x) | 给指定位置设置String 类型的值 |
void setObject(int parameterIndex, Object x) | 给指定位置设置Object 类型的值 |
4.执行SQL
PreparedStatement执行原理
PreparedStatement进行批处理
JDBC提供的批处理功能(Batch)。关键点如下:
- 批处理是指将关联的SQL语句组合成一个批处理,并将他们当成一次调用提交给数据库, 一次 发送多个SQL语句到数据库,可以减少通信的资源消耗,从而提高了性能
- executeBatch() 方法用于启动执行所有组合在一起的语句
executeBatch() 方法返回一个整数数组,数组中的每个元素代表了各自的影响行数
PreparedStatement获取自增主键值
//创建预编译对象 本次参数要比之前多个返回值的指定
PreparedStatement ps =
connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//获取自增主键
ResultSet resultSet = ps.getGeneratedKeys();
if(resultSet.next()){
System.out.println("主键id是:" + resultSet.getInt(1));
}
JDBC进行事务控制
事务控制的使用方式
1. 使用MySQL的命令来操作事务控制
2. 使用JDBC操作事务控制
事务是一个整体, 由一条或者多条SQL语句组成, 这些SQL语句要么都执行成功, 要么就失败, 只要有一条SQL出现异常, 整个操作就会回滚。
事务(ACID): 原子性、一致性、隔离性、持久性。
事务的隔离级别:
1. 读未提交: 脏读 不可重复读 幻读
2. 读已提交: 不可重复读 幻读
3. 可重复读: 幻读
4. 串行化: 完全解决,整张表加锁, 同时只能有一个客户端操作。
回滚: 就是事务运行的过程中发生了某种故障, 或者SQL出现了异常, 事务不能继续执行, 系统将事务中对数据库的所有已完成的操作全部取消, 回滚到事务开始时的状态。(宕机等情况自动回滚, 代码的bug手动回滚)
事务相关方法
使用connection中的方法实现事务控制。
方法 | 说明 |
---|---|
void setAutoCommit(boolean autoCommit) | 参数设置为true,表示自动提交; 设置为false,表示手动提交 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
连接池
对于一个简单的应用程序,由于对于数据库的访问不是很频繁。 这时可以用时直接创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。
但是对于一个复杂的数据库应用,频繁的建立、关闭连接,会极大的降低系统的性能,因为对于连接的使用成了系统性能的瓶颈。
连接池的原理
数据库连接池的基本原理是连接池对象中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法
连接池工具类
分类
-
C3P0数据库连接池,速度相对较慢,稳定性不错。(hibernate,spring)
-
DBCP数据库连接池,速度相比C3P0快,但不稳定。
-
Druid(德鲁伊)数据库连接池,是由阿里提供的连接池,及DBCP,C3P0,...优点于一身的数据库连接池。