目录
JDBC相关介绍
数据的持久化
数据库存储技术
数据访问
体系结构
编写步骤
获取数据库连接
要素一:Driver接口实现类
要素二:URL
要素三:用户名和密码
具体实现
操作和访问数据库
图解
数据库的调用方式
ResultSet
ResultSetMetaData
资源的释放
代码实现:通用的增删改操作
代码实现:通用的查询操作
总结
两种思想
两种技术
JDBC相关介绍
概述:JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库的公共接口(一组API)。
数据的持久化
持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用,持久化的实现过程大多通过各种关系数据库来完成
数据库存储技术
在Java中,数据库存取技术可分为如下几类:
1. JDBC直接访问数据库
2. JDO(Java Data Object )技术
3. 第三方O/R工具,如Hibernate, Mybatis等JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
数据访问
没有JDBC,java程序访问数据库 有了JDBC,java程序访问数据库 总结
体系结构
JDBC接口(API)包括两个层次:
1. 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果
2. 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。(面向接口编程)
编写步骤
说明:ODBC(Open Database Connectivity,开放式数据库连接)
获取数据库连接
要素一:Driver接口实现类
Driver接口 java.sql.Driver接口是所有JDBC驱动程序需要实现的接口
这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现
在程序中不需要直接去访问实现了Driver接口的类
而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现
Oracle的驱动:oracle.jdbc.driver.OracleDriver
mySql的驱动:com.mysql.jdbc.Driver加载驱动 加载JDBC驱动需调用Class类的静态方法forName(),向其传递要加载的JDBC驱动的类名
Class.forName(“com.mysql.jdbc.Driver”)注册驱动 DriverManager类是驱动程序管理器类,负责管理驱动程序 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动 通常不用显式调用DriverManager类的registerDriver()方法来注册驱动程序类的实例
因为Driver接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用
DriverManager.registerDriver()方法来注册自身的一个实例
要素二:URL
说明 JDBC URL用于标识一个被注册的驱动程序,驱动程序管理器通过这个URL选择正确的驱动程序,从而建立到数据库的连接 JDBC URL的标准由三部分组成,各部分间用冒号分隔
jdbc:子协议:子名称
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息
包含主机名(对应服务端的ip地址),端口号,数据库名
图解 常用数据库的JDBC URL
MySQL的连接URL编写 1. jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参=值&参数=值
2. jdbc:mysql://localhost:3306/test
3. jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
如果JDBC程序与服务器端的字符集不一致,会导致乱码
可以通过参数指定服务器端的字符集
4. jdbc:mysql://localhost:3306/test?user=root&password=123456
Oracle 9i的连接URL编写 1. jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
2. jdbc:oracle:thin:@localhost:1521:test
SQLServer的连接URL编写 1. jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
2. jdbc:sqlserver://localhost:1433:DatabaseName=atguigu
要素三:用户名和密码
- user,password可以用"属性名=属性值"方式告诉数据库
- 可以调用DriverManager类的getConnection()方法建立到数据库的连接
具体实现
使用配置文件的方式保存配置信息,在代码中加载配置文件 使用配置文件的好处 实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 如果修改了配置信息,省去重新编译的过程 配置文件设计 因为操作数据库时会多次获取连接,所以就把获取连接和关闭资源的功能代码放在工具类中。
package util; //导包 import java.io.InputStream; import java.sql.*; import java.util.Properties; //操作数据库的工具类 public class JDBCUtils { //获取数据库的连接 public static Connection getConnection() throws Exception { //1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); //文件内容读到集合 Properties prop = new Properties(); prop.load(is); //获取对应的值 String user = prop.getProperty("user"); String password = prop.getProperty("password"); String url = prop.getProperty("url"); String driverClass = prop.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); return conn; } //关闭资源 public static void closeResource(Connection conn, Statement ps){ try { if (ps != null) ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } //关闭资源 public static void closeResource(Connection conn, Statement ps,ResultSet rs){ try { if (ps != null) ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); }try { if (rs != null) rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
操作和访问数据库
概述:数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接
图解
数据库的调用方式
Statement 概述:用于执行静态SQL语句并返回它所生成结果的对象 接口中定义了的方法 int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):执行查询操作SELECT
弊端 存在拼串操作,繁琐
说明:"SELECT user,password FROM user_table
WHERE user = '"+ user +"' AND password = '"+ password +"'"
存在SQL注入问题
说明:SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查
而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password
FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1')
从而利用系统的SQL引擎完成恶意行为的做法
PrepatedStatement 概述:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句 介绍:PreparedStatement接口是Statement的子接口,它表示一条预编译过的SQL语句 创建:可以通过调用Connection对象的preparedStatement(String sql)方法获取PreparedStatement对象 使用 PreparedStatement对象所代表的SQL语句中的参数用问号(?) 来表示
调用PreparedStatement对象的setObject()方法来设置这些参数
setObject()方法有两个参数
第一个参数是要设置的SQL语句中的参数的索引(从1开始)
第二个是设置的SQL语句中的参数的值
PreparedStatement VS Statement 1. PreparedStatement可以操作Blob的数据,Statement做不到
2. PreparedStatement能实现更高效的批量操作,最大可能提高性能
3. PreparedStatement可以解决Statement拼串和防止SQL注入
CallableStatement 概述:用于执行SQL存储过程 图解
ResultSet
说明:查询需要调用PreparedStatement的executeQuery()方法,查询结果是一个ResultSet对象 介绍 ResultSet对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet接口由数据库厂商提供实现 ResultSet返回的实际上就是一张数据表,有一个指针指向数据表的第一条记录的前面 使用 ResultSet对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前
可以通过ResultSet对象的next()方法移动到下一行。调用next()方法检测下一行是否有效
若有效,该方法返回true,且指针下移。相当于Iterator对象的hasNext()和next()方法的结合体
当指针指向一行时, 可以通过调用getXxx(int index)或getXxx(int columnName)获取每一列的值
例如: getInt(1), getString("name")
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始常用方法 - boolean next()
- getString()图解 Java与SQL
对应数据类型转换表
ResultSetMetaData
说明 可用于获取关于ResultSet对象中列的类型和属性信息的对象
ResultSetMetaData meta = rs.getMetaData();常用方法 getColumnName(int column):获取指定列的名称
getColumnLabel(int column):获取指定列的别名
getColumnCount():返回当前ResultSet对象中的列数getColumnTypeName(int column):检索指定列的数据库特定的类型名称
getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位
isNullable(int column):指示指定列中的值是否可以为null
isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的注意 表的字段名与类的属性名不相同的情况:
1. 必须声明sql时,使用类的属性名来命名字段的别名
2. 使用ResultSetMetaData时,需要使用getColumnLabel()来替换getColumnName()获取列的别名说明:如果sql中没有给字段起别名,getColumnLabel()获取的就是列名
图解
资源的释放
释放ResultSet,、Statement、Connection 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机
Connection的使用原则是尽量晚创建,尽量早的释放
可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭
代码实现:通用的增删改操作
说明:获取数据库连接和资源关闭使用的是之前创建的工具类中的方法。
//通用的增删改操作 //sql中占位符的个数与可变形参的长度相同! public void update(String sql,Object ...args) { Connection conn = null; PreparedStatement ps = null; try { //1.获取数据库连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //4.执行 ps.execute(); }catch (Exception e){ e.printStackTrace(); }finally { //5.资源关闭 JDBCUtils.closeResource(conn, ps); } }
代码实现:通用的查询操作
说明:查询操作有返回值,我们一般使用创建对象,再给对象赋值的方式输出结果。
//结果集的输出方式 //方式一: //System.out.println("id="+id+" name="+name+" email="+email+" Date:"+birth); //方式二: //Object[] date = new Object[]{id,name,email,birth}; //方式三:将数据封装为一个对象(推荐)查询一条记录:
//针对于不同表的通用查询操作,返回表中一条记录 public <T> T getInstance(Class<T> clazz,String sql,Object ...args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接 conn = JDBCUtils.getConnection(); //预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //执行:捕获结果集 rs = ps.executeQuery(); //获取结果集的元数据 ResultSetMetaData rsmd = rs.getMetaData(); //获取列数 int columnCount = rsmd.getColumnCount(); if (rs.next()){ //创建此Class对象所表示类的实例 T t = clazz.newInstance(); //处理结果集一行数据中的每一个列 for (int i = 0; i < columnCount; i++) { //获取每个列的列值:通过ResultSet Object columnValue = rs.getObject(i + 1); //获取每个列的列名:通过ResultSetMetaData String columnLabel = rsmd.getColumnLabel(i + 1); //通过反射,将对象指定名columnName的属性赋值为指定的值columnValue Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columnValue); } return t; } }catch (Exception e){ e.printStackTrace(); }finally { //释放资源 JDBCUtils.closeResource(conn,ps,rs); } return null; }
查询多条记录:
//针对于不同表的通用查询操作,查询多条记录 public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接 conn = JDBCUtils.getConnection(); //预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //执行:捕获结果集 rs = ps.executeQuery(); //获取结果集的元数据 ResultSetMetaData rsmd = rs.getMetaData(); //获取列数 int columnCount = rsmd.getColumnCount(); //创建集合对象 ArrayList<T> list = new ArrayList<>(); while (rs.next()){ //创建此Class对象所表示类的实例 T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0; i < columnCount; i++) { //获取每个列的列值:通过ResultSet Object columnValue = rs.getObject(i + 1); //获取每个列的列名:通过ResultSetMetaData String columnLabel = rsmd.getColumnLabel(i + 1); //通过反射,将对象指定名columnName的属性赋值为指定的值columnValue Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columnValue); } //添加对象到集合 list.add(t); } return list; }catch (Exception e){ e.printStackTrace(); }finally { //释放资源 JDBCUtils.closeResource(conn,ps,rs); } return null; }
总结
两种思想
- 面向接口编程的思想
- ORM思想(object relational mapping)
一个数据表对应一个java类
表中的一条记录对应java类的一个对象
表中的一个字段对应java类的一个属性两种技术
- JDBC结果集的元数据:ResultSetMetaData
获取列数:getColumnCount()
获取列的别名:getColumnLabel()- 通过反射,创建指定类的对象,获取指定的属性并赋值