Java DataBase Connectivity
在java语言中编写sql语句,对mysql数据库中的数据进行CRUD操作。增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
java.sql.*;
java.sql.*;
这个包下都是JDBC的接口,SUN公司制定的!
JDBC是体现“接口作用”的非常经典的例子。
JDBC降低了耦合度,提高了扩展力。
对于java程序员来说,不需要关心数据库是哪个品牌。只要面向JDBC接口编程就行!
JDBC整个程序的结构当中有三波人????
第一波:SUN公司,负责制定JDBC接口。这些接口已经写好了,在java.sql.*;
第二波:java.sql.*下面的所有接口都要有实现类,这些实现类是数据库厂家编写的。
我们连接的是mysql数据库,mysql数据库厂家的实现类在哪里呢?
mysql-connector-java-5.1.23-bin.jar
jar包中很多.class字节码文件,这是mysql数据库厂家写的接口实现!
注意:如果连接的是oracle数据库,你需要从网上下载oracle的jar包。
mysql-connector-java-5.1.23-bin.jar 这个jar包有一个专业的术语,
大家记住就行:mysql的驱动。
如果是oracle的jar,被称为oracle的驱动。
第三波:我们java程序员,面向JDBC接口写代码就行!
mysql的驱动jar包,需要配置到classpath当中吗?
mysql-connector-java-5.1.23-bin.jar里是字节码,是class文件。
Java虚拟机的类加载器会去加载class文件,类加载器怎么能够
找到这些class文件呢?
classpath没有配置的情况下,默认从当前路径下加载class。
classpath如果配置死了,例如:classpath=D:\abc,则表示固定只从d:\abc目录下找class
classpath=.;D:\course\04-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
. 代表什么?当前路径。
以上的classpath什么意思?
类加载器从当前路径下加载class,如果当前路径下没找到,
则去D:\course\04-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
找class文件。
jar包需要解压吗?
不需要解压,java虚拟机的类加载器有这个能力找到class文件。
package jdbc;
import java.sql.Driver;
import java.sql.*;
public class jdbc01 {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
try {
//1、注册驱动//告诉java程序我们连接的是那类数据库
Driver driver = new com.mysql.cj.jdbc.Driver();//多态,父类型引用指向子类型对象
//Driver driver=new oracle.jdbc.driver.OracleDriver();//oracle的驱动
DriverManager.registerDriver(driver);
//↑static void registerDriver(Driver driver)向DriverManager注册给定应用程序
//2、获取连接
/*
url:统一资源定位符(网络中某个资源的绝对路径)
http://www.baidu.com/ 这就是URL。
URL包括哪几部分?
协议
IP
PORT
资源名
http:// 182.61.200.7:80/index.html
http:// 通信协议
182.61.200.7服务器ip地址
80服务器上软件的端口
index.html是服务器上某个资源名
ip是计算机的代号,端口是软件应用的代号
jdbc:mysql://127.0.0.1:3306/bjpowernode
jdbc:mysql://协议
127.0.0.1 IP地址
3306 mysql数据库端口号
bjpowernode 具体的数据库实例名
说明localhost 和127.0.0.1都是本机ip地址。
什么是通信协议,有什么用?
通信协议是通信之前就提前定好的数据传送格式。
数据包具体怎么传数据,格式提前定好的。
oracle的url:
jdbc:oracle:thin:@localhost:1521:orcl
*/
String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";
String user = "root";
String password = "123456";
//获取连接 是个流
//public static Connection getConnection(String url,String user,String password) throws SQLException
//试图建立到给定数据库URL的连接,DriverManager试图从已注册的JDBC驱动程序集 中选择一个适当的驱动程序。
conn = DriverManager.getConnection(url, user, password);
System.out.println("数据库连接对象=" + conn);
//3、获取数据库操作对象(statement专门执行sql语句的)
//Statement createStatement()
//创建一个Statement对象来将SQL语句发送到数据库
stmt=conn.createStatement();//
//4、执行sql
String sql="insert into dept(deptno,dname,loc) values(40,'人事部','北京')";
//专门执行DML语句的(insert delete update)
//返回值是"影响数据库中的记录条数",删除三条返回3,更新两条返回2
int count=stmt.executeUpdate(sql);
//int executeUpdate(String sql)
// 执行给定的sql语句,该语句可能为insert,update或delete语句,或者不返回任何内容的sql语句(sql ddl数据库定义)
System.out.println(count==1?"保存成功":"保存失败");
//5、处理查询结果集
//这个只是个插入语句
} catch (SQLException e) {
e.printStackTrace();
}finally{
//6、释放资源
//为了保证资源一定释放,在finally语句块中关闭资源
//并且要遵循从小到大依次关闭
//分别对其try..catch
try{
if(stmt!=null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn!=null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
package jdbc;
import java.sql.*;
/*
JDBC完成delete update
*/
public class jdbc02 {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
try {
//1、注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","123456");
System.out.println("数据库连接对象=" + conn);
//3、获取数据库操作对象
stmt=conn.createStatement();//
//4、执行sql
String sql="delete from dept where deptno=40";
//JDBC中的sql语句不需要提供分号结尾,否则报错
//String sql="update dept set dname='销售部",loc='天津'where deptno=20";
int count=stmt.executeUpdate(sql);
System.out.println(count==1?"删除成功":"删除失败");
} catch (SQLException e) {
e.printStackTrace();
}finally{
//6、释放资源
if(stmt!=null){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn!=null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
package jdbc;
import com.sun.javaws.IconUtil;
import java.sql.*;
public class jdbc03 {
public static void main(String[] args) {
try{
//1、注册驱动
//这是注册驱动的第一种写法
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//注册驱动的第二种方式:常用的
//为什么这种方式常用?因为参数是一个字符串,字符串可以写到xxx.properties文件中。
//以下方法不需要接收返回值,因为我们只想用它的类加载动作。
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获取连接
Connection conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","123456");
System.out.println(conn);
}catch(SQLException e){
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
jdbcc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/bjpowernode
user=root
password=123456
package jdbc;
import java.sql.*;
import java.util.ResourceBundle;
/*
实际开发中不建议把连接数据库的信息写死在java程序中。
*/
public class jdbc04 {
public static void main(String[] args) {
// 使用资源绑定器绑定属性配置文件
ResourceBundle bundle =ResourceBundle.getBundle("jdbcc");
String driver=bundle.getString("key");
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
Connection conn=null;
Statement stmt=null;
try {
//1、注册驱动(只需要改这个)
Class.forName(driver);
//2、获取连接
conn = DriverManager.getConnection(url,user,password);
//3、获取数据库操作对象
stmt=conn.createStatement();//
//4、执行sql
String sql="update dept set dname='销售部2',loc='天津'where deptno=20";
int count=stmt.executeUpdate(sql);
System.out.println(count==1?"修改成功":"修改失败");
} catch (SQLException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
//6、释放资源
if(stmt!=null){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn!=null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
ResultSet rs 存的是执行sql语句后的查询结果 集
package jdbc;
import javax.swing.*;
import java.sql.*;
/*
处理查询结果集
*/
public class jdbc05 {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//1、注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","123456");
//3、获取数据库操作对象
stmt=conn.createStatement();//
//4、执行sql
String sql="select empno,ename,sal from emp";
//ResultSet executeQuery(String sql) throws SQLEXception
//执行给定的SQL语句,该语句返回单词ResultSet对象。
// 参数sql 为要发送给数据库的SQL语句,通常为静态SQL select语句。
//int executeUpdate(insert/delete/update)
//ResultSet executeQuery(select)//ResultSet是执行sql语句后的查询结果集
rs=stmt.executeQuery(sql);
//5、处理查询结构集
/*理解过程
boolean flag1=rs.next();//本来在第0行,现在next到了第一行,如果有数据就返回true
// System.out.println(flag1); //true
if(flag1){
//光标指向的行有数据
//取数据
//getString()方法的特点是:不管数据库中的数据类型是什么,都以String的形式取出
String empno=rs.getString(1);//跟的是下标,第一列,第二列,第三列
String ename=rs.getString(2);//jdbc中所有的下标从1开始,不是从0开始的
String sal=rs.getString(3);
System.out.println(empno+","+ename+","+sal);
}
//rs.next();
//boolean next() throws SQLException将光标从当前位置向前移一行。
// ResultSet 光标最初位于第一行之前;第一次调用 next
// 方法使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。
// 返回:如果新的当前行有效,则返回 true;如果不存在下一行,则返回 false
//抛出:SQLException - 如果发生数据库访问错误或在关闭的结果集上调用此方法
*/
while(rs.next()){
/*
String empno=rs.getString(1);
String ename=rs.getString(2);
String sal=rs.getString(3);
System.out.println(empno+","+ename+","+sal);
*/
/*另一种写法,不是以列的下标获取,以列的名字获取
String empno=rs.getString("empno");
String ename=rs.getString("ename");
String sal=rs.getString("sal");
System.out.println(empno+","+ename+","+sal);
*/
int empno=rs.getInt(1);//之所以这能用int 是因为底层数据是int类型的,不然要以String类型
String ename=rs.getString(2);
double sal=rs.getDouble(3);
System.out.println(empno+","+ename+","+(sal-100));//double类型 方便进行运算
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
//6、释放资源
if(rs!=null){//释放查询结果集
try{
rs.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(stmt!=null){//释放数据库操作对象
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn!=null){//释放数据库连接对象
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
package jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class jdbc06 {
public static void main(String[] args) {
//初始化一个界面,可以让用户输入用户名和密码
Map<String,String>userLoginInfo=initUI();
//连接数据库验证用户和密码是否正确
boolean ok=checkNameAndPwd(userLoginInfo.get("loginName"),userLoginInfo.get("loginPwd"));
System.out.println(ok?"登陆成功":"登陆失败");
}
private static boolean checkNameAndPwd(String loginName, String loginPwd) {
boolean ok=false;//默认登录是失败的,
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 1、注册驱动
try {
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、执行sql语句
String sql = "select * from t_user where login_name='"+loginName+"'and login_pwd='"+loginPwd+"'";
System.out.println(sql);
//程序执行到此处,才会将以上的sql语句发送到DBMS上,DBMS进行sql语句的编译。
rs = stmt.executeQuery(sql);//sql处理后会返回一个处理集,
//如果以上sql语句中用户名和密码是正确的,那么结果集最多也就查询出一条记录,所以以下不需要while循环。if就够了
if(rs.next()){//此条件如果成立,表示登录成功
ok=true;
}
// 5、处理结果集
} catch (Exception e) {
e.printStackTrace();
}finally {
// 6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn!= null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return ok;
}
private static Map<String, String> initUI(){
System.out.println("欢迎使用该系统,请输入用户名和密码进行身份认证");
Scanner s=new Scanner(System.in);
System.out.println("用户名:");
String loginName=s.nextLine();
System.out.println("密码:");
String loginPwd=s.nextLine();
//将用户名和密码放在map集合中
Map<String,String> useLoginInfo=new HashMap<>();
useLoginInfo.put("loginName",loginName);
useLoginInfo.put("loginPwd",loginPwd);
//返回map
return useLoginInfo;
}
/*
随便输入一个用户名的密码,登录成功了,这种现象被称为SQL注入现象!
导致SQL注入的根本原因是:用户不是一般的用户,用户是懂程序的,输入的用户名信息以及密码信息中,
含有SQL语句的关键词,这个SQL语句的关键字和底层的SQL语句进行“字符串拼接”,导致原SQL语句的含义
被扭曲了。最最主要的原因是:用户提供的信息参与了SQL语句的编译。
主要因素,这个程序是先进行字符串的拼接,然后再进行sql语句的编译,正好彼注入。
用户名:
fdsa
密码:
fdsa' or '1'='1
select * from t_user where login_name='fdsa'and login_pwd='fdsa' or '1'='1'
*/
}
package jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
怎么避免sql注入?
sql注入的根本原因是,先进行了字符串的拼接,然后在进行的编译。
java.sql.Statement接口的特点,先进行字符串的拼接。然后再进行sql语句的编译。
优点:使用Statement可以进行sql语句的拼接。
缺点:因为拼接的存在,导致可能给不法分子机会。
java.sql.PreparedStatement接口的特点,先进行sql语句的编译,然后在进行sql语句的传值。
优点:避免sql注入。
缺点:没有办法进行sql语句的拼接,只能给sql语句传值。
PreparedStatement预编译的数据库操作对象
*/
public class jdbc07 {
public static void main(String[] args) {
//初始化一个界面,可以让用户输入用户名和密码
Map<String,String>userLoginInfo=initUI();
//连接数据库验证用户和密码是否正确
boolean ok=checkNameAndPwd(userLoginInfo.get("loginName"),userLoginInfo.get("loginPwd"));
System.out.println(ok?"登陆成功":"登陆失败");
}
private static boolean checkNameAndPwd(String loginName, String loginPwd) {
boolean ok=false;//默认登录是失败的,
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
// 1、注册驱动
try {
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
// 3、获取预编译的数据库操作对象
//一个问号是一个占位符,一个占位符只能接收一个值/数据
String sql = "select * from t_user where login_name=? and login_pwd=?";
stmt=conn.prepareStatement(sql);//此时会发送sql给DBMS,进行sql语句的编译
//给占位符?传值
//怎么解决sql注入的?即使用户信息中有sql关键字,但是不参加编译就没事。
//stmt.setInt(1,100);//这样必须是填数字,↓下面那个必须填字符串。
stmt.setString(1,loginName);//给第一个占位符传值
stmt.setString(2,loginPwd);//给第二个占位符传值
// 4、执行sql语句
rs = stmt.executeQuery();//这个方法不需要将sql语句传递进去,不能是这样:rs = stmt.executeQuery(sql);因为上面已经把sql传过去了。
if(rs.next()){//此条件如果成立,表示登录成功
ok=true;
}
// 5、处理结果集
} catch (Exception e) {
e.printStackTrace();
}finally {
// 6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn!= null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return ok;
}
private static Map<String, String> initUI(){
System.out.println("欢迎使用该系统,请输入用户名和密码进行身份认证");
Scanner s=new Scanner(System.in);
System.out.println("用户名:");
String loginName=s.nextLine();
System.out.println("密码:");
String loginPwd=s.nextLine();
//将用户名和密码放在map集合中
Map<String,String> useLoginInfo=new HashMap<>();
useLoginInfo.put("loginName",loginName);
useLoginInfo.put("loginPwd",loginPwd);
//返回map
return useLoginInfo;
}
}
//先使用PreparedStatement-
这个程序输入desc或者asc会出错,因为
select ename,sal from emp order by sal ?会被换成select ename,sal from emp order by sal 'desc’而不是select ename,sal from emp order by sal desc ,
PreparedStatement比较适合传值,Statement比较适合进行字符串的拼接。
京东升降序那不让用户输入,就是不给你注入的机会。
package jdbc;
import java.sql.*;
import java.util.Scanner;
/*
*需求:用户再控制台上输入desc则降序,输入asc则升序。
*思考以下,这个应该选择Statement还是PreparedStatement
选Statement,y因为PreparedStatement只能传值,不能进行sql语句的拼接。
*/
public class jdbc08 {
public static void main(String[] args){
Scanner s=new Scanner(System.in);
System.out.print("请输入desc或asc[desc是表示降序,asc表示升序]:");
//用户输入的
String orderKey=s.next();
//先使用PreparedStatement
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//3、获取预编译的数据库操作对象
String sql="select ename,sal from emp order by sal ?";
ps=conn.prepareStatement(sql);
//给?传值
ps.setString(1,orderKey);
//4、执行sql语句
rs=ps.executeQuery();
while(rs.next()){
String ename=rs.getString("ename");
String sal=rs.getString("sal");
System.out.println(ename+","+sal);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
//再使用statement
此时可以执行了
package jdbc;
import java.sql.*;
import java.util.Scanner;
/*
*需求:用户再控制台上输入desc则降序,输入asc则升序。
*思考以下,这个应该选择Statement还是PreparedStatement
*/
public class jdbc09 {
public static void main(String[] args){
Scanner s=new Scanner(System.in);
System.out.print("请输入desc或asc[desc是表示降序,asc表示升序]:");
//用户输入的
String orderKey=s.next();
//再使用Statement
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行sql语句
String sql="select ename,sal from emp order by sal "+orderKey;
rs=stmt.executeQuery(sql);
while(rs.next()){
String ename=rs.getString("ename");
String sal=rs.getString("sal");
System.out.println(ename+","+sal);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class jdbc10 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//3、获取预编译的数据库操作对象
//增加操作
/*String sql="insert into dept(deptno,dname,loc) values(?,?,?)";
ps=conn.prepareStatement(sql);
//给?传值
ps.setInt(1,50);
ps.setString(2,"销售部");
ps.setString(3,"天津");*/
//更新操作
/* String sql="update dept set dname=?,loc=? where deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,"软件研发部");
ps.setString(2,"北京");
ps.setInt(3,50);*/
//删除操作
String sql="delete from dept where deptno = ?";
ps=conn.prepareStatement(sql);
ps.setInt(1,50);
// 4、执行sql
int count=ps.executeUpdate();
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
package jdbc;
import java.sql.*;
public class jdbc11 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//3、获取预编译的数据库操作对象
//找出名字中含有O的
//以下第一种方法是错误的,因为?不能放在单引号内
/*String sql="select ename from emp where ename like '%?%'";
ps=conn.prepareStatement(sql);
ps.setString(1, "O");*/
String sql="select ename from emp where ename like ?";
ps=conn.prepareStatement(sql);
ps.setString(1, "%O%");
// 4、执行sql
rs=ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
package jdbc;
/*
JDBC默认情况下对事务是怎么处理的?
模拟一个银行账户转账,A账户向B账户转账10000元。
从A账户减去10000,向B账户加上10000
必须同时成功,或者同时失败
转账需要执行两条update语句
//jdbc默认执行自动提交:
就是执行一条语句就提交一次语句。
在实际开发中必须将jdbc的自动提交机制关闭掉,改为手动提交,
当一个完整的事务结束之后,再提交。
conn.setAutoCommit(false);关闭自动提交机制
conn.commit();手动提交;
conn,rollback();手动回滚;
*/
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class jdbc12 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//拿到连接对象后立即开启事务。开启事务:将自动提交机制关闭掉
//void setAutoCommit(boolen autoCommit) throws SQLException
// 参数autoCommit为true表示启动自动提交模式,为false表示禁用自动提交模式
conn.setAutoCommit(false);
//3、获取预编译的数据库操作对象
String sql=" update t_act set balance =? where actno= ?";
ps=conn.prepareStatement(sql);
//给?传值
ps.setDouble(1,10000);
ps.setString(2,"A");
int count=ps.executeUpdate();
//模拟异常(验证回滚)
//String s=null;
//s.toString();//空指针异常
//给?传值
ps.setDouble(1,10000);
ps.setString(2,"B");
count+=ps.executeUpdate();
System.out.println(count==2?"转账成功":"转账失败");
//代码能执行到此处,说明上面的代码没有出现任何异常,表示都成功了,手动提交
//手动提交,事务结束。
conn.commit();
} catch (Exception e) {
//出现异常的话,为了保险起见,这里要回滚!
try {
if(conn!=null){
conn.rollback();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
}finally {
//6、释放资源
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
外部文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/bjpowernode
user=root
password=123456
封装类
package jdbc.utils;
//在一个没有结束的程序中,DBUtil这个类只加载一次
import java.sql.*;
import java.util.ResourceBundle;
/*
数据库工具类,便于JDBC的代码编写
*/
public class DBUtil {
//静态变量和静态代码块的执行时候是一样的,先出现的先执行
//构造方法私有化是为了防止new对象,为什么要防止用对象?
//因为工具类中的方法都是静态的,不需要new对象,直接用“类名.”的方式调用
private DBUtil(){};//工具类的构造方法私有化的
//类加载时绑定属性资源文件
private static ResourceBundle bundle=ResourceBundle.getBundle("jdbcc");
//保证注册驱动在类加载的时执行且只执行一次,则使用静态代码块
static{
try {
Class.forName(bundle.getString("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获得数据库连接对象
public static Connection getConnection() throws SQLException {
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
Connection conn=DriverManager.getConnection(url,user,password);
return conn;
}
/**
* 释放资源
* conn 连接对象
* stmt 数据库操作对象
* rs 查询结果集
*/
public static void close(Connection conn, Statement stmt,ResultSet rs){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试类
package jdbc;
import java.sql.*;
public class jdbc11 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
//3、获取预编译的数据库操作对象
//找出名字中含有O的
//以下第一种方法是错误的,因为?不能放在单引号内
/*String sql="select ename from emp where ename like '%?%'";
ps=conn.prepareStatement(sql);
ps.setString(1, "O");*/
String sql="select ename from emp where ename like ?";
ps=conn.prepareStatement(sql);
ps.setString(1, "%O%");
// 4、执行sql
rs=ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
关于DQL语句的悲观锁
对于一个DQL语句来说,末尾是可以添加这样一个关键字的:for update
select ename,sal from emp where job=‘MANAGER’ for update ;
以上sql语句的含义是:
在本次事务的执行过程中,job='MANAGER’的记录被查询,
这些记录在我查询的过程中,任何人,任何事务都不能对
这些记录进行修改操作,直到我当前事务结束。
这种机制被称为:行级锁机制(又叫做悲观锁!)
在mysql中是这样的,
当使用select…where…for updare…时,mysql进行row lock(行锁)还是table lock(表锁)只取决于是否能使用索引(例如主键,unique字段),能则为行锁,否则为表锁:
未查到数据则无锁,而使用’<>’,'like’等操作时,索引会失效,自然进行的是table lock。
所以慎用for update。
整个表锁住的时候会导致性能性能降低,谨慎使用。
使用for update的时候,最好是锁主键值,或者具有unique约束的字段,
锁别的字段可能会导致整个表锁住。