JDBC
- 一、JDBC
- (一)什么是JDBC
- (二) 操作步骤
- 二、statement对象执行sql语句
- (一)参数配置
- (二)建立连接
- (三)操作数据库
- (1)增
- (2)删
- (3)改
- (4)查
- 三、PrepareStatement对象执行sql语句
- (一)参数配置
- (二)建立连接
- (三)操作数据库
- (1)增
- (2)查
- 四、statement和PreparedStatement对象
- (一)什么是sql注入异常
- (二)演示sql注入异常
- (1)Statement对象测试
- (2)PreparedStatement对象测试
- 五、JDBC操作事务
- (一)流程
- (二)代码实现
- 六、连接池
- (一)为什么有连接池
- (二)工作原理
- (三)使用流程
- (四)代码
一、JDBC
(一)什么是JDBC
- 通过Java代码如何操作Mysql C/S 客户端服务器模型
- JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,
是一个规范而不是一个实现。各种不同类型的数据库都依这种规范有相应的实现,
都是由java类和接口组成。
我们所要学习的是通过jdbc连接MySQL数据库。
(二) 操作步骤
(1)导入jdbc的Mysql实现依赖
(2)参数配置
jdbcUrl :唯一确定数据库 以及编码形式 中文
jdbc:mysql://127.0.0.1:3306/school?
useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
jdbc:mysql :和mysql建立连接时使用的协议
127.0.0.1:3306 :mysql服务器所在的ip地址和端口号
ip地址:唯一的确定mysql服务器运行的主机 127.0.0.1 当前主机
端口号:能够确定mysql服务器是这台主机的哪一个进程 3306 mysql默认端口
IP 和 端口号帮助我们确定mysql服务器的位置。
school : 要操作就是数据库的名字。
userName=root
password=123456
jdbcDriver=com.mysql.jdbc.Driver jdbc驱动
(3)加载驱动 驱动加载好之后就可以使用jdbc了
(4)建立连接
(5)创建操作数据库对象 statement /PreparedStatement
(6)使用statement /PreparedStatement对象,执行sql语句
二、statement对象执行sql语句
(一)参数配置
private String url = "jdbc:mysql://127.0.0.1:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
private String userName = "root";
private String password = "111111";
private String jdbcDriver = "com.mysql.jdbc.Driver"; //jdbc 驱动
private Connection connection;
private Statement statement;
(二)建立连接
public void createConnection() {
try {
Class.forName(jdbcDriver);
try {
connection = DriverManager.getConnection(url, userName, password);
//getConnection:就是jdbc提供给我们建立连接的方法 登录到mysql服务器的过程
if (!connection.isClosed()) {
System.out.println("连接建立成功");
statement = connection.createStatement();//创建操作数据库对象
} else {
System.out.println("连接建立失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
(三)操作数据库
(1)增
public void insertTest() { //update delete
String insertSql = "insert into test values(10,'财经大学')";
try {
statement.executeUpdate(insertSql);
//executeUpdate所有会对数据库内容造成修改的sql语句都是调用这个方法执行的
} catch (SQLException e) {
e.printStackTrace();
}
}
(2)删
public void deleteTest() {
String deleteSql = " delete from test where id = 10";
try {
statement.executeUpdate(deleteSql);
} catch (SQLException e) {
e.printStackTrace();
}
}
(3)改
public void updateTest() {
String updateSql = " update test set name = '红黑树' where id = 10";
try {
statement.executeUpdate(updateSql);
} catch (SQLException e) {
e.printStackTrace();
}
}
(4)查
public void selectTest() {
String selectSql = "select * from test";
try {
ResultSet resultSet = statement.executeQuery(selectSql); //resultSet 查询到的结果集的集合
//executeQuery:执行查询数据的SQL语句专用
while (resultSet.next()) {//resultSet.next() 判断集合是否有数据
System.out.println(" id " + resultSet.getString(1)
+ " name " + resultSet.getString(2)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
三、PrepareStatement对象执行sql语句
(一)参数配置
private String url = "jdbc:mysql://127.0.0.1:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
private String userName = "root";
private String password = "111111";
private String jdbcDriver = "com.mysql.jdbc.Driver"; //jdbc 驱动
private Connection connection;
(二)建立连接
public void createConnection() {
try {
Class.forName(jdbcDriver);
try {
connection = DriverManager.getConnection(url, userName, password);
//getConnection:就是jdbc提供给我们建立连接的方法 登录到mysql服务器的过程
if (!connection.isClosed()) {
System.out.println("连接建立成功");
} else {
System.out.println("连接建立失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
(三)操作数据库
(1)增
public void insertTest() {
String insertSql = "insert into test values(?,?)"; //定义无参的sql语句
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(insertSql);//将无参数的sql语句提交给mysql
//给无参数的sql语句提交参数
pre.setInt(1, 201);
pre.setString(2, "红");
pre.executeUpdate(); //执行这条sql
pre.setInt(1, 202);
pre.setString(2, "黑");
pre.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (pre !=null){
try {
pre.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)查
public void selectTest() {
String selectSql = "select * from test where id > ?";
try {
PreparedStatement pre = connection.prepareStatement(selectSql);
pre.setString(1, "201");
ResultSet resultSet = pre.executeQuery();
while (resultSet.next()) {//resultSet.next() 判断集合是否有数据
System.out.println(
"id " + resultSet.getString(1) +
" name " + resultSet.getString(2)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
四、statement和PreparedStatement对象
- 有了statement对象,我们为什么还要学习PreparedStatement对象?
回答:
1、当执行多条同结构的sql语句时,PreparedStatement对象会先提交一个无参数的sql语句进进行编译。
例如:insert into test values(?,?);
2、同结构的sql语句执行多次,PreparedStatement对象的执行效率要比statment对象高的多。
原因:PreparedStatement对象对于同结构的sql只编译一次。而statment对象有几条sql就需要执行几次。
一个结构的sql语句只执行一次,statment对象更好。原因是:statment对象执行sql时
编译和执行一次就完成。而PreparedStatement对象先编译无参数的sql,再提交参数
然后再执行。
3、实际操作时,更多使用的是PreparedStatement对象。原因就是statment对象容易产生sql注入异常
(一)什么是sql注入异常
- 结构化查询语言(SQL)是一种用来和数据库交互的文本语言。SQL Injection 就是利用非法的SQL拼接,从而达到入侵数据库的目的。 它的产生主要是由于程序对用户输入的数据没有进行严格的过滤,导致非法的数据库SQL操作语句的执行。 SQL 注入(SQL Injection)攻击具有很大的危害, 攻击者可以利用它读取、修改或者删除数据库内的数据, 获取数据库中的用户名和密码等敏感信息,甚至可以获得 数据库管理员的权限,而且SQL Injection 也很难防范, 一般的防火墙也无法拦截 SQL Injection 攻击。
PreparedStatement对象 能够有效的防止sql注入的攻击。
(二)演示sql注入异常
- 创建表 ,插入数据如下:
(1)Statement对象测试
public void login1(String name,String password){
String sql1 = "select * from user where name ='"+ name +"'and password ='"+ password+"'";
try {
ResultSet resultSet = statement.executeQuery(sql1);
if (resultSet.next()){
System.out.println("登陆成功");
}else {
System.out.println("登陆失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
- 我们先正常登录看看:
- sql注入异常
我们看到密码是拼装而成的,并不是正确的密码,但为什么会登陆成功
分析:password:" 123’ or ’ 1=1" 我们看到1 = 1这是对的,因为加上了or,所以" 123’ or ’ 1=1" 是一条永真的sql语句,那么肯定会进入到if语句里面,从而显示登陆成功!
(2)PreparedStatement对象测试
public void login2(String name,String password){
String sql2 = "select * from user where name = ? and password = ?";
try {
PreparedStatement pre = connection.prepareStatement(sql2);
pre.setString(1,name);
pre.setString(2,password);
ResultSet resultSet = pre.executeQuery();
if (resultSet.next()){
System.out.println("登陆成功");
}else {
System.out.println("登陆失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
- 我们先正常登录看看:
- sql注入异常
- 我们发现拼装密码对PreparedStatement对象没用,那PreparedStatement对象是怎么阻止拼装密码或者说sql注入异常的呢?
代码实践:我们在PreparedStatement对象代码加一行代码,打印看看拼装后的sql语句
public void login2(String name,String password){
String sql2 = "select * from user where name = ? and password = ?";
try {
PreparedStatement pre = connection.prepareStatement(sql2);
pre.setString(1,name);
pre.setString(2,password);
System.out.println(pre);//加这行代码
ResultSet resultSet = pre.executeQuery();
if (resultSet.next()){
System.out.println("登陆成功");
}else {
System.out.println("登陆失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
打印结果:
结果分析:
PreparedStatement对象对把拼装密码看成一个整体,并不会因为or的存在而把拼装密码分开来判断,从而阻止了sql注入异常!
这也就是为什么实际操作时,更多使用的是PreparedStatement对象,PreparedStatement对象不会出现这种情况。
五、JDBC操作事务
(一)流程
(1)开启事务 SET AUTOCOMMIT = 0;
connection.setAutoCommit(false);
(2)设置事务的隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); //设置成序列化
(3)回滚 rollback
connection.rollback();
(4)提交 commit
connection.commit();
(5)设置保存点 和 退回保存点 测试
SAVEPOINT point1 (保存点名字)
Savepoint point1 = connection.setSavepoint("p1"); point1就是保存到的队形
ROLLBACK TO point1
connection.rollback(point1);
(二)代码实现
public class JDBCTranTest {
private String url = "jdbc:mysql://127.0.0.1:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
private String userName = "root";
private String password = "111111";
private String jdbcDriver = "com.mysql.jdbc.Driver"; //jdbc 驱动
private Connection connection;
PreparedStatement selectpre = null;
PreparedStatement insertpre = null;
public void createConnection() {
try {
Class.forName(jdbcDriver);
try {
connection = DriverManager.getConnection(url, userName, password);
//getConnection:就是jdbc提供给我们建立连接的方法 登录到mysql服务器的过程
if (!connection.isClosed()) {
System.out.println("连接建立成功");
connection.setAutoCommit(false);// 1、开启事务
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);//2、设置事务隔离级别
} else {
System.out.println("连接建立失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void selecttest(){
String sql = "select * from test";
try {
selectpre = connection.prepareStatement(sql);
ResultSet resultSet = selectpre.executeQuery();
while (resultSet.next()){
System.out.println("id"+resultSet.getString(1)+
"name"+resultSet.getString(2));
}
}catch (SQLException e){
e.printStackTrace();
}
}
public void inserttest(){
String sql = "insert into test values(?,?)";
try {
insertpre = connection.prepareStatement(sql);
insertpre.setInt(1,3);
insertpre.setString(2,"小王");
insertpre.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
public void inserttest1(){
String sql = "insert into test values(?,?)";
try {
insertpre = connection.prepareStatement(sql);
insertpre.setInt(1,4);
insertpre.setString(2,"王小");
insertpre.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
/**
* 回滚方法的测试
*/
public void rollbackTest(){
System.out.println("对回滚方法的测试:");
System.out.println("初始数据:");
selecttest();
System.out.println("----------------------------------");
inserttest();
System.out.println("插入数据,但此时并未回滚:");
selecttest();
System.out.println("----------------------------------");
try {
connection.rollback();
System.out.println("回滚之后:");
selecttest();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交方法的测试
*/
public void commitTest(){
System.out.println("对提交方法的测试:");
System.out.println("初始数据:");
selecttest();
System.out.println("-------------------------------");
inserttest();
try {
//插入数据,提交以后
connection.commit();
System.out.println("插入数据,提交以后");
selecttest();
System.out.println("---------------------------------");
connection.rollback();
//再次回滚,发现数据已经永久性修改
System.out.println("回滚,发现数据已经永久性修改");
selecttest();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 设置保存点和退回到保存点测试方法
*/
public void savePointTest(){
System.out.println("初始数据:");
selecttest();
System.out.println("---------------------------------");
try {
System.out.println("第一次插入数据(在此处设置保存点point1):");
inserttest();
Savepoint point1 = connection.setSavepoint();
selecttest();
System.out.println("---------------------------------");
System.out.println("第二次插入数据(未设置保存点):");
inserttest1();
selecttest();
System.out.println("----------------------------------");
System.out.println("此时回退到保存点point1,发现第二次插入的数据并未保存:");
connection.rollback(point1);
selecttest();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeConnection(){
try {
connection.close();
selectpre.close();
insertpre.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JDBCTranTest jdbcTranTest = new JDBCTranTest();
jdbcTranTest.createConnection();
jdbcTranTest.rollbackTest();
jdbcTranTest.closeConnection();
}
}
这里我就不一一测试了,测试下事务回滚,结果如下:
六、连接池
(一)为什么有连接池
- 普通的JDBC请求MySQL链接当完成所有SQL请求任务之后,MySQL连接就会关闭。这样一来MySQL的连接就变成了单次使用,每次使用前都需要重新创建,使用之后都需要将链接关闭。如果此时有多个用户使用JDBC的话,就会出现连接频繁的创建和关闭的过程。而链接池的出现就是为了让链接能够重复使用。
(二)工作原理
- 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。当用户第一次获取连接时,会先请求一定数量(初始链接数量)链接池中,并且会记录每个连接的初始(未使用)时间,然后再将链接池中的连接给有需要的用户。当缓冲池中初始连接用完了,链接池会判断所创建的连接数量是否大于最大链接数如果不大于则可以继续创建连接给请求的用户使用(创建的新连接的个数可自己控制,链接池中有相应的增长参数)。如果已经到到最大链接数则当前用户需要等待直到有用户使用完之后关闭连接,这个用户才可以重复使用。当我们开始使用链接池的时候链接池内部就会进行计时(超时时间),每隔空闲链接的存活时间就会对链接池内部的连接进行一次清理。把所有(当前时间 - 链接最后使用的时间 )> 空闲链接的存活时间的连接释放掉。
(三)使用流程
(1)导入依赖
(2)参数配置
(3)获取链接
(4)创建发送SQL请求的对象,进行相关操作
(5)关闭连接
(四)代码
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
public class PollService {
private final String jdbcDriver = "com.mysql.jdbc.Driver";
private final String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
private final String userName = "root";
private final String password = "111111";
private final int initSize = 5; //初始连接数量
private final int maxSize = 10; //最大连接数量
private final int minSize = 2; //最小连接数量
private final int acq = 2; //增长因子
private final int idle = 60; //单位秒 空闲时间
// c3p0连接池的数据对象
private final ComboPooledDataSource dataSource = new ComboPooledDataSource();
PollService(){
try {
dataSource.setDriverClass(jdbcDriver);
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(userName);
dataSource.setPassword(password);
dataSource.setInitialPoolSize(initSize); //初始连接数量
dataSource.setMaxPoolSize(maxSize); //最大连接数量
dataSource.setMinPoolSize(minSize); //最小连接数量
dataSource.setAcquireIncrement(acq); //增长因子
dataSource.setMaxIdleTime(idle); //空闲时间
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
//给用户调用的获取连接的接口
public Connection getConnection() {
Connection con = null;
try {
con = dataSource.getConnection(); //核心代码
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
}
- 测试代码
public class app{
public static void main(String[] args) {
PollService service = new PollService();
Connection connection = service.getConnection(); //从连接池获取连接
PreparedStatement preparedStatement = null;
String sql = "select name,password from user where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);//无参数的sql进行编译
preparedStatement.setInt(1,1 );
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println("name: " + resultSet.getString(1)
+" password"+resultSet.getString(2));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
preparedStatement.close();
connection.close(); //并没有将连接真的关闭掉 只是将连接退回到连接池当中
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 运行结果