JDBC的概念
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
JDBC本质上是一个接口,是一种规范.
这里我以Mysql数据库为例子:
通俗来讲就是使用Java代码来访问数据库,对表进行增删改查的操作.
入门案列HelloWorld
1.首先准备数据库中数据
-- 创建hello表
CREATE TABLE hello(
id INT,
`name` VARCHAR(10)
);
-- 添加数据
INSERT INTO hello VALUES(1,'hello');
INSERT INTO hello VALUES(2,'world');
2.导入jar包
https://mvnrepository.com/ 可以在这个maven网站去下载
mysql-connector-java-5.1.37-bin.jar
3.编写java代码
//第一个jdbc程序
public class HelloWorld {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/*
第一步:注册驱动 -- 在sql5版本后,这个步奏可以省略,因为在jar包下有个Driver配置文件,已经帮我们注册了
本身没在内存里面,需要加载才能使用,
什么情况触发类加载
1.new
2.使用静态方法
3.加载子类的时候,父类也会加载
*/
Class.forName("com.mysql.jdbc.Driver");
/*
第二步:获取连接对象 -- 通过DriverManager的方法获取
jdbc:mysql 是一种协议和http类似 通过这个协议,就知道是jdbc和mysql的连接
192.168.93.132 是安装mysql的服务器的ip地址 3306 是对应的端口号,jdbc表示你要操作的那个库,后面的是mysql用户名密码
*/
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.93.132:3306/jdbc", "root", "root");
/*
第三步:获取语句执行对象
通过Connection对象获取语句对象
作用:
封装sql语句,
执行sql语句
1.DML:结果是影响的行数 int类型
2.Dql:结果是resultSet 结果集
*/
Statement statement = connection.createStatement();
/*
第四步:获取返回的结果集
执行查询语句,得到结果集对象
作用:封装mysql服务器响应的数据
*/
ResultSet resultSet = statement.executeQuery("select * from hello");
/*
循环打印每个结果
resultSet.next()方法是返回一个Boolean的值,如果结果集中有下一行数据,就返回true
*/
while (resultSet.next()) {
System.out.print(resultSet.getInt("id") + " " + resultSet.getString("name") + "\n");
}
//第五步:释放资源
statement.close();
resultSet.close();
connection.close();
}
}
4.执行结果
总结Jdbc入门案列
由上面的代码得出jdbc的通用套路分为以下几步
1.通过类加载器注册驱动
-- 在sql5版本后,这个步奏可以省略,因为在jar包下有个Driver配置文件,已经帮我们注册了
2.获取连接对象
通过DriverManager.getConnection(参数详见代码);获取到连接对象Connection
3.通过连接对象得到执行语句对象
通过connection.createStatement();得到Statement执行语句对象
4.通过statement对象的以下两个方法执行语句
statement.executeQuery(sql语句);-->这是执行的是DQL语句,返回的是ResultSet结果集,这个结果集相当于List<Map<String,Object>>
statement.executeUpdate(sql语句);-->这是执行的是DML语句,返回的是影响的行数
图示:
解决SQL注入
从上面的入门案列中我们使用的执行语句对象是statement,这个对象在执行sql语句的时候是对sql语句进行字符串拼接,所以会存在sql注入风险
PreparedStatement
它的父接口是Statement,Prepared准备好的SQL语句。MySQL中的语句也是要编译以后才执行,编译是需要消耗时间的。
比父接口更强大的地方:
1.SQL语句会预先编译,执行效率会更高。
2.解决SQL注入的问题,更安全
3.SQL语句代码的可读性更好,所有要替换的参数使用占位符,占位符是?问号
/**
* 模拟登录解决sql注入的问题,对入门案列的改造
*/
public class LoginUser {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/*
第一步:注册驱动 -- 在sql5版本后,这个步奏可以省略,因为在jar包下有个Driver配置文件,已经帮我们注册了
*/
Class.forName("com.mysql.jdbc.Driver");
/*
第二步:获取连接对象 -- 通过DriverManager的方法获取
*/
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.93.132:3306/jdbc", "root", "root");
/*
第三步:获取预编译语句执行对象
通过Connection对象获取预编译语句对象
*/
//定义两个字符串来模拟用户传递的用户名密码,
String username = "user";
String password = "mima";
//通过连接对象获取预编译语句对象
PreparedStatement pst = connection.prepareStatement("select * from user where username=? and password=?");
//传递参数:参数1表示语句中的第一个问号,2就是第二个问号
pst.setString(1,username);
pst.setString(2,password);
//执行查询方法,返回结果集
ResultSet resultSet = pst.executeQuery();
/*
第四步:获取返回的结果集
执行查询语句,得到结果集对象
作用:封装mysql服务器响应的数据
*/
while (resultSet.next()) {
//如果查询出有结果集,表示有这个用户,就打印登录成功
System.out.println("登录成功");
}
//第五步:释放资源
pst.close();
resultSet.close();
connection.close();
}
}
JDBC对事务的管理
通过连接对象中的方法来实现事务管理:注意事务管理的连接对象要是同一个
Connection接口中与事务有关的方法:
void setAutoCommit(boolean autoCommit) 设置事务的提交方式true 表示自动提交false 表示手动提交如果要开启事务,设置为false
void commit() 提交事务
void rollback() 回滚事务
目标
使用JDBC来处理事务:实现银行转账的操作
1.数据准备
-- 准备数据,创建账户表
CREATE TABLE `account` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`money` DOUBLE DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- 添加数据
INSERT INTO account VALUES(NULL,'小付',1000),(NULL,'小花',1000);
表中的数据:
JDBC控制事务模拟转账代码:
public class ZhuanZhang {
public static void main(String[] args) throws Exception {
//调用转账方法,让A给B转100元钱
zhuanQian("小付","小花",900D,1100D);
}
/**
*假设2个人都是1000 元
* @param nameA 转钱的用户
* @param nameB 收钱的用户
* @param moneyA 模拟转多少钱后更新表中的钱
* @param moneyB 模拟收多少钱后更新表中的钱
*/
public static void zhuanQian(String nameA,String nameB,Double moneyA,Double moneyB){
Connection connection = null;
//获取连接对象
try {
connection = DriverManager.getConnection("jdbc:mysql://192.168.93.132:3306/jdbc", "root", "root");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
//开启事务 false表示关闭自动提交事务
connection.setAutoCommit(false);
//得到预编译语句对象,先执行减钱的一方
PreparedStatement pstA = connection.prepareStatement("UPDATE account SET money = ? WHERE NAME = ?");
pstA.setDouble(1,moneyA);
pstA.setString(2,nameA);
pstA.executeUpdate();
//得到预编译对象,执行加钱的一方
PreparedStatement pstB = connection.prepareStatement("UPDATE account SET money = ? WHERE NAME = ?");
pstB.setDouble(1,moneyB);
pstB.setString(2,nameB);
pstB.executeUpdate();
//执行到这里说明都没有问题就提交事务
connection.commit();
} catch (SQLException throwables) {
//说明sql语句出现异常,就回滚事务,让数据回到最初的时候
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
} finally {
//释放资源
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
执行代码后数据库信息:
我们先还原数据库的数据,都为1000元:
然后在代码中制造一个异常:
//得到预编译语句对象,先执行减钱的一方
PreparedStatement pstA = connection.prepareStatement("UPDATE account SET money = ? WHERE NAME = ?");
pstA.setDouble(1,moneyA);
pstA.setString(2,nameA);
pstA.executeUpdate();
//制造异常 制造一个通用异常
int a = 1/0;
//得到预编译对象,执行加钱的一方
PreparedStatement pstB = connection.prepareStatement("UPDATE account SET money = ? WHERE NAME = ?");
pstB.setDouble(1,moneyB);
pstB.setString(2,nameB);
pstB.executeUpdate();
执行发现数据库中的数据没有任何变化,控制台爆出异常
然后查询数据库发现数据并没有发生改变
所以我们发现当代码执行中出现异常,进行了事务回滚.