一、SQL注入
登录案例
通过键盘输入账号和密码,然后到数据库中查询是否存在输入的账号密码,如果存在则正常登录,如果不存在在登录失败。
准备数据
create table user(
id int primary key auto_increment,
username varchar(32) not null unique,
password varchar(32) not null
);
insert into user values(null,'zhangsan','123');
insert into user values(null,'lisi','123');
insert into user values(null,'wangwu','123');
代码演示
import java.sql.*;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) throws Exception {
// 创建键盘录入对象
Scanner input = new Scanner(System.in);
System.out.print("请输入账号:");
String username = input.nextLine();
System.out.print("请输入密码:");
String password = input.nextLine();
// 调用登录系统的方法
boolean result = login(username,password);
System.out.println( result ? "登录成功" : "登录失败");
}
// 登录系统的方法
public static boolean login(String username, String password) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
Connection connection = DriverManager.getConnection(url,"root","root");
// 获取Statement对象
Statement statement = connection.createStatement();
// 准备SQL语句
String sql = "select * from user where username='"+ username +"' and password = '"+ password +"'";
System.out.println(sql);
// 获取结果集,要么有一条记录,要么没有记录
ResultSet resultSet = statement.executeQuery(sql);
// 声明变量 记录结果集中的数据结果
boolean flag = false;
if ( resultSet.next() ) {
flag = true;
}
// 释放资源
resultSet.close();
statement.close();
connection.close();
// 返回结果
return flag;
}
}
结果分析
上述的案例中我们发现,如果输入的用户名和密码中包含特有的SQL语句关键字,并且这些SQL语句的关键字参与了SQL语句的编译,导致了SQL语句原意扭曲,即使账号密码在不正确的情况下,也能实现账号的登录,这种现象我们程序SQL注入。
SQL 注入
SQL是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用SQL。而SQL注入是将Web页面的原URL、表单域或数据包输入的参数,修改拼接成SQL语句,传递给Web服务器,进而传给数据库服务器以执行数据库命令。如Web应用程序的开发人员对用户所输入的数据内容不进行过滤或验证(即存在注入点)就直接传输给数据库,就可能导致拼接的SQL被执行,获取对数据库的信息以及提权,发生SQL注入攻击。
解决方案
- 如果用户提供的信息中含有SQL语句的关键字,只要让这些关键字不参与SQL语句的编译,就不会产出SQL注入。
- 为了防止SQL注入,JDBC中提供了java.sql.PreparedStatement接口,这个接口叫做:预编译的数据库操作对象。
- 在编译SQL语句之前,先将SQL语句的框架使用PreparedStatement进行预先的编译,再给这个框架SQL传值,即使用户提供的信息中含有SQL语句的关键字,也不会参与SQL语句的编译,避免SQL注入的产生。
二、PreparedStatement接口
java.sql.PreparedStatement
:表示预编译的SQL语句的对象。
SQL语句已预编译并存储在一个 PreparedStatement 对象中。 然后可以使用该对象多次有效地执行此语句。
获取PreparedStatement对象的方法
PreparedStatement prepareStatement(String sql) 创建一个 PreparedStatement对象,用于将参数化的SQL语句发送到数据库。
SQL语句框架语法格式
select * from 表名 where 字段名 = ? , 字段名 = ?..;
select * from user where username=? and password=?; #字段的编号(从1开始)
给SQL框架传值的方法
void setString(int parameterIndex, String x) 将指定的参数设置为给定的Java String值。//SQL框架中的字段下标从1开始
执行SQL语句并获取结果集的方法
ResultSet executeQuery() 执行此 PreparedStatement对象中的SQL查询,并返回查询生成的 ResultSet对象。
代码演示
import java.sql.*;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) throws Exception {
// 创建键盘录入对象
Scanner input = new Scanner(System.in);
System.out.print("请输入账号:");
String username = input.nextLine();
System.out.print("请输入密码:");
String password = input.nextLine();
// 调用登录系统的方法
boolean result = login(username,password);
System.out.println( result ? "登录成功" : "登录失败");
}
// 登录系统的方法
public static boolean login(String username, String password) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
Connection connection = DriverManager.getConnection(url,"root","root");
// 声明SQL语句框架
String sql = "select * from user where username=? and password=?";
// 获取预编译的数据库操作对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给SQL框架中的字段传值,下标从1开始
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
// 执行SQL语句获取结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 声明变量 记录结果集中的数据结果
boolean flag = false;
if ( resultSet.next() ) {
flag = true;
}
// 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
// 返回结果
return flag;
}
}
注意
在有需求中,是需要通过SQL注入去完成一些功能的,这些功能需要使用Statement接口,因为Statement接口支持SQL注入。
PreparedStatement
:相比较Statement的执行效率更高。
Statement
:发送一次SQL语句,编译一次,然后执行一次。
PreparedStatement
:发送一次SQL语句,编译一次,然后可以执行N次。
三、预编译增删改查
准备数据
create table person(
id int primary key auto_increment,
name varchar(32) not null,
age int not null
);
insert into person values(null,'貂蝉',22);
insert into person values(null,'秋香',26);
insert into person values(null,'石榴姐',32);
insert into person values(null,'玉环',18);
3.1、添加数据
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取预编译的数据库操作对象
String sql = "insert into person values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给?赋值
preparedStatement.setInt(1,5);
preparedStatement.setString(2,"春香");
preparedStatement.setInt(3,20);
// 执行SQL语句
int count = preparedStatement.executeUpdate();
System.out.println("成功插入" + count + "条语句");
// 释放资源
preparedStatement.close();
connection.close();
}
}
3.2、修改数据
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取预编译的数据库操作对象
String sql = "update person set name =? , age = ? where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置?的值
preparedStatement.setString(1,"春花");
preparedStatement.setInt(2,99);
preparedStatement.setInt(3,5);
// 执行SQL语句
int count = preparedStatement.executeUpdate();
System.out.println("修改成功了" + count + "条数据");
// 释放资源
preparedStatement.close();
connection.close();
}
}
3.3、删除数据
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取预编译的数据库操作对象
String sql = "delete from person where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置?的值
preparedStatement.setInt(1,5);
// 执行SQL语句
int count = preparedStatement.executeUpdate();
System.out.println("成功删除了" + count + "条数据");
// 释放资源
preparedStatement.close();
connection.close();
}
}
3.4、查询数据
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取预编译的数据库操作对象
String sql = "select * from person where name like ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给?赋值
preparedStatement.setString(1,"张%");
// 执行SQL语句
ResultSet resultSet = preparedStatement.executeQuery();
// 处理结果集
while ( resultSet.next() ) {
String name = resultSet.getString("name");
System.out.println("name = " + name);
}
// 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
四、批处理
批处理顾名思义就是将一批的SQL语句同时处理执行,JDBC提供了批处理的机制,用以提升执行效率。
比如大学开学,需要把所有的学生信息录入到学生管理系统中,一条一条的SQL语句执行效率低,而通过批处理的方式可以将一批的SQL语句同时执行。
4.1、Statement实现批处理
成员方法
void addBatch(String sql) | 将给定的SQL命令添加到此 Statement对象的当前命令列表中。 |
int[] executeBatch() | 将一批命令提交到数据库以执行,并且所有命令都执行成功,返回一个更新计数的数组。 |
void clearBatch() | 清空这个 Statement对象当前的SQL命令列表。 |
代码演示
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取Statement对象
Statement statement = connection.createStatement();
// 准备SQL语句
for( int i = 1; i <= 10000;i++ ) {
String sql = "insert into person values(null,'name" + i + "',30)";
// 添加到批处理中
statement.addBatch(sql);
// 每1000条发送一次
if( i % 1000 == 0 ) {
statement.executeBatch();
// 清空批次
statement.clearBatch();
}
}
// 执行批次
statement.executeBatch();
// 释放资源
statement.close();
connection.close();
}
}
4.2、PreparedStatement实现批处理
import java.sql.*;
public class Demo {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
// 获取预编译的数据库操作对象
String sql = "insert into person values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给?赋值
for( int i = 1; i <= 10000; i++ ) {
preparedStatement.setString(1,"name" + i);
preparedStatement.setInt(2,20);
// 添加批次
preparedStatement.addBatch();
}
// 执行批次
preparedStatement.executeBatch();
// 释放资源
preparedStatement.close();
connection.close();
}
}
五、JDBC事务
场景:实现某个需求时,需要一组逻辑上的SQL语句实现
要求在逻辑上的一组(insert、update、delete)SQL语句,在执行时,要么全部成功,要么全部失败。
案例数据
create table account(
id int primary key auto_increment,
name varchar(32) not null unique,
money double not null
);
insert into account values(null,'a',1000);
insert into account values(null,'b',1000);
案例需求
要求a给b转账100元,最后的结果a的金额少100元,b的金额多100元。
update account set money = money - 100 where name = 'a';
update account set money = money + 100 where name = 'a';
没有事务
假设此时第一条SQL语句执行结束之后,发生了异常的情况,导致第二条SQL语句没有执行到,那么不仅需求没有实现,还造成的金钱的损失。
开启事务
即使第一个SQL语句执行结束发生了异常,但是通过事务管理可以实现数据回滚:就是让更改的记录无效,从而避免损失。
5.1、mysql中的事务管理
开启事务语法
start transaction;#此语句后面的所有SQL语句都处在在事务中,更改的内容不会自动提交,需要手动提交
rollback;#事务的回滚,表示全部失败,事务结束,并且数据回复到开始之前的状态
commit;#事务的提交,表示全部成功,事务结束,数据更改
事务回滚
start transaction;#开启事务
update account set money = money - 100 where name = 'a';#a开始转账
# 比如此时发生异常 要事务回滚 --> 事务结束
rollback;
# 回滚后查询数据是否发生变化
select * from account;
事务提交
start transaction;#开启事务
update account set money = money - 100 where name = 'a';#a开始转账
update account set money = money + 100 where name = 'b';#b接收转账
select * from account;#查询结果
#提交事务,更改数据
commit;
5.2、JDBC中的事务管理
JDBC的事务管理,通过Connection对象来完成,当JDBC程序向数据库获取一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL语句在同一个事务中执行,需要通过方法设置。
成员方法
成员方法 | 描述 |
---|---|
void setAutoCommit(boolean autoCommit) | 将此连接的自动提交模式设置为给定状态。// 设置为false表示开启事务 |
void rollback() | 撤消在当前事务中所做的所有更改,并释放此 Connection对象当前持有的任何数据库锁。 |
void commit() | 使上次提交/回滚之后所做的所有更改都将永久性,并释放此 Connection对象当前持有的任何数据库锁。 |
注意:rollback()
方法要放入到catch代码中,发生异常时实现回滚。
代码演示
import java.sql.*;
public class Demo {
public static void main(String[] args) {
// 声明连接对象
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
connection = DriverManager.getConnection(url, user, password);
// 设置事务
connection.setAutoCommit(false);
// 获取预编译数据库操作对象
String sql = "update account set money = money - 100 where name=? ";
preparedStatement = connection.prepareStatement(sql);
// 设置?的值
preparedStatement.setString(1,"a");
// 执行SQL语句
preparedStatement.executeUpdate();
// 产生异常
// System.out.println( 10 / 0 );
String sql1 = "update account set money = money + 100 where name=?";
preparedStatement = connection.prepareStatement(sql1);
// 设置?的值
preparedStatement.setString(1,"b");
// 执行SQL语句
preparedStatement.executeUpdate();
// 提交事务
connection.commit();
}catch (Exception e) {
// 事务回滚
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
try {
if( preparedStatement != null ) {
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if( connection != null ) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
5,3、事务回滚点
案例
当一次性去执行多条(10000)SQL语句的时候,一旦出现异常,有可能会造成10000条数据的全部回滚。这样的情况是极其不合理的。
解决方案
可以设置事务的回滚点,比如每500条执行一批次的SQL语句,即使在发生异常的情况下,只会回滚回滚点之前的数据,而不用回滚所有的数据。
事务回滚点
java.sql.Savepoint
:一个保存点的表示,这是当前事务中可以从Connection.rollback方法引用的一个点。 当事务回滚到保存点时,保存点之后所做的所有更改都将被撤销。
Connection获取回滚点对象
Savepoint setSavepoint()
在当前事务中创建一个未命名的保存点,并返回表示它的新的 Savepoint对象。
Connection设置回滚点
void rollback(Savepoint savepoint)
撤消在给定的 Savepoint对象设置后进行的所有更改。
代码演示
import java.sql.*;
public class Demo {
public static void main(String[] args) {
// 声明连接对象
Connection connection = null;
PreparedStatement preparedStatement = null;
Savepoint savepoint = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
String url = "jdbc:mysql://localhost:3306/dljd?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";
connection = DriverManager.getConnection(url, user, password);
// 开启事务
connection.setAutoCommit(false);
// 马上设置回滚点
savepoint = connection.setSavepoint();
// 获取预编译数据库操作对象
String sql = "insert into person values(null,?,?)";
preparedStatement = connection.prepareStatement(sql);
for( int i = 1; i <= 10000; i++ ) {
// 设置?的值
preparedStatement.setString(1,"name" + i);
preparedStatement.setInt(2,22);
// 执行SQL语句
preparedStatement.executeUpdate();
// 执行到一半时发生异常
if( i == 9527 ) {
System.out.println( 10 / 0 );
}
// 判断
if( i % 500 == 0 ) {
// 设置回滚点
savepoint = connection.setSavepoint();
}
}
// 提交事务
connection.commit();
}catch (Exception e) {
// 事务回滚
try {
connection.rollback(savepoint);
// 提交事务,把回滚点之前的内容进行提交
connection.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
try {
if( preparedStatement != null ) {
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if( connection != null ) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
六、悲观锁和乐观锁
6.1、事务中数据的丢失更新问题
当有两个或多个事务更新同一行数据。但是这些事务彼此之间都不知道其他事务也在进行这行数据的更操作,因此造成第二个更新覆盖了第一个更新。
两个事务同时读取到一条数据记录,A先修改记录,B也跟着修改记录(B不知道A修改过),B提交数据覆盖了A的修改结果。
解决丢失更新问题有两种方案: 悲观锁和乐观锁。
6.2、悲观锁
悲观锁:使用数据库内部锁机制,进行table表单的锁定,在一个事务修改数据时,将数据锁定。此时其他事务就无法对数据进行更新的操作。避免两个事务同时修改。
悲观表示的是一种心里,每次操作数据都悲观的认为会发生数据的丢失更新。
在mysql的事务中,当修改数据时,会自动为数据加锁,防止两个事务同时修改数据。
数据和锁不可分割,锁一定是存在于事务中,当事务关闭锁自动释放。
两个事务同时开启,未修改数据前,都可以给表单查询。
当一个事务对数据修改时,数据被会锁定,另外一个事务默认可以查询数据,但是无法修改数据,必须等到另外一个事务结束后,修改的SQL语句才能被继续执行。
6.3、悲观锁—读锁和写锁
在mysql内部有两种常用锁:读锁和写锁。
读锁
读锁(共享锁):一张表中可以添加多个读锁,当表中被其他事务添加了读锁时,则其他事务无法修改当前表中的数据。语法如下:
select * from table lock in share mode;
共享锁在使用时容易发生死锁的问题。
写锁
写锁(排他锁):在一张表中只能添加一个排它锁,当一个表中添加了排他锁后,那么这个表中就无法在添加其他的排它锁或共享锁。语法如下:
select * from table for update;
总结:共享锁比较容易发生死锁的问题,所以在悲观锁中一般使用排它锁解决丢失更新问题。
排它锁解决丢失更新图解
6.4、乐观锁
乐观锁:使用的不是数据库自带的锁机制,而是使用一个特殊标记字段(时间戳),记录字段的状态和内容,从而判断时间是否发生并发访问。
时间戳使用数据库的timestamp字段表示。
乐观表示假设不会发生丢失更新问题。
时间戳演示
create table blog(
id int primary key auto_increment,
title varchar(40),
updatetime timestamp
);
insert into blog values(null,'动力节点为啥牛',null);#插入数据时,timestamp字段生成当前时间
update blog set title = '上海疫情' where id = 1;#timestamp在修改数据时,自动更新当前时间
时间戳无法自动赋值原因:
在mysql5之中explicit_defaults_for_timestamp默认值是off,在mysql8之中默认值改为了on
查看explicit_defaults_for_timestamp默认值: show global variables like
“explicit_defaults_for_timestamp”;
修改explicit_defaults_for_timestamp默认值: set
@@global.explicit_defaults_for_timestamp=off;
乐观锁解决丢失更新图解
6.5、乐观锁和悲观锁的优缺点
悲观锁:
优点:
- 可以保证数据操作的原子性;
- 在多线程并发情况下,不会出现数据不一致的问题;
- 相对简单易实现。
缺点:
- 在并发量较高的情况下,容易导致性能瓶颈;
- 多线程操作同一数据时,其他线程等待锁的释放,会影响性能;
- 会导致死锁或饥饿现象。
乐观锁:
优点:
- 适用于并发量比较大的场景,不会出现锁等待的情况,性能比较高;
- 不会导致死锁或饥饿现象。
缺点:
- 无法保证数据操作的原子性;
- 在多线程竞争同一数据时,容易出现ABA问题;
- 代码相对复杂,易出错。
在实际开发中,悲观锁和乐观锁的使用情况取决于具体的业务场景和需求。一般情况下,对于读多写少的场景,推荐使用乐观锁,因为乐观锁相对于悲观锁性能更优,而悲观锁适用于写多读少的场景,因为在这种情况下,大部分时间读和写是互斥的。但是,在实际应用中,需要根据具体的业务场景和并发情况进行选择和使用,以达到更好的性能和数据一致性。
七、数据库连接池
数据库连接池:类似于线程池,是用于保存数据库连接对象的容器。
好处:节约资源,访问效率高
javax.sql.DataSource
:一个连接到这个DataSource对象所代表的物理数据源的工厂。
对于DriverManager设备的替代方案, DataSource对象是获取连接的首选方法。
DataSource接口由驱动程序供应商实现。类似于数据库的驱动jar包,都是由数据库厂商实现,并提供对饮的jar包。
成员方法
成员方法 | 描述 |
---|---|
Connection getConnection() | 尝试建立与此 DataSource对象所代表的数据源的连接。 |
void close() | 在数据连接池中表示的不是释放资源,而是回收资源到数据库连接池中。 |
常用的数据库连接池
C3P0:是一个开源的JDBC连接池,在开发中使用较少。
Druid:由阿里巴巴提供的数据库连接池技术,开发中常用。
数据库连接池的使用步骤
- 在项目下创建lib目录,先导入数据库的驱动jar包,再导入数据库连接池jar包。
- 设置修改数据库连接池的配置文件。配置文件直接放在src目录下,可以直接加载
- 创建数据库连接池对象。
- 通过数据库连接池对象获取数据库的连接对象。
- 按照前面讲解jdbc的操作过程继续操作(获取statement对象,发送SQL语句,处理结果…)
- 归还数据库连接对象。
7.1、C3P0数据库连接池
准备工作
-
将c3p0的3个jar包保存到lib目录下,并添加。(要添加mysql数据库驱动jar包)
-
修改xml配置文件信息。
配置文件的名称必须是:c3p0.properties 或者 c3p0-config.xml
创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
代码演示
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo {
public static void main(String[] args) throws SQLException {
// 创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
// 获取连接对象
Connection connection = ds.getConnection();
System.out.println("connection = " + connection);
// 获取statement对象
Statement statement = connection.createStatement();
// 发送SQL语句
String sql = "select * from person";
ResultSet resultSet = statement.executeQuery(sql);
// 处理结果
while (resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
// 归还连接对象
connection.close();
}
}
7.2、Druid数据库连接池
Druid是阿里巴巴开源的一个数据源连接池框架,支持MySQL、Oracle、PostgreSQL、Microsoft SQL Server等关系型数据库。
使用Druid连接池,可以提高应用程序对数据库的访问效率和性能。
准备工作
- 将Druid数据库连接池的jar包添加到lib目录下(还有mysql数据库驱动jar包)。
- 将配置文件放到项目路径下(配置文件的名字可自定义)
- Druid数据库的jar包中有一个数据库连接池的工厂类:
DruidDataSourceFactory
,通过类中的createDateSource()
方法创建数据库连接池对象。
通过工厂类获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
代码演示
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws Exception {
// 创建属性集对象
Properties pro = new Properties();
// 读取配置数据到属性集中
pro.load(new FileReader("src//druid.properties"));
// 通过工厂类获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
// 获取连接对象
Connection connection = ds.getConnection();
System.out.println("connection = " + connection);
// 获取statement对象
Statement statement = connection.createStatement();
// 发送SQL语句
String sql = "select * from person";
ResultSet resultSet = statement.executeQuery(sql);
// 处理结果
while (resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
// 归还连接对象
connection.close();
}
}
7.2.1 Druid工具类封装
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/*
Druid工具类:
1.类加载完成实现:配置文件加载和连接池对象的初始化
2.返回DateSource连接池对象方法
3.返回Connection连接对象方法
4.释放资源和归还连接对象方法
*/
public class DruidUtils {
private static DataSource ds;
static {
// 读取配置数据到属性集中
try {
// 创建属性集对象
Properties pro = new Properties();
pro.load(new FileReader("src//druid.properties"));
// 通过工厂类获取连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
// 返回DateSource连接池对象方法
public static DataSource getDataSource() {
return ds;
}
// 返回Connection连接对象方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 释放资源和归还连接对象方法
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if( resultSet != null ) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( statement != null ) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( connection != null ) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
7.2.2 Druid工具类的使用
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Test {
public static void main(String[] args) {
// 声明对象
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 获取连接
connection = DruidUtils.getConnection();
// 获取预编译对象
String sql = "select * from person where name like ?";
preparedStatement = connection.prepareStatement(sql);
// 给?号赋值
preparedStatement.setString(1,"%香");
// 执行SQL语句
resultSet = preparedStatement.executeQuery();
// 处理结果
while (resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
}catch (Exception e) {
e.printStackTrace();
}finally {
DruidUtils.close(resultSet,preparedStatement,connection);
}
}
}
7.3、JdbcTemplate对象
Spring框架对jdbc实现了封装,通过JdbcTemplate简化JDBC的操作
public class Demo05_JdbcTemplate {
public static void main(String[] args) {
// 创建JDBCTemplate对象
JdbcTemplate template = new JdbcTemplate(DruidUtils.getDs());
// 1.添加
String sql1 = "insert into account values(null,?,?)";
int count = template.update(sql1, "梁萧", 13500);
System.out.println("count = " + count);
// 2.修改
String sql2 = "update account set money = 18000 where name = ?";
template.update(sql2, "梁萧");
// 3.删除
String sql3 = "delete from account where id = ?";
template.update(sql3, 1);
// 4.查收单条数据
String sql4 = "select * from account where id = ?";
Map<String, Object> map = template.queryForMap(sql4, 3);
System.out.println(map);
// 5.查询多条数据
String sql5 = "select * from account";
List<Map<String, Object>> list = template.queryForList(sql5);
for (Map<String, Object> map : list) {
System.out.println(map);
}
// 聚合函数的查询
String sql6 = "select sum(money) from account";
Double moneySum = template.queryForObject(sql6, double.class);
System.out.println("moneySum = " + moneySum);
}
}
JdbcTemplate对象常用方法如下:
常用方法 | 描述 |
---|---|
execute() | 执行任何可以表示为SQL语句的操作,如DDL(数据定义语言)和DML(数据操作语言)操作。 |
update() | 执行DML操作,如INSERT、UPDATE、DELETE等,并返回受影响的行数。 |
batchUpdate() | 批量执行DML操作,如INSERT、UPDATE、DELETE等,并返回一个数组,数组中的每个元素表示在执行相应操作时受影响的行数。 |
query() | 执行SELECT查询操作,并返回一个包含结果集的ArrayList对象。 |
queryForObject() | 执行SELECT查询操作,并返回结果集中的第一行第一列的值。 |
queryForList() | 执行SELECT查询操作,并返回一个包含结果集的List对象。 |
queryForMap() | 执行SELECT查询操作,并返回一个包含结果集的Map对象。 |
queryForRowSet() | 执行SELECT查询操作,并返回一个包含结果集的SqlRowSet对象。 |
queryForInt() | 执行SELECT查询操作,并返回结果集中的第一行第一列的整数值。 |
queryForLong() | 执行SELECT查询操作,并返回结果集中的第一行第一列的长整型值。 |
queryForDouble() | 执行SELECT查询操作,并返回结果集中的第一行第一列的双精度浮点型值。 |
queryForList() | 执行SELECT查询操作,并将结果集中的每行记录映射成指定的Java对象,并返回一个包含这些Java对象的List对象。 |