时间:2021-07-01 10:21:17 帮助过:29人阅读
控制台输出情况。
以上为最原始的方式,但是存在问题,即DriverManager会注册两次,因为底层Driver有一个静态方法也会注册一次,因此这里采用反射加载驱动类来实现注册,具体参考代码。
底层静态代码块加载后会注册一次。
1 package com.boe.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 9 import com.mysql.jdbc.Driver; 10 11 public class ConnectionDemo1 { 12 13 public static void main(String[] args) { 14 //1 注册数据库驱动 15 //问题1 手动注册了一次,底层代码也注册了一次,注册了两次驱动实际上,只需要注册一次即可 16 //问题2 包名和代码绑定死,如果更换数据库,需要修改包名 17 //DriverManager.registerDriver(new Driver()); 18 //定义成员变量 19 Connection conn=null; 20 Statement stmt=null; 21 ResultSet rs=null; 22 try { 23 Class.forName("com.mysql.jdbc.Driver"); 24 //利用反射API获取一次Driver类,会自动执行一次Driver类中的静态方法 25 //2 获取数据库连接 26 //Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2", "root", "2688"); 27 //下面的连接方式写法也可以,参考文档26连接器 28 conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688"); 29 //3 创建传输器 30 stmt=conn.createStatement(); 31 //4 创建sql会返回结果 32 rs=stmt.executeQuery("SELECT * FROM dept"); 33 //5 遍历结果 34 while(rs.next()){//next()会移动行的光标,如果移动到的行有数据返回true,否则返回false,初始化位置在第一行前面 35 //获取表字段数据 36 int id=rs.getInt("id"); 37 String name=rs.getString("name"); 38 System.out.println("id="+id+",name="+name); 39 } 40 } catch (Exception e) { 41 e.printStackTrace(); 42 throw new RuntimeException(e); 43 }finally{ 44 //6 关闭资源 后创建的先关闭 45 try { 46 if(rs!=null){ 47 rs.close();//不关闭,数据库数据会留在内存 48 } 49 } catch (SQLException e) { 50 e.printStackTrace(); 51 //可以选择抛出运行时异常,不会打断程序 52 throw new RuntimeException(e); 53 }finally{ 54 rs=null; 55 } 56 try { 57 if(stmt!=null){ 58 stmt.close();//这个关闭会自动关闭rs 59 } 60 } catch (SQLException e) { 61 e.printStackTrace(); 62 throw new RuntimeException(e); 63 }finally{ 64 stmt=null; 65 } 66 try { 67 if(conn!=null){ 68 conn.close(); 69 } 70 } catch (SQLException e) { 71 e.printStackTrace(); 72 throw new RuntimeException(e); 73 }finally{ 74 conn=null; 75 } 76 } 77 } 78 }
以下简单的实现了CRUB操作,使用优化后注册方式。
1 package com.boe.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 9 import org.junit.Test; 10 11 import com.boe.utils.JDBCUtils; 12 13 public class ConnectionDemo3 { 14 //添加操作 15 @Test 16 public void add(){ 17 Connection conn=null; 18 Statement stmt=null; 19 try { 20 //1 注册数据库驱动 21 Class.forName("com.mysql.jdbc.Driver"); 22 //2 创建数据库连接 23 conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688"); 24 //3 获取传输器 25 stmt=conn.createStatement(); 26 //4 输出sql 获取结果集 27 int i=stmt.executeUpdate("INSERT INTO dept Values(null,‘文娱部‘)"); 28 if(i>0){ 29 System.out.println("插入成功,受到影响的行数为:"+i+"行"); 30 } 31 } catch (Exception e) { 32 e.printStackTrace(); 33 }finally{ 34 //后创建的先关闭 35 try { 36 if(stmt!=null){ 37 stmt.close(); 38 } 39 } catch (SQLException e) { 40 e.printStackTrace(); 41 }finally{ 42 stmt=null; 43 } 44 try { 45 if(conn!=null){ 46 conn.close(); 47 } 48 } catch (SQLException e) { 49 e.printStackTrace(); 50 }finally{ 51 conn=null; 52 } 53 } 54 } 55 56 //更新数据的方法 57 @Test 58 public void update(){ 59 Connection conn=null; 60 Statement stmt=null; 61 try{ 62 Class.forName("com.mysql.jdbc.Driver"); 63 conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688"); 64 stmt=conn.createStatement(); 65 int i=stmt.executeUpdate("UPDATE dept SET name=‘IT部门‘ WHERE id=6"); 66 if(i>0){ 67 System.out.println("更新成功,受到影响的行数为:"+i+"行"); 68 } 69 }catch(Exception e){ 70 e.printStackTrace(); 71 }finally{ 72 //后创建的先关闭 73 try { 74 if(stmt!=null){ 75 stmt.close(); 76 } 77 } catch (SQLException e) { 78 e.printStackTrace(); 79 }finally{ 80 stmt=null; 81 } 82 try { 83 if(conn!=null){ 84 conn.close(); 85 } 86 } catch (SQLException e) { 87 e.printStackTrace(); 88 }finally{ 89 conn=null; 90 } 91 } 92 } 93 94 //删除数据 95 @Test 96 public void delete(){ 97 Connection conn=null; 98 Statement stmt=null; 99 try{ 100 conn=JDBCUtils.getConnection(); 101 stmt=conn.createStatement(); 102 int i=stmt.executeUpdate("DELETE FROM dept WHERE id=8"); 103 if(i>0){ 104 System.out.println("删除成功,受到影响的行数为:"+i+"行"); 105 } 106 }catch(Exception e){ 107 e.printStackTrace(); 108 }finally{ 109 JDBCUtils.closeConnection(conn, stmt, null); 110 } 111 } 112 }
在使用的过程中发现很多代码需要重复写,如类加载和获取连接,以及关闭资源等,这些反复书写的代码可以提取出来,写到一个工具类里面,这里将创建连接需要的驱动类名,url,用户名和密码都保存在了一个properties文件里面,这样如果需要更改数据库就只需要修改配置文件即可。
1 package com.boe.utils; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.sql.Connection; 8 import java.sql.DriverManager; 9 import java.sql.ResultSet; 10 import java.sql.SQLException; 11 import java.sql.Statement; 12 import java.util.Properties; 13 14 //工厂类:不允许创建对象,方法为静态方法 15 public class JDBCUtils { 16 //不允许创建对象,构造方法私有化 17 private JDBCUtils(){ 18 19 } 20 //prop变成静态,代表只创建一次 21 private static Properties prop=new Properties(); 22 static{ 23 try { 24 prop.load(new FileInputStream(new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath()))); 25 } catch (FileNotFoundException e) { 26 e.printStackTrace(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 //写静态方法 32 public static Connection getConnection() throws Exception{ 33 //将配置信息加载prop 34 //Properties prop=new Properties(); 35 //prop.load(new FileInputStream(new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath()))); 36 //获取prop里的内容 37 String driver=prop.getProperty("driver"); 38 String url=prop.getProperty("url"); 39 String user=prop.getProperty("user"); 40 String pwd=prop.getProperty("password"); 41 //Class.forName("com.mysql.jdbc.Driver"); 42 Class.forName(driver); 43 //2 创建数据库连接 44 //return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688"); 45 return DriverManager.getConnection(url,user,pwd); 46 } 47 public static void closeConnection(Connection conn,Statement stmt,ResultSet rs){ 48 //后创建的先关闭 49 try { 50 if(rs!=null){ 51 rs.close(); 52 } 53 } catch (SQLException e) { 54 e.printStackTrace(); 55 }finally{ 56 rs=null; 57 } 58 try { 59 if(stmt!=null){ 60 stmt.close(); 61 } 62 } catch (SQLException e) { 63 e.printStackTrace(); 64 }finally{ 65 stmt=null; 66 } 67 try { 68 if(conn!=null){ 69 conn.close(); 70 } 71 } catch (SQLException e) { 72 e.printStackTrace(); 73 }finally{ 74 conn=null; 75 } 76 } 77 }
在完成工具类的泛化后,接下来使用它可以完成一个简单的登录案例,其中引出PreparedStatement和SQL注入,
如果使用Statement,会有SQL注入的风险,即SQL后台拼接后如果参数里有SQL关键字,可能导致整条SQL语义的变化导致不需要输入密码也可以登录的情况,如用户名后加上‘ #,或者密码里加上‘OR ‘ 1=1。这样的情况下,PreparedStatement就应用而生,它是Statement的一个子接口,使用它将先将SQL进行预编译,参数只是用?占位符来代替,后面传入参数也只是将其作为文本来处理,不会导致语义的变化。
1 package com.boe.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.Statement; 7 import java.util.Scanner; 8 9 import com.boe.utils.JDBCUtils; 10 11 //登录 12 public class Login { 13 //用户输入用户名和密码,和数据库中的进行比较,如果匹配则提示登录成功,否则提示失败 14 public static void main(String[] args) { 15 //获取控制台输入的用户名和密码 16 Scanner scan=new Scanner(System.in); 17 System.out.println("请输入用户名:"); 18 String user=scan.nextLine(); 19 System.out.println("请输入密码:"); 20 String pwd=scan.nextLine(); 21 //测试是否正确 22 testLogin( user,pwd); 23 //testPreparedStatementLogin(user,pwd); 24 25 } 26 /** 27 * 访问数据库,使用Preparedstatement根据用户名和密码进行访问 28 * @param user 29 * @param pwd 30 */ 31 private static void testPreparedStatementLogin(String user, String pwd) { 32 Connection conn=null; 33 PreparedStatement ps=null; 34 ResultSet rs=null; 35 try { 36 conn=JDBCUtils.getConnection(); 37 String sql="SELECT * FROM user WHERE username=? AND password=? "; 38 ps=conn.prepareStatement(sql); 39 ps.setString(1, user); 40 ps.setString(2, pwd); 41 rs=ps.executeQuery(); 42 if(rs.next()){ 43 System.out.println("登录成功"); 44 }else{ 45 System.out.println("登录失败"); 46 } 47 } catch (Exception e) { 48 e.printStackTrace(); 49 }finally{ 50 //PreparedStatement继承自Statement接口,所以可以直接用 51 JDBCUtils.closeConnection(conn, ps, rs); 52 } 53 54 } 55 /** 56 * 访问数据库,根据用户名和密码进行访问 57 * @param user 58 * @param pwd 59 */ 60 private static void testLogin(String user, String pwd) { 61 Connection conn=null; 62 Statement stmt=null; 63 ResultSet rs=null; 64 try { 65 conn=JDBCUtils.getConnection(); 66 stmt=conn.createStatement(); 67 String sql="SELECT * FROM user WHERE username=‘"+user+"‘ AND password=‘"+pwd+"‘"; 68 rs=stmt.executeQuery(sql); 69 //打印sql,查看sql注入攻击 70 System.out.println(sql); 71 if(rs.next()){ 72 System.out.println("登录成功"); 73 }else{ 74 System.out.println("登录失败"); 75 } 76 } catch (Exception e) { 77 e.printStackTrace(); 78 }finally{ 79 JDBCUtils.closeConnection(conn, stmt, rs); 80 } 81 } 82 83 }
不使用预编译的结果,可以看到可以sql注入。
在介绍完上面的基本操作后,JDBC还提供批量处理的API,可以实现SQL批量处理,其有两种选择,一个是使用Statement的批处理,一个是使用PreparedStatement的批处理,两者各有优缺点。前者的话就是可以放入不同结构的SQL,但是没有预编译效率低,后者是有预编译效率高,但是更适合有相同结构但是参数不同的SQL。
1 package com.boe.batch; 2 3 import java.sql.Connection; 4 import java.sql.Statement; 5 6 import com.boe.utils.JDBCUtils; 7 8 //批处理 9 /* 10 * create table t1(id int primary key auto_increment,name varchar(20)); 11 * insert into t1 values(null,‘鸣人‘); 12 * insert into t1 values(null,‘向日‘); 13 * insert into t1 values(null,‘卡卡西‘); 14 * insert into t1 values(null,‘大蛇‘); 15 * 16 * */ 17 public class StatementBatch { 18 19 public static void main(String[] args) { 20 Connection conn=null; 21 Statement stmt=null; 22 try{ 23 conn=JDBCUtils.getConnection(); 24 stmt=conn.createStatement(); 25 stmt.addBatch("create table t1(id int primary key auto_increment,name varchar(20))"); 26 stmt.addBatch(" insert into t1 values(null,‘鸣人‘)"); 27 stmt.addBatch(" insert into t1 values(null,‘向日‘)"); 28 stmt.addBatch(" insert into t1 values(null,‘卡卡西‘)"); 29 stmt.addBatch(" insert into t1 values(null,‘大蛇‘)"); 30 int[] arr=stmt.executeBatch(); 31 System.out.println(arr.toString()); 32 System.out.println("批处理执行完成"); 33 }catch(Exception e){ 34 e.printStackTrace(); 35 }finally{ 36 JDBCUtils.closeConnection(conn, stmt, null); 37 } 38 } 39 40 }
1 package com.boe.batch; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 6 import com.boe.utils.JDBCUtils; 7 8 /* 9 * create table t1(id int primary key auto_increment,name varchar(20)); 10 * insert into t1 values(null,‘鸣人‘); 11 * insert into t1 values(null,‘向日‘); 12 * insert into t1 values(null,‘卡卡西‘); 13 * insert into t1 values(null,‘大蛇‘); 14 * 15 * */ 16 public class PrepareBatch { 17 18 public static void main(String[] args) { 19 //PrepareStatement比较适合sql主干一致的 20 Connection conn=null; 21 PreparedStatement ps=null; 22 try{ 23 conn=JDBCUtils.getConnection(); 24 ps=conn.prepareStatement("INSERT INTO t1 values(null,?)"); 25 //添加批处理 26 for(int i=1;i<1000;i++){ 27 ps.setString(1, "小明"+i); 28 ps.addBatch(); 29 if(i%100==0){ 30 //每隔1000条执行一次 31 ps.executeBatch(); 32 //执行完的批处理可以清空 33 ps.clearBatch(); 34 System.out.println("第"+i/100+"批处理完成"); 35 } 36 } 37 //批量处理,收尾 38 ps.executeBatch(); 39 System.out.println("全部处理完成"); 40 }catch(Exception e){ 41 e.printStackTrace(); 42 }finally{ 43 JDBCUtils.closeConnection(conn, ps, null); 44 } 45 } 46 47 }
在大量数据库连