前言
本文只是个人对JDBC与DAO封装的见解,如有错误与不足,望大佬不舍赐教,评论指出!
学海无涯,共同进步!
JDBC介绍
JDBC(Java DataBase Connectivity,java数据库连接)是java和数据库之间的一个桥梁,是java语言中用来规范客户端程序如何来访问数据库的应用程序接口(不是具体实现),提供了诸如查询和更新数据库中数据的方法。
JDBC 编程步骤
1.装载相应数据库的JDBC驱动(即导入相应的jar包)
- JDBC是java提供的一套访问数据库的接口规范,并不是具体实现。所以我们使用JDBC访问数据库时必须导入相应数据库公司提供的JDBC驱动(jar包),里面是对JDBC接口的具体实现,不同数据库的实现不同,所以也需要导入不同的驱动。以MySql为例,我们需要导入一个叫mysql-connector-java-8.0.19.jar的文件(版本很多,其他版本也行)。点击前往下载jar包
- 如果是使用meven管理的项目,我们导入这个jar包只需要在项目的pom.xml中写入以下代码。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency>
- 如果我们没有使用maven管理项目,我们则需要去网上手动下载这个jar文件,然后在项目导入。
Eclipse导包步骤:右键project -> project -> java bulid path -> libaries -> add external jars
2.初始化数据库驱动
- 以MySql数据库和我们刚导入的jar包为例
注意:当我们把Driver的类名或者路径写错时,会抛出一个ClassNotFoundException的异常,所以我们这里需要捕获异常try{ Class.forName("com.mysql.cj.jdbc.Driver"); }catch(ClassNotFoundException e){ e.printStackTrace(); }
Class.forName是把这个类加载到虚拟机中,加载的时候就会执行其中的静态代码块,完成初始化数据库驱动的相关工作
3.建立JDBC和数据库之间的Connection连接
-
Connection是JDBC中的数据库连接接口,一个Connection实例就代表一个数据库连接。Connection通过 DriverManager(驱动管理器)类获得连接。连接时需要3个参数:(以MaySql为例)
url :jdbc:mysql://localhost(数据库服务端IP地址):3306(端口号)/databaseName(数据库名称)
userName:数据库管理员用户名
password:管理员密码try{ //创建一个数据库连接 Connection conn = DriverManager.getConnection(url,userName,password); }catch(SQLException e){ e.printStackTrace(); }
注意:当3个参数中存在错误时,会抛出一个SQLException,所以这里也要进行异常处理。
if(conn != null){ try{ //关闭数据库连接 conn.close(); }catch(SQLException e){ e.printStackTrace(); } }
使用完数据库连接之后,一定要关闭连接!!!一定要关闭连接!!!一定要关闭连接!!!
4.创建Statement或者PreparedStatement接口对象,执行SQL语句
-
Statement和PreparedStatement接口都是用来执行sql语句的,包含3个主要的方法:(下文提到的ResultSet请看第5点)
ResultSet executeQuery()throws SQLException;建议用于执行查询操作
int executeUpdate()throws SQLException;建议用于执行增、删、改操作
int execute()throws SQLException;建议用于执行数据库、表操作
注意:Statement对象调用这3个方法必须将sql语句作为参数传入 -
使用Statement接口
try { //创建Statement对象 Statement stmt = conn.createStatement(); //SQL语句 String sql = "select 'hello' as a from dual"; //执行SQl获得结果集(ResultSet) ResultSet rs = stmt.executeQuery(sql); while(rs.next()){ //输出结果集中的内容 System.out.println(rs.getString("a")); } } catch (SQLException e) { e.printStackTrace(); }
-
使用PreparedStatement接口
try { //创建PreparedStatement对象(?为占位符) PreparedStatement ptmt = conn.prepareStatement("select * from user while userName = ?"); //为?占位符所占位置赋值,如果没有使用?占位符则可省略这步 ptmt.setString(1,"userName"); //执行sql ResultSet rs = ptmt.executeQuery(); while (rs.next()){ System.out.println(rs.getString("userName")); } } catch (SQLException e) { e.printStackTrace(); }
-
Statement与PreparedStatement的区别
-
1、性能区别
创建时:
Statement statement = conn.createStatement();
PreparedStatement ptmt = conn.preparedStatement(sql);
执行时:
ResultSet rs = stmt.executeQuery(sql);
ResultSet rs = ptmt.executeQuery();
由上可以看出,PreparedStatement是有预编译的过程,已经绑定了sql,之后无论执行多少遍,都不会再去进行编译,而Statement不会,如果执行多遍,则相应的就要编译多少遍sql,所以从这点看,PreparedStatement的效率会比Statement要高一些 -
2、安全性问题
我们先看下面2个代码,都是登录的部分代码//使用statement对象 public boolean login(String userName,String password){ Connection conn = null; try{ //建立连接 conn = DriverManager.getConnection(URL,USER,PASS); //创建statement对象 Statement stmt = conn.createStatement(); //拼接sql String sql = "select userName from users where userName = '"+userName+"' and password = '"+password+"'"; //执行sql,获得结果 ResultSet rs = stmt.executeQuery(sql); //处理结果:如果查询结果中有数据,说明登录成功,否则登录失败 if(rs.next()){ return true; }else{ return false; } }catch (SQLException e){ e.printStackTrace(); throw new RuntimeException("程序异常,登录失败"); }finally { if(conn != null){ try { //关闭连接 conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
//使用PreparedStatement对象 public boolean login(String userName,String password){ Connection conn = null; try{ //建立数据库连接 conn = DriverManager.getConnection(URL,USER,PASS); //sql语句 String sql = "select userName from users where userName = ? and password = ?"; //创建PreparedStatement对象,传入sql进行预编译 PreparedStatement ptmt = conn.prepareStatement(sql); //设置sql中的参数 ptmt.setString(1,userName); ptmt.setString(2,password); //处理结果:如果查询结果中有数据,说明登录成功,否则登录失败 ResultSet rs = ptmt.executeQuery(); if(rs.next()){ return true; }else{ return false; } }catch (SQLException e){ e.printStackTrace(); throw new RuntimeException("程序异常,登录失败"); }finally { if(conn != null){ try { //关闭数据库连接 conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
这时我们登录时使用 ’ or ‘1’ = '1 作为密码,则第一段代码执行的sql语句为:
select userName from users where userName='userName' and password='' or '1'='1
导致where后面的条件成为永真式,从而导致程序有bug。而第二段代码就没有这种困扰,所以说PreparedStatement的安全性要比Statement要高。
-
5.ResultSet接口处理查询结果
- 当Statement或者PreparedStatement对象执行executeQuery()方法(即执行sql查询语句)时,会返回一个ResultSet对象,里面存储了查询结果。ResultSet常用的方法有next()和getXXXX();
- next()方法:查询结果是一个二维表, 我们可以理解为ResultSet对象中有一个指针,初始指向结果集二维表的第一行,也就是列名那一行,当执行next()方法时,如果指针所指的那一行的下一行有数据,指针会指向二维表的下一行记录,并放回true;否则返回false。
- getXXX():用于读取结果集中的数据,它的参数可以是整型表示第几列(从1开始),也可以是列名。返回的是指针指向的那一行数据对应的XXX类型的值,如果对应那列是空值,XXX是对象的话返回对象的空值,如果XXX是数字类型,如int等则返回0,boolean类型放回false。
注意:getXXX()使用整型为参数时,对应的是结果集中的第i列,而不是数据库表中的i列,建议使用列名作为参数,因为使用整型作为参数时,如果数据表中的属性值顺序发送变化会导致查询结果的顺序改变从而出错
Properties类
-
Properties是java中用来处理配置文件的类,Java中配置文件常为.properties文件,文件的内容的格式是 “键=值” 注释使用"#"。要注意的是,值读取出来是字符串类型,但是我们写配置文件时不需要加引号,一行对应一个“键=值”,末尾不需要加分号,而且=号两边不能有空格。
-
它用来配置应用程序的一些信息,比如我们可以将使用JDBC时需要的driver,url,userName,password等参数写入.properties文件中,这样我们程序后期如果需要更换数据库时,只需要对配置文件进行修改,而不需要修改源代码。
-
具体操作如下:
##数据库配置文件 driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/databaseName userName=root password=123456
//读取配置文件代码片段 private static String driver = null; private static String url = null; private static String userName = null; private static String password = null; static { /*加载数据库配置文件*/ Properties prop = new Properties(); InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties"); try { pro.load(in); driver = prop.getProperty("driver"); url = prop.getProperty("url"); userName = prop.getProperty("userName"); password = prop.getProperty("password"); System.out.println("数据库配置文件加载成功!"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("数据库配置文件加载失败!"); }finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } //加载数据库驱动的代码也可以放这里,因为驱动只需要加载一次,static代码块也只执行一次 try{ Class.forName(driver); }catch(ClassNotFoundException e){ e.printStackTrace(); throw new RuntimeException("加载数据库驱动失败!请检查配置文件中的driver配置!"); } }
DAO封装
- DAO(Data Access Object,数据存取对象)位于业务逻辑和持久化数据之间,实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来,对外提供相应的接口。
- DAO模式的优势在于隔离了数据访问代码和业务逻辑,业务代码需要访问数据直接调用DAO方法即可,完全感觉不到数据库表的存在,分工明确,数据访问层代码不影响业务逻辑代码,这符合单一职能原则,提高了可复用性。
DAO封装主要有以下部分
- 1、DAO接口:把数据库的所有操作定义成抽象方法,可以提供多种实现方式
- 2、DAO实现类:对dao接口的具体实现
- 3、实体类:用于存放与传输数据(数据库每一个数据表对应一个实体类)
- 4、数据库连接和关闭工具类:封装了数据库连接和关闭的代码,提高代码的可复用性