JDBC
JDBC:Java DataBase Connection ,是Java连接数据库的标准,数据库工具、数据库框架的底层原理是jdbc。
它提供了一系列的接口,目前市场上的数据库系统有很多,不管是何种数据库,如果想要使用Java进行连接的话,就必须对JDBC提供的这些接口进行实现,从而可以使用Java进行连接。比如MySQL数据库,需要使用Java进行连接的话,就必须提供实现了JDBC一系列接口的程序,而往往这些程序会进行打包成jar文件,也就是常说的架包或者叫做类库,而通常我们也称这些程序为连接数据库的驱动程序。
JDBC分类
JDBC分类有三类:
- JDBC-ODBC:sun公司推出的最早的Java连接数据库的标准,使用是桥连接的模式,想要使用Java连接数据库,就必须经过ODBC这个桥梁,效率就比较低了。所以现在来说在开发中是绝对不会使用的。
- JDBC连接:sun公司推出的第二代Java连接数据库的技术,提供了一套标准的Java连接数据库的标准接口,数据库想要使用Java进行连接,就必须提供实现了这套标准接口的类库。
- JDBC网络连接:表示的是连接网络上的数据库。JDBC网络连接和 JDBC连接是一回事,数据库连接别人的,要求在同一局域网内,并关闭防火墙。真正项目开发中,是连接网络上的数据库。教学才会连接本地的。
java连接数据库
JDBC的核心接口:
Connection : 此接口的对象是数据库连接对象
Statement:数据库操作对象
PreaparedStatement : 预处理(预编译)对象
ResultSet : 查询结果集对象
DriverManager : 驱动操作类(驱动管理器)
使用JDBC连接数据库的步骤:
1.将驱动程序添加编译到当前的工程中,将jar包放到lib下并Build Path编译它。动态WEB工程会自动编译jar包。
右击java project工程名,new个Folder并命名为lib,将jar包放入lib,右击jar包,Build Path,Add to Build Path
编译后会在Referenced Libraries目录下有刚才那个放到lib目录下的驱动jar包。
2.加载驱动程序,Class.forName(“com.mysql.jdbc.Driver”);
3.使用DriverManager(驱动管理器)获取数据库连接,
conn=DriverManager.getConnection(url,user,password);
先得定义conn对象,Connection conn = null;在方法中局部变量没有默认值,要赋初始值null
4.进行数据库增删改查操作(定义sql语句)(Statement(有sql注入问题)|PreparedStatement(预编译的形式,可能需要向sql中的占位符传递值)|CallableStatement),
如果是查询需要使用ResultSet接收查询的结果集。
遍历查询结果集,用if(res.next())只能遍历一笔数据,用while(res.next())可以循环遍历多笔数据,一般用while。如查询登录,是查询一笔数据(2个结果,要么1笔要么0笔)
5.关闭数据库,数据库的连接资源非常有限,使用完成之后需要关闭。
初学者容易忘记将jar包编译到当前工程中。
- java.sql包是JDBC操作包,其中定义的大部分是接口,如果说想要想要连接到MySQL数据库,那么MySQL数据库肯定要对JDBC的标准接口进行实现,那么实现程序呢?需要加入jar包到应用程序中。
-如果大家现在用的是MySQL8.0X 版本,使用这个jar包mysql-connector-java-8.0.13.jar。 - 连接参数的问题,使用JDBC连接MySQL数据库查询结果可能乱码,可能连接出现时区问题等等,都需要加上连接参数。
- MySQL8.0级以上版本的驱动程序不再是com.mysql.jdbc.Driver,而是com.mysql.cj.jdbc.Driver,8.0及以上版本还必须加上时区的参数jdbc:mysql://127.0.0.1:3306/ssm?useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
- 查询结果集乱码需要加的参数是:jdbc:mysql://127.0.0.1:3306/ssm?useUnicode=true&characterEncoding=utf-8
package com.wanbangee.j;
/*1.加载驱动, Class.forName("com.mysql.jdbc.Driver");
2.获取数据库连接,Connection conn=DriverManager.getConnection(url,user,password);
conn = DriverManager.getConnection("jdbc:mysql:///ssm","root","3306");*/
import java.sql.Connection; //全部是SQL包下的
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCDemo01 {
public static void main(String[] args) {
//Connection 是数据库连接的接口,其对象conn就是数据库连接对象
Connection conn = null;
//先得定义conn对象,在方法中局部变量没有默认值,要赋初始值null
try {//反射中,class对象获取有三种方式,1对象.class 2类名.class 3Class.forName(全类名)
Class.forName("com.mysql.jdbc.Driver");//加载驱动程序
//驱动jar包的全类名,com.mysql.jdbc包名+Driver驱动类名
//这行代码会出现受检查的异常,需要throws抛异常或try catch捕获异常,这里用捕获异常
conn = DriverManager.getConnection("jdbc:mysql:///ssm","root","3306");
//获取数据库连接,DriverManager是驱动管理器的意思
//这里要将Connection conn变量定义到try外面,主方法里面,否则后面conn.close();就无效
//因为conn变量定义在try中,它的作用域也只有在try后面花括号中
System.out.println(conn);//测试连接
} catch (Exception e) { //上面两行代码都会出现不同异常,所以用最高异常捕获Exception e
e.printStackTrace();
} finally {
try {//关闭数据库是受检查的异常,用try catch捕获异常
conn.close();//关闭数据库
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCDemo02 {
/*先定义几个常量,常量字母全部要大写,DRIVERCLASS就是加载驱动程序的路径
URL就是要访问哪个数据库,jdbc:mysql://localhost:3306/ssm,如果访问的是本地数据库,端口号默认3306,
这里localhost:3306(或者127.0.0.1:3306)也可省略不写。
USER 就是使用者统一用root。PASSWORD就是数据库登入密码,3306*/
public static final String DRIVERCLASS = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/ssm";
public static final String USER = "root";
public static final String PASSWORD = "3306";
public static void main(String[] args) {
Connection conn = null;
try {
Class.forName(DRIVERCLASS);
conn = DriverManager.getConnection(URL, USER, PASSWORD);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
数据库连接工具类DBUtil:
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBUtil {
public static final String DRIVERCLASS = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql:///ssm";
public static final String USER = "root";
public static final String PASSWORD = "3306";
//定义个静态方法,通过类名DBUtil就可访问
//这个方法要返回数据库连接对象,所以用Connection类型接收
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName(DRIVERCLASS); //1.加载驱动
conn = DriverManager.getConnection(URL, USER, PASSWORD);//2.获得库连接
} catch (Exception e) {
e.printStackTrace();
}
return conn; //3.返回数据库连接对象
}
}
测试类:
package com.wanbangee.j;
import java.sql.Connection;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtil.getConnection();
System.out.println(conn);
} catch (Exception e) {
}finally { //关闭才用到finally
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
基本的增删该查操作:
使用Statement这个接口进行增删改查操作,如果是查询的需要使用ResultSet接收查询的结果集。如果是进行增删改操作,还需要进行事务的管理。
java代码中的字符串拼接:往字符串里加变量(这个变量是事先定义好的)的操作,先打一对双引号,
在引号里打两个加号,在加号中间加上变量,"+变量+"
String s = "+变量+"; String s = "abcdef"; String s = "abc"+变量+"def";
String name = "lll";
double balanceAfter = 50;
String sql = "update account set balance = "+balanceAfter+" where name = '"+name+"'";
新增:
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCDemo03 {
public static void main(String[] args) {
String userName = "jjm";
String passWord = "jjm123";
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "insert into admin (username,password) values ('"+userName+"','"+passWord+"')"; //2编写sql语句
//String sql = "insert into admin values (1,'m','1')";
state = conn.createStatement();//3取得数据库操作对象
state.execute(sql); //4执行sql语句
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//5关闭顺序,先声明后关闭,后声明先关闭。
state.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "insert into account values('wzx',100)";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
state.execute(sql);//4执行sql
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//5关闭,先用后关,后用先关
state.close();
conn.close();
修改:只需要在数据新增的程序进行sql的修改
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCDemo03 {
public static void main(String[] args) {
String userName = "姜建民";
String passWord = "jjm123";
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "update admin set username = '"+userName+"' where username = 'jjm'";//2编写sql
state = conn.createStatement();//3取得数据库操作对象
state.execute(sql);//4执行sql语句
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//5关闭顺序,先声明后关闭,后声明先关闭。
state.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "update account set balance = 200 where name = 'wzx'";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
state.execute(sql);//4执行sql
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//5关闭,先用后关,后用先关
state.close();
conn.close();
删除:只需要在数据修改的程序进行sql的修改
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "delete from admin where username = '姜建民'";//2编写sql
state = conn.createStatement();//3取得数据库操作对象
state.execute(sql);//4执行sql语句
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//5关闭顺序,先声明后关闭,后声明先关闭。
state.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "delete from account where name = 'wzx'";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
state.execute(sql);//4执行sql
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//5关闭,先用后关,后用先关
state.close();
conn.close();
查询:需要使用ResultSet接收查询的结果集。
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
ResultSet res = null;//声明数据库查询操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "select username,password from admin";//2编写sql
state = conn.createStatement();//3取得数据库操作对象
res = state.executeQuery(sql);//4执行查询,并返回查询结果集
while(res.next()) { //5遍历结果集,取得并且判断是否有下一个结果
//通过查询结果集中的数据列名称获取值,如果查询的列有别名的话,则使用别名获取。
System.out.println(res.getString("username")+"======"+res.getString("password"));
//通过查询结果集中数据列的位置获取值,索引从1开始,这种方式的sql语句不建议使用
//System.out.println(res.getString(1)+"======"+res.getString(2));
//数据库中索引是从1开始的,这里查询两列信息select username,password 所以下标为1和2
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//关闭顺序,先声明后关闭,后声明先关闭。
res.close();
state.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
ResultSet res = null;//声明数据库查询操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "select name,balance from account";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
res = state.executeQuery(sql);//4执行sql,并返回查询结果集
while(res.next()) {//5遍历结果集,取得并且判断是否有下一个结果
//System.out.println(res.getString(1)+"====="+res.getString(2));
//通过查询结果集中数据列的位置获取值,索引从1开始,这种方式的sql语句不建议使用
//数据库中索引是从1开始的,这里查询两列信息select name,balance 所以下标为1和2
System.out.println(res.getString("name")+"======"+res.getString("balance"));
//通过查询结果集中的数据列名称获取值,如果查询的列有别名的话,则使用别名获取。
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//6关闭,先用后关,后用先关
res.close();
state.close();
conn.close();
String name = "mpp";
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
ResultSet res = null;//声明数据库查询操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "select name,balance from account where name = '"+name+"'";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
res = state.executeQuery(sql);//4执行sql,并返回查询结果集
while(res.next()) {//5遍历结果集,取得并且判断是否有下一个结果
if(res.getDouble("balance") > 60) {
System.out.println("余额充足");
}else {
System.out.println("余额不足");
}
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//6关闭,先用后关,后用先关
res.close();
state.close();
conn.close();
以上完成了增删改查操作,注意的是:查询需要使用ResultSet接收查询的结果集,对于Connection、Statement和ResultSet 都需要进行关闭。
预处理操作(重点,核心)
预处理:预先执行sql语句,而后将sql语句中需要传入的值一个一个的设置上。如果sql语句中有需要传入的参数,我们使用?作为占位符,执行了sql语句之后,再单独的设置每个问号的值。
预处理的操作对象是PreparedStatement
遍历查询结果集,用if(res.next())只能遍历一笔数据,用while(res.next())可以循环遍历多笔数据,一般用while。如查询登录,是查询一笔数据(2个结果,要么1笔要么0笔)
使用PreparedStatement这个接口进行增删改查操作,如果是查询的需要使用ResultSet接收查询的结果集。如果是进行增删改操作,还需要进行事务的管理。
登录的查询:
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo03 {
public static void main(String[] args) {
String userName = "mpp";
//String passWord = "mpp123";
String passWord = "'mpp12' or 1=1";
//这就是sql注入问题,或 or 0 = 0 只要后面跟上个恒等的表达式,
//无论输入什么错误密码,只要用户名对了,它就能登入成功。反之用户名也一样。
Connection conn = null;
Statement state = null;
ResultSet res = null;
try {
conn = DBUtil.getConnection();
//String sql = "select count(*) from admin where username = '"+userName+"' and password = '"+passWord+"'";
String sql = "select count(*) from admin where username = '"+userName+"' and password = "+passWord+"";
//这里"+passWord+"没有单引号,"'mpp12' or 1=1"中错误密码'mpp12'用了单引号
state = conn.createStatement();//创建数据库操作对象
res = state.executeQuery(sql);//执行查询,并返回查询结果集
while(res.next()) {
if(res.getInt(1) > 0) {
System.out.println("登入成功");
}else {
System.out.println("登入失败");
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//关闭顺序,先声明后关闭,后声明先关闭。
res.close();
state.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
以上的程序存在sql注入的问题,也就是说使用Statement存在安全的问题,所以在开发中几乎不会使用Statement,而是使用PreparedStatement,表示的是预处理,可以防止sql注入,使用?作为占位符,执行了sql语句之后再一个一个给?赋值。
以上的程序存在sql注入的问题,比如用户输入 100 or 0=0,拼接到sql中是一个恒成立的,能够查询所有数据,这就是sql注入,比如登录程序中,要求用户输入用户名和密码,用户名是明文(***)显示的,其他人获取用户名是很简单的,登录的sql:select * from user where user_name = jjm and password = 123 or 1=1 ,这种情况下,我们不能使用Statement进行处理了,应该使用预编译的形式:sql语句中的参数不再是通过字符串拼接的形式传递,而是使用?作为参数的占位符,先执行sql,再设置参数。这个使用就需要使用PreparedStatement 预编译操作对象。
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class JDBCDemo03 {
public static void main(String[] args) {
String userName = "mpp";
//String passWord = "'mpp12' or 1=1"; 失败,只有正确的密码才行
String passWord = "mpp123";
Connection conn = null;
PreparedStatement pstate = null;
ResultSet res = null;
try {
conn = DBUtil.getConnection();
String sql = "select count(*) from admin where username = ? and password = ?";
pstate = conn.prepareStatement(sql);//1.获取数据库预处理对象,并且执行sql
pstate.setString(1, userName);//设置第一个?号的值
pstate.setString(2, passWord);//设置第二个?号的值
res = pstate.executeQuery();//2.接收查询的结果集
while(res.next()) {
if(res.getInt(1) > 0) {
System.out.println("登入成功");
}else {
System.out.println("登入失败");
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//关闭顺序,先声明后关闭,后声明先关闭。
res.close();
pstate.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
String nameAfter = "mpp";
double balanceAfter = 100;
Connection conn = null;//声明数据库连接对象
PreparedStatement pstate = null;//声明数据库预处理对象
ResultSet res = null;//声明数据库查询操作对象
try {
conn = DBUtil.getConnection();//1取得数据库连接
String sql = "select name,balance from account where name = ? and balance = ?";//2编写sql
pstate = conn.prepareStatement(sql);//3获得数据库预处理对象,并执行sql
pstate.setString(1, nameAfter);//设置第一个?号的值
pstate.setDouble(2, balanceAfter);//设置第二个?号的值
res = pstate.executeQuery();//4接收查询结果集
while(res.next()) {//5遍历结果集,取得并且判断是否有下一个结果
if(res.getDouble("balance") > 60) {
System.out.println("余额充足"+res.getString("name")+"==="+res.getDouble(2));
}else {
System.out.println("余额不足");
}
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {//6关闭,先用后关,后用先关
res.close();
pstate.close();
conn.close();
以上的程序就不存在sql注入 的问题了,而且我们可以使用预处理完成增删改操作。以后的开发中,绝对不会使用Statement,而是使用PreparedStatement预编译的形式。
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into admin (username,password) values (?,?)";
//String sql = "update admin set passeord = ? where username = ?";
//String sql = "delete from admin where username = ? and password = ?";
pstate = conn.prepareStatement(sql);//获取预处理对象,并且执行sql
pstate.setString(1, "wzx");//设置第一个?号的值
pstate.setString(2, "wzx123");设置第二个?号的值
pstate.execute();//最终的执行
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
pstate.close();
conn.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
总结:
account表,name(String)和balance(double)字段
使用Statement完成增删改操作:
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
try{
conn = DBUtil.getConnection();//1取得数据库连接
String sql ="";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
state.execute(sql);//4执行sql
}catch(Exception e){
e.printStackTrace();
}finally{
try{
state.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}
使用Statement完成查询操作,要用ResultSet来接受查询的结果集
Connection conn = null;//声明数据库连接对象
Statement state = null;//声明数据库操作对象
ResultSet res = null;//声明数据库查询对象
try{
conn = DBUtil.getConnection();//1取得数据库连接
String sql ="";//2编写sql
state = conn.createStatement();//3获得数据库操作对象
res = state.executeQuery(sql);//4执行sql,并接收查询结果集
while(res.next()){
if(res.getDouble(2)>100){
Syetem.out.println(res.getString("name"));
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
res.close();
state.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}
使用PreparedStatement完成增删改操作:
Connection conn = null;//声明数据库连接对象
PreparedStatement pstate = null;//声明数据库预处理对象
try{
conn = DBUtil.getConnection();//1取得数据库连接
String sql ="";//2编写sql
pstate = conn.prepareStatement(sql);//3获得数据库预处理对象,并执行sql
pstate.setString(1,"");//设置第一个?号的值
pstate.setDouble(2,5.00);//设置第二个?号的值
pstate.execute();//4最终的执行
}catch(Exception e){
e.printStackTrace();
}finally{
try{
pstate.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}
使用PreparedStatement完成查询操作,要用ResultSet来接受查询的结果集
Connection conn = null;//声明数据库连接对象
PreparedStatement pstate = null;//声明数据库预处理对象
ResultSet res = null;//声明数据库查询对象
try{
conn = DBUtil.getConnection();//1取得数据库连接
String sql ="";//2编写sql
pstate = conn.prepareStatement(sql);//3获得数据库预处理对象,并执行sql
pstate.setString(1,"");//设置第一个?号的值
pstate.setDouble(2,5.00);//设置第二个?号的值
res = pstate.executeQuery();//4执行sql,并接收查询结果集
while(res.next()){
Syetem.out.println(res.getString(1));
}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
res.close();
pstate.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}
批处理操作(了解)
一次性执行多条sql语句就是批处理。(开发中实际使用的少)
package com.wanbangee.j;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into admin (username,password) values (?,?)";
pstate = conn.prepareStatement(sql);
for (int i = 0; i < 10; i++) {
pstate.setString(1, "wzx");
pstate.setString(2, "iiiii");
pstate.addBatch();//加入批处理
}
pstate.executeBatch();//执行批处理
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
pstate.close();
conn.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
事务管理(重点,核心)
事务核心点就是事务不能自动提交,而是手动提交,如果程序运行没有异常则进行提交,出现异常则进行回滚。
执行一批sql的时候,要么全部成功要么全部失败。事务的步骤:
任何一条DML(增删改)语句的执行都可视为事务的开启。
使用PreparedStatement这个接口进行增删改查操作,如果是查询的需要使用ResultSet接收查询的结果集。如果是进行增删改操作,还需要进行事务的管理。
- 取消事务的自动提交
- 执行sql没有出现异常则提交事务
- 出现异常则回滚事务
package com.wanbangee.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.wanbangee.util.DBUtil;
public class JDBCDemo09 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
try {
conn = DBUtil.getConnection();
//取消事务的自动提交
conn.setAutoCommit(false);
String sql = "insert into boys (boyname,usercp) values(?,?)";
pstate = conn.prepareStatement(sql);
for(int i = 5;i<15;i++) {
pstate.setString(1, "王大锤");
pstate.setInt(2, i);
pstate.addBatch();//加入批处理
}
pstate.executeBatch();//执行批处理
conn.commit();//没有异常则提交事务
} catch (Exception e) {
try {
conn.rollback();//出现异常回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
pstate.close();
conn.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
注意这里要将数据库中usercp类型改为char(1)
没有事务的效果:
- 创建account表,有name和balance字段,balance存在检查约束不能小于0(mysql不能添加 check检查约束)
- 往表中加入数据,INSERT INTO account VALUES(‘jjm’,100),(‘mpp’,100);
- jjm转账给mpp1000
package com.wanbangee.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo05 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
try {
conn = DBUtil.getConnection();
String sql = "update account set balance = balance + ? where name = ?";
pstate = conn.prepareStatement(sql);
//jjm转账给mpp1000
//mpp加上一千
pstate.setDouble(1, 1000);
pstate.setString(2, "mpp");
pstate.addBatch();//加入到批处理
//jjm减一千
pstate.setDouble(1, -1000);
pstate.setString(2, "jjm");
pstate.addBatch();//加入到批处理
pstate.executeBatch();//执行批处理
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
pstate.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
以上的程序存在问题,如果jjm的余额小于1000的情况下,转账给mpp,这时jjm余额减去1000的话,违反了检查约束条件,程序出现异常,但是mpp的余额居然加了1000,那么这1000块钱是从哪里来的呢?出现问题的原因就是第一次sql执行成功,第二次执行失败,但是转账又是一个不可分割的整体,要么全部执行成功,要么全部执行失败。这种程序必须放在一个事务中。JDBC中事务的操作步骤:
- 取消事务的自动提交
- 没有异常,提交事务
- 出现异常,回滚事务
package com.wanbangee.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo05 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
try {
conn = DBUtil.getConnection();
conn.setAutoCommit(false);//取消事务的自动提交
String sql = "update account set balance = balance + ? where name = ?";
pstate = conn.prepareStatement(sql);
//jjm转账给mpp1000
//mpp加上一千
pstate.setDouble(1, 1000);
pstate.setString(2, "mpp");
pstate.addBatch();//加入到批处理
//jjm减一千
pstate.setDouble(1, -1000);
pstate.setString(2, "jjm");
pstate.addBatch();//加入到批处理
pstate.executeBatch();//执行批处理
String balanceAfter = "select balance from account where name = 'jjm'";
int a = Integer.parseInt(balanceAfter); //将字符串转变为整型
if(a < 0) {
throw new RuntimeException("余额不足");
}
/*if(1==1) {
throw new Exception("");
}*/
conn.commit();//没有异常则提交事务
} catch (Exception e) {
try {
conn.rollback();//出现异常回滚事务
} catch (Exception e2) {
}
e.printStackTrace();
}finally {
try {
pstate.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}//这还是错误的操作
正确操作:
package com.wanbangee.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo5 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstate = null;
ResultSet res = null;
try {
conn = DBUtil.getConnection();
conn.setAutoCommit(false);//取消事务的自动提交
String sql = "update account set balance = balance + ? where name = ?";
pstate = conn.prepareStatement(sql);
//jjm转账给mpp1000
//mpp加上一千
pstate.setDouble(1, 1000);
pstate.setString(2, "mpp");
pstate.addBatch();//加入到批处理
//jjm减一千
pstate.setDouble(1, -1000);
pstate.setString(2, "jjm");
pstate.addBatch();//加入到批处理
pstate.executeBatch();//执行批处理
String balanceAfter = "select balance from account where name = ?";
pstate = conn.prepareStatement(balanceAfter);//获得预处理对象,并执行sql
pstate.setString(1, "jjm");//给?设置值
res = pstate.executeQuery();//返回查询结果集
while(res.next()) { //遍历结果集
if(res.getDouble("balance") < 0) {
System.out.println(res.getDouble(1)+"余额不足");
throw new RuntimeException("余额不足");
}else {
System.out.println(res.getDouble("balance"));
}
}
conn.commit();//没有异常则提交事务
} catch (Exception e) {
try {
conn.rollback();//出现异常回滚事务
} catch (Exception e2) {
}
e.printStackTrace();
}finally {
try {
pstate.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}