零基础学习java------25--------jdbc
jdbc开发步骤图
以下要用到的products表
一. JDBC简介
补充 JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口,各个数据库厂商趋势线这个接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类(各个数据库,自己去实现jdbc这个接口)
JDBC(java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的API,可以为多种关系数据库提供统一访问,它由一组用java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序
(1)Java DataBase Connectivity(java数据库连接)
(2)组成包:java.sql.*;javax.sql.*;这两个包都包含在了JDK中。
(3)还需要数据库的驱动,这些驱动就相当于对JDBC规范的实现
主要接口或类
1. DriverManger
作用:
a. 注册驱动
b. 获取与数据库的连接
改进注册驱动:
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
缺点:严重依赖具体的驱动类(导入什么驱动的包就只能注册什么驱动),会导致驱动被注册2次。
获取与数据库的连接
DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm", "root", "hang"); //ssm为数据库名
2. Connection
所有与数据库的交互作用都是基于连接的基础之上的,想要对数据库进行操作,首先要获取此对象,从连接对象中获取执行数据库的statement对象
Statement stmt = conn.createStatement():创建向数据库发送sql的statement对象
Conm.preparedStatement(sql) -------更常用,可进行预编译,防止sql注入
3. Statement
作用:向数据库发送并执行具体的SQL语句
常用的方法:
(1)ResultSet executeQuery(String sql):只适合查询,返回的是查询的结果集
(2)int executeUpdate(String sql):适合DML,或者没有返回结果集的DDL,返回的是影响的记录行数
(3)boolean execute(String sql):执行任何的SQL语句,返回的不是成功与否。有结果集的返回true,没有返回false。
4. PreparedStatement
当再次查询相同的数据时,则直接在缓存中获取,不需要再和数据库进行交换,所以说效率高
5. ResultSet
作用:代表查询语句的查询结果集
二. 入门程序以及改造
1. 开发步骤:
(1)创建一个java项目
(2)导入mysql的数据库驱动jar包
(3)注册驱动
(4)获取与数据库的连接
(5)得到代表发送和执行SQL语句的对象--------Statement
(6)执行语句
(7)释放占用的资源
public class TestDemo { public static void main(String[] args) throws Exception { // 注册驱动 DriverManager.registerDriver(new Driver()); // 建立连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_demo1", "root", "feng"); // 获取执行sql语句的对象 Statement st = conn.createStatement(); String sql = "select * from products"; // 执行 返回结果 ResultSet set = st.executeQuery(sql); //遍历结果集 while (set.next()) { //获取字段值 为name的数据 String name = set.getString("name"); System.out.println(name); } // 释放资源 set.close(); st.close(); conn.close(); } }
这个程序存在几个问题:
2. 注册驱动时会出现两次注册,代码扩展性不好问题
由com.mysql.jdbc.Driver()的源码(如下)
mysql的Driver类中有一个静态代码块,静态代码块中已经通过DriverManager注册了这个驱动!
大家都知道静态代码快是在类加载器加载这个类的字节码文件的时候就已经执行了的,也就是说,在DriverManager.registerDriver(new com.mysql.jdbc.Driver())这段代码中,一旦new了这个驱动,这个驱动就已经被加载了!
所以如果DriverManager.registerDriver(new com.mysql.jdbc.Driver()),实际上Mysql的Driver会被加载两次,所以只要new com.mysql.jdbc.Driver();实际上就已经注册驱动了
但是我们能new com.mysql.jdbc.Driver()这样子来注册驱动吗?可以!但是还是不提倡,为什么呢?因为这样的话还有一个问题,这样写十分依赖Jar包,一旦jar包找不到,编译时期就会报错。所以在开发过程中通常写成:Class.forName(“com.mysql.jdbc.Driver”);
这样的话,通过类加载的方式来加载Driver类,照样能够执行static代码块中的注册驱动的方法。而且由于将Driver的位置写成了字符串的形式,对jar包的依赖就降低了,也易于使用配置文件加载这个类,所以下次如果换成连接Oracle或者其他数据库,改一下配置文件再填一个jar包就可以了。
使用Class.forName("com.mysql.jdbc.Driver")进行注册,为了代码的可扩展性,下面采取了读取配置文件的形式获取相应的参数(为了保障资源一定能被释放,应该放到finaly代码块中去)
public class JdbcDemo2 { // 读取配置文件,获取连接需要的数据 static String url; static String userName; static String passWord; static { try { Properties p = new Properties(); p.load(JdbcDemo2.class.getClassLoader().getResourceAsStream("db.properties")); String className = p.getProperty("className"); url = p.getProperty("url"); userName = p.getProperty("userName"); passWord = p.getProperty("passWord"); // 注册驱动 Class.forName("com.mysql.jdbc.Driver"); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Connection conn = null; Statement st = null; ResultSet set = null; try { // 建立连接 conn = DriverManager.getConnection(url, userName, passWord); // 得到执行sql语句的对象 st = conn.createStatement(); String sql = "select * from products"; // 执行并返回结果 set = st.executeQuery(sql); while(set.next()) { String str = set.getString("price"); System.out.println(str); } } catch (Exception e) { e.printStackTrace(); }finally { // 释放资源 try { if (set != null) { set.close(); set = null; } if (st != null) { st.close(); st = null; } if (conn != null) { conn.close(); conn = null; } } catch (SQLException e) { e.printStackTrace(); } } } }
以上代码中出现的如st = null是为了让jvm更快的回收st对象,即使创建对象的引用指向为空。
3. 工具类的提取(将连接和资源的释放封装)
根据上面的例子发现,获取数据库连接,释放数据库资源的代码都一样,可以提取出来一个方法,当获取连接,释放资源就直接调用方法,减少代码的冗余度。
public class JdbcUtils { static String url; static String userName; static String passWord; static { Properties p = new Properties(); try { p.load(JdbcUtils.class.getClassLoader().getResourceAsStream("properties")); String className = p.getProperty("className"); url = p.getProperty("url"); userName = p.getProperty("userName"); passWord = p.getProperty("passWord"); // 注册 Class.forName(className); } catch (Exception e) { e.printStackTrace(); } } // 获取连接方法 public static Connection getConnection() { Connection conn = null; try { conn = DriverManager.getConnection(url, userName, passWord); } catch (SQLException e) { e.printStackTrace(); } return conn; } // 释放资源 public static void release(ResultSet set, Connection conn,Statement st) { try { if (set != null) { set.close(); set = null; } if (st != null) { st.close(); st = null; } if (conn != null) { conn.close(); conn = null; } } catch (SQLException e) { e.printStackTrace(); } } }
三 单元测试
作用:在普通的方法中执行一段代码
1.
下面代码是无法运行的
public class Test1 { public void test1() { System.out.println("哈哈"); } }
但利用单元测试(Junit)可达到运行此段代码的目的,步骤:在test1方法上方加上一个@Test,并将光标放置Test上,点击提示的Junit添加至路径,即可运行
当点击run as时,你会发现出现Junit Test,点击即可运行,其一般配合@Before(被修饰的方法在@Test修饰的方法前运行)和@After使用(被修饰的方法在@Test修饰的方法后运行)
public class Test1 { @Test public void test1() { System.out.println("哈哈"); } @Before public void init() { System.out.println("哈哈之前"); } @After public void after() { System.out.println("哈哈之后"); } }
运行结果:
2. 将单元测试运用至jdbc
此例注意点,与数据库交互会产生乱码,解决----->在url处制定编码格式,如下(因为此处获取url是在配置文件中,所以在配置文件中修改编码)
public class TestJunit { // 执行数据库的链接,并获取执行sql语句的对象 Connection conn = null; Statement st = null; ResultSet set = null; @Before public void getConnection() throws Exception { conn = JdbcUtils.getConnection(); st = conn.createStatement(); } // 执行数据库的增删改查 @Test public void test() throws SQLException { //增 // st.execute("insert into products values(null ,'一个抢手的故事',10,'文艺',1,'一把锃亮的手枪')"); //删 // st.execute("delete from products where id = 11"); //改 // st.execute("update products set name='奋斗' where id =1"); // 查 ResultSet set = st.executeQuery("select * from products"); while(set.next()) { System.out.println(set.getString(2)); } } // 释放资源 @After public void release() { JdbcUtils.release(set, conn, st); } }
四 SQL注入
SQL Injection:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串中,最终达到欺骗服务器执行恶意的SQL命令。
具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
以下是会造成sql注入的代码:
public static void main(String[] args) throws Exception { DriverManager.registerDriver(new Driver()); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_demo1", "root", "feng"); // 获取执行sql语句的对象 String sql = "select * from products where id >20 or 1=1"; Statement st = conn.createStatement(); ResultSet set = st.executeQuery(sql); //遍历结果集 while (set.next()) { //获取字段值 为name的数据 String name = set.getString("name"); System.out.println(name); } // 释放资源 set.close(); st.close(); conn.close(); } }
此处的sql语句中的“1=1”直接使得where条件无效,导致可以查出products表中的所有消息,解决方法
使用PrepareStatement对象
public class JdbcDemo3 { public static void main(String[] args) throws Exception { DriverManager.registerDriver(new Driver()); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_demo1", "root", "feng"); // 获取执行sql语句的对象 String sql = "select * from products where id >? "; PreparedStatement ps = conn.prepareStatement(sql); // 对sql语句进行预编译 // ? 占位符 ,1表示第一个? ps.setString(1, "20 or 1=1"); ResultSet set = ps.executeQuery(); //遍历结果集 while (set.next()) { //获取字段值 为name的数据 String name = set.getString("name"); System.out.println(name); } // 释放资源 set.close(); ps.close(); conn.close(); } }
运行无结果,即阻止了sql注入
五. 数据库连接池
1. 什么是数据库连接池
数据库连接池是程序员启动时建立足够多的数据库连接,并将这些连接组成一个连接池,由程序动态的对池中的连接进行申请,使用,释放。
优点:节省创建连接与释放连接的性能消耗(即创建对象与回收对象的性能消耗);连接池中连接起到复用的作用 ,提高程序性能
2. 数据库连接池的运行机制
(1)程序初始化时,创建连接池
(2)使用时向连接池申请可用连接
(3)使用完毕,将连接返还给连接池
(4)程序退出时,断开所有连接,并释放资源
3. C3p0连接池(自动读取配置文件(底层封装了读取,加载配置文件的代码),加载四大参数,当连接断开时,其会自动尝试连接) dbcp连接池(半自动) druid(德鲁伊,阿里巴巴的开源连接池)
4. C3p0的使用
4.1 添加两个jar包,并添加路径
4.2 数据库的原配置文件
(1)db.properties 前面的关键字可以不按规定写,但是需要自己加载,如下面这样写就需要自己加载配置文件
(2)以下两种配置方式,c3p0连接池会自动读取配置文件,数据源
第一种方式:c3p0.properties
注意: c3p0.properties 文件名不能改,必须放在src下,配置文件中的key名称固定
案例1
product类(javabean)
public class Product { int id ; String name ; double price ; String category ; int pnum ; String description ; // 省略。。。。。 }
public class C3p0Demo1 { public static void main(String[] args) throws Exception { // 操作数据库,连接池对象[四大参数] ComboPooledDataSource dateSource = new ComboPooledDataSource(); //连接 ,获取Statement对象 resultset对象 QueryRunner runner = new QueryRunner(dateSource); // 执行sql语句 String sql = "select * from products"; List<Product> list = runner.query(sql,new BeanListHandler<>(Product.class)); // 此处若是确定返回的是一条数据,则可以使用new BeanHandler<>(product.class)为参数 for (Product product : list) { System.out.println(product); } } }
运行结果
案例二
public class C3p0Demo2 { public static void main(String[] args) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); QueryRunner runner = new QueryRunner(dataSource); // 查 String sql = "select * from products where id=?"; // runner执行sql语句时会对其进行预编译 Product query = runner.query(sql, new BeanHandler<>(Product.class),"100 or 1=1"); Product query1 = runner.query(sql, new BeanHandler<>(Product.class),1); //返回单条数据 System.out.println(query);//null System.out.println(query1);//Product [id=1, name=奋斗, price=100.0, category=励志, pnum=100, description=一次心灵的鸡汤] } }
注意,若以上的product.class定义的字段(属性),与数据库中获取的字段不一致,则数据存不进product这个javabean中的相应字段中去,如数据库中的products表格中有name字段,若将product这个javabean的name字段改为userName,则获取到的name属性将不能封装进product这个javabean中,userName值将全为空
第二种形式(不大懂)
可以配置多个数据库连接并且可以指定名字去加载