JDBC学习总结
JDBC
- 概念:Java DataBase Connectivity
- JDBC本质:官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
快速入门
-
导入jar包,步骤如下:
- 在IDEA中,新建一个文件夹为libs
- 把jar包贴进libs文件夹中
- 右键点击libs文件夹,选择Add as library
-
样例
import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class quicklyrun { public static void main(String[] args) throws Exception { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取数据库连接对象,第一个为数据库名称,第二个为用户名,第三个为用户密码 Connection conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root"); //设置sql语句 String sql="UPDATE account SET balance=10 WHERE id=1"; //获取sql的执行对象 Statement st = conn.createStatement(); //执行sql语句 int result = st.executeUpdate(sql); //查看sql语句的影响行数 System.out.println(result); //释放资源 st.close(); conn.close(); } }
详解各个对象
-
DriverManager:驱动管理对象
功能
-
注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) //注册与给定的驱动程序 DriverManager
在快速入门中我们为什么是这样注册驱动的呢?
Class.forName("com.mysql.jdbc.Driver");
查看源码,原来在com.mysql.jdbc.Driver类中存在静态代码块
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
注意:MySQL5之后的驱动jar包可以省略注册驱动的步骤。
-
获取数据库连接
方法
static Connection getConnection(String url, String user, String password)
参数解释
- url:指定连接的路径
- 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
- 例子:jdbc:mysql://localhost:3306/db3
- 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
- user:用户名
- password:密码
-
-
Connection:数据库连接对象
功能
-
获取执行sql 的对象
Statement createStatement()
PreparedStatement prepareStatement(String sql)
-
管理事务
-
开启事务
setAutoCommit(boolean autoCommit) //调用该方法设置参数为false,即开启事务
-
提交事务
commit()
-
回滚事务
rollback()
-
-
-
Statement:执行sql的对象
执行sql
boolean execute(String sql) //可以执行任意的sql 了解
//返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。 int executeUpdate(String sql) //执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
ResultSet executeQuery(String sql) //执行DQL(select)语句
-
ResultSet:结果集对象,封装查询结果
boolean next() //游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
getXxx(参数) //获取数据 /* Xxx:代表数据类型 如: int getInt() , String getString() 参数: 1. int:代表列的编号,从1开始 如: getString(1) 2. String:代表列名称。 如: getDouble("balance") */
-
注意
- 使用步骤
- 游标向下移动一行
- 判断是否有数据
- 获取数据
- 使用步骤
-
练习1
定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
我的数据库定义
-
定义emp类
package demo_jdbc; import java.util.Date; /* 该类用来存放emp表的数据 */ public class emp { private int id; private String ename; private int job_id; private int mgr; private Date joindate; private double salary; private double bonus; private int dept_id; public emp(int id, String ename, int job_id, int mgr, Date joindate, double salary, double bonus, int dept_id) { this.id = id; this.ename = ename; this.job_id = job_id; this.mgr = mgr; this.joindate = joindate; this.salary = salary; this.bonus = bonus; this.dept_id = dept_id; } public emp() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public int getJob_id() { return job_id; } public void setJob_id(int job_id) { this.job_id = job_id; } public int getMgr() { return mgr; } public void setMgr(int mgr) { this.mgr = mgr; } public Date getJoindate() { return joindate; } public void setJoindate(Date joindate) { this.joindate = joindate; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public double getBonus() { return bonus; } public void setBonus(double bonus) { this.bonus = bonus; } public int getDept_id() { return dept_id; } public void setDept_id(int dept_id) { this.dept_id = dept_id; } @Override public String toString() { return "emp{" + "id=" + id + ", ename='" + ename + '\'' + ", job_id=" + job_id + ", mgr=" + mgr + ", joindate=" + joindate + ", salary=" + salary + ", bonus=" + bonus + ", dept_id=" + dept_id + '}'; } }
-
在主类中定义方法 public List findAll(){},实现方法select * from emp
package demo_jdbc; import java.sql.*; import java.util.ArrayList; import java.util.List; public class jdbc_demo1 { public static void main(String[] args) { List<emp> list=new jdbc_demo1().findall(); for (emp em : list) { System.out.println(em); } } public List<emp> findall(){ Connection conn =null; Statement st=null; ResultSet rs =null; List<emp> list=null; try { //注册驱动,MySQL5.0之后可不写 Class.forName("com.mysql.jdbc.Driver"); //获得数据库连接 conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root"); //定义sql语句 String sql="SELECT * FROM emp"; //创建一个sql执行对象 st = conn.createStatement(); //执行sql命令,返回结果集 rs = st.executeQuery(sql); //游标获取数据 list=new ArrayList<emp>(); emp e=null; while(rs.next()){ int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); e=new emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id); list.add(e); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { if(rs!=null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(st!=null){ try { st.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } return list; } }
-
结果
-
-
PreparedStatement:执行sql的对象
-
SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
- 输入用户随便,输入密码:a’ or ‘a’ = 'a
- sql:select * from user where username = ‘fhdsjkf’ and password = ‘a’ or ‘a’ = ‘a’
-
解决sql注入问题:使用PreparedStatement对象来解决
-
预编译的SQL:参数使用?作为占位符
-
步骤
-
导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
-
注册驱动
-
获取数据库连接对象 Connection
-
定义sql
注意:sql的参数使用?作为占位符。 如:
select * from user where username = ? and password = ?;
-
获取执行sql语句的对象 PreparedStatement
Connection.prepareStatement(String sql)
-
给?赋值
方法:setXxx(参数1,参数2)
- 参数1:?的位置编号 从1 开始
- 参数2:?的值
-
执行sql,接受返回结果,不需要传递sql语句
-
处理结果
-
释放资源
-
-
注意:后期都会使用PreparedStatement来完成增删改查的所有操作
- 可以防止SQL注入
- 效率更高
-
抽取JDBC工具类:JDBCUtils
-
目的:简化书写
-
分析:
- 抽取注册驱动
- 抽取一个方法获取连接对象
- 需求:不想传递参数(麻烦),还得保证工具类的通用性。
- 解决:配置文件
- 抽取一个方法释放资源
-
样例,给练习1进化
我的文件树
-
在src目录下,新建一个名为jdbc.properties的配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql:///db3 user=root password=root
-
在当前包下再新建一个Utils包,再在这个包下新建一个JDBCUtils工具类
package demo_jdbc.Utils; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JDBCUtils { private static String driver; private static String url; private static String user; private static String password; static{ //创建Properties对象 Properties pro=new Properties(); //用类加载器获取jdbc.properties文件的输入流 ClassLoader cl = JDBCUtils.class.getClassLoader(); InputStream is = cl.getResourceAsStream("jdbc.properties"); //获得文件内容 try { pro.load(is); driver=pro.getProperty("driver"); url=pro.getProperty("url"); user=pro.getProperty("user"); password=pro.getProperty("password"); Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } //连接资源 public static Connection getConnection(){ Connection conn=null; try { conn=DriverManager.getConnection(url, user, password); } catch (SQLException throwables) { throwables.printStackTrace(); } return conn; } //释放资源,这里有可能没有游标,所以写两种情况的重载 public static void close(Connection conn, Statement st){ if(st!=null){ try { st.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } public static void close(Connection conn, Statement st,ResultSet rs){ if(rs!=null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(st!=null){ try { st.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } }
-
在练习1的主类的基础上写一个新的主类
package demo_jdbc; import demo_jdbc.Utils.JDBCUtils; import java.sql.*; import java.util.ArrayList; import java.util.List; public class jdbc_demo2 { public static void main(String[] args) { List<emp> list=new jdbc_demo2().findall(); for (emp em : list) { System.out.println(em); } } public List<emp> findall(){ Connection conn =null; Statement st=null; ResultSet rs =null; List<emp> list=null; try { //注册驱动,MySQL5.0之后可不写 //获得数据库连接 conn = JDBCUtils.getConnection(); //定义sql语句 String sql="SELECT * FROM emp"; //创建一个sql执行对象 st = conn.createStatement(); //执行sql命令,返回结果集 rs = st.executeQuery(sql); //游标获取数据 list=new ArrayList<emp>(); emp e=null; while(rs.next()){ int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); e=new emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id); list.add(e); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JDBCUtils.close(conn,st,rs); } return list; } }
-
结果(与练习1一致)
-
-
练习2
需求
1. 通过键盘录入用户名和密码
2. 判断用户是否登录成功步骤
-
创建数据库表USER
CREATE TABLE USER( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32), PASSWORD VARCHAR(32) ); INSERT INTO USER VALUES(NULL,'zhangsan','123'); INSERT INTO USER VALUES(NULL,'lisi','234');
-
JDBCUtils和样例一致,这里写一个新的主类
package demo_jdbc; import demo_jdbc.Utils.JDBCUtils; import java.sql.*; import java.util.Scanner; public class jdbc_demologin { public static void main(String[] args) { Scanner sc=new Scanner(System.in); System.out.println("请输入用户名"); String username=sc.nextLine(); System.out.println("请输入密码"); String password=sc.nextLine(); boolean login = new jdbc_demologin().login(username, password); if(login){ System.out.println("登陆成功"); }else{ System.out.println("用户名或密码错误"); } } public boolean login(String username,String password){ Connection conn=null; Statement st=null; ResultSet rs =null; if(username==null && password==null){ return false; } try { //获得数据库的连接 conn= JDBCUtils.getConnection(); //定义sql语句 String sql="SELECT * FROM USER WHERE username='"+username+"' AND PASSWORD='"+password+"'"; //创建sql执行对象 st = conn.createStatement(); rs = st.executeQuery(sql); return rs.next(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { JDBCUtils.close(conn,st,rs); } return false; } }
-
上述例子我使用了普通的执行对象Statement,会导致SQL注入的问题,如图
-
执行对象改用prepareStatement,代码如下
package demo_jdbc; import demo_jdbc.Utils.JDBCUtils; import java.sql.*; import java.util.Scanner; public class jdbc_demologin { public static void main(String[] args) { Scanner sc=new Scanner(System.in); System.out.println("请输入用户名"); String username=sc.nextLine(); System.out.println("请输入密码"); String password=sc.nextLine(); boolean login = new jdbc_demologin().login(username, password); if(login){ System.out.println("登陆成功"); }else{ System.out.println("用户名或密码错误"); } } public boolean login(String username,String password){ Connection conn=null; PreparedStatement pst=null; ResultSet rs =null; if(username==null && password==null){ return false; } try { //获得数据库的连接 conn= JDBCUtils.getConnection(); //定义sql语句 String sql="SELECT * FROM USER WHERE username=? AND PASSWORD=?"; //创建sql执行对象 pst = conn.prepareStatement(sql); pst.setString(1,username); pst.setString(2,password); rs = pst.executeQuery(); return rs.next(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { JDBCUtils.close(conn,pst,rs); } return false; } }
-
再次使用刚刚的输入查看是否会有SQL注入的问题
-
JDBC控制事务
-
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
-
操作
- 开启事务
- 提交事务
- 回滚事务
-
使用Connection对象来管理事务
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 在执行sql之前开启事务
- 提交事务:commit()
- 当所有sql都执行完提交事务
- 回滚事务:rollback()
- 在catch中回滚事务
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
-
样例
我的数据库定义
代码
package demo_jdbc;
import demo_jdbc.Utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class jdbc_demoshiwu {
public static void main(String[] args) {
PreparedStatement pst1 = null;
PreparedStatement pst2 = null;
Connection conn= null;
try {
conn= JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql="UPDATE account SET balance=balance - ? WHERE id=?";
String sql2="UPDATE account SET balance=balance + ? WHERE id=?";
pst1 = conn.prepareStatement(sql);
pst2 = conn.prepareStatement(sql2);
pst1.setDouble(1,500);
pst1.setInt(2,1);
pst2.setDouble(1,500);
pst2.setInt(2,2);
pst1.executeUpdate();
//手动制造异常
int i=1/0;
pst2.executeUpdate();
conn.commit();
} catch (Exception e) {
if(conn!=null){
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
e.printStackTrace();
}finally {
JDBCUtils.close(conn,pst1);
JDBCUtils.close(null,pst2);
}
}
}