JDBC总结

好记性,不如烂笔头(烂键盘).不知为何我的e键莫名被磨没了.

概述

JDBC(Java Database Connectivity) 是Java语言为操作数据库定义的一套通用的API.充分体现了Java的一次编译,处处运行的特点. JDBC的API主要集中在java.sqljavax.sql 这两个包下面

1.1 JDBC调用层次

Java执行数据库操作采用的三层架构模型

1.2 ODBC

在JDBC之前,微软就定义了ODBC(Open Database Connectivity)提供一套标准的API来访问数据库管理系统,使得ODBC具有最大的独立性:与具体的编程语言无关,与具体的数据库系统无关,与具体的操作系统无关。

1.3 JDBC驱动程序类型

类型一: JDBC-ODBC桥

这应该是历史问题, 所有JDBC的调用传递给ODBC,再让后者调用数据库本地驱动代码(也就是数据库厂商提供的数据库操作二进制代码库,例如Oracle中的oci.dll)。

类型二 本地API驱动

这种类型的驱动通过客户端加载数据库厂商提供的本地代码库(C/C++等)来访问数据库.但是速度比类型一块

类型三 网络协议驱动

驱动给客户端提供了一个网络API,客户端上的JDBC驱动程序使用套接字(Socket)来调用服务器上的中间件程序,后者在将其请求转化为所需的具体API调用。

类型四 本地协议驱动

这种类型的驱动使用Socket,直接在客户端和数据库间通信。速度最快

1.4 编写JDBC基本步骤

  1. 注册驱动
  2. 建立数据库连接
  3. 准备SQL语句
  4. 获取SQL语句发送器
  5. 发送并执行SQL语句,得到结果集
  6. 处理结果集
  7. 关闭资源

1.5 常用的接口

我们打开源码,可以看到两个包下有很多的接口,这就是JDBC的规范

  • Driver 有数据库管理系统实现该接口,来实现加载对应数据库管理系统的驱动程序.
  • DriverManger 一个具体的类,提供一套比较基础的Driver管理
  • Collenction 一个数据库的连接,相当于一个对话.
  • Statement 执行SQL语句的载体.一般我们不用该接口直接去执行SQL语句,而是使用它的子类
  • PreparedStatement Statement的子类,使用占位符?来填充具体的SQL语句,安全性比Statement高 .
  • RestultSet SQL语句执行的返回结果,一般是Select语句

第二章 获取数据库连接

2.1 Driver接口的实现类

在开发中,我们并不需要自己去实现该类,而是有具体的数据库厂商来实现 ,例如MySQL的JDBC的实现类com.mysql.cj.jdbc.Driver .

2.2 获得连接Connection

方式一: 硬编码

  @Test
    public void testDriver1() throws SQLException {
        Driver driver = new com.mysql.cj.jdbc.Driver();
        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "123456");
        String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
        Connection connection = driver.connect(url, info);
        System.out.println(connection);
    }
    //输出: 
    //com.mysql.cj.jdbc.ConnectionImpl@2e1d27ba

方式二:通过反射获取Driver

      Class driverClass = Class.forName("com.mysql.cj.jdbc.Driver");
            Constructor constructor=driverClass.getConstructor();
            Driver driver=(Driver) constructor.newInstance();
            //这样代码的可移植性跟高.

方式三:使用DriverManger

     driverClass = Class.forName("com.mysql.cj.jdbc.Driver");
            Constructor constructor=driverClass.getConstructor();
            Driver driver=(Driver) constructor.newInstance();
            DriverManager.registerDriver(driver);
            String user="root";
            String url="jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
            String password="123456";
            Connection connection=DriverManager.getConnection(url,user,password);

方式四:使用配置文件(最常用的)

     InputStream stream= ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        try {
            properties.load(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String user= properties.getProperty("user");
        String password = properties.getProperty("password");
        String url =properties.getProperty("url");
        String myDriver = properties.getProperty("driverClass");
        //使用XML文件也是相似的,一般框架中都提供对应的Factory.简化加载

:
后面的章节中,我们把方式四,写成一个方法,来直接获取Conncetion getConnection

第三章 Statement和PreparedStatement操作数据库

3.1 Statement操作数据库

Statement执行数据连接有点类似有硬编码的方法,用字符串生成SQL语句,交给数据库管理系统执行.

String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";

假设我们需要执行的是这条语句,特就是当user和password直接连接进sql语句中,如果在user后面加上OR 1=1,那么不就永远都执行成功了,而且还能得到所有的用户名和密码.
这里直接使用SQL语句直接执行查看结果.

SELECT 
    user, password
FROM
    user_table
WHERE
    user = 'AA'
        OR 1 = 1 AND password = '1';

所以说,一般我们都不使用Statem来实现执行SQL语句,而是使用PreparedStatement来执行SQL语句.

3.2 PreparedStatement操作数据库

PreparedStatement是Statement的实现类.比Statement更加的安全.用于执行的SQL语句预编译语句. 同时数据库管理系统也该语句进行了缓存,使得重复执行速度更快.

3.2.1 PreparedStatement常用的API

  • execute() 普通的运行语句,返回为boolean类型,是否有结果集
  • executeQuery() 带有ResultSet的查询,一般是select语句
  • addBatch() 批处理,执行大量相同的语句是,可以使用批处理
  • clearBatch() 清空Batch
  • executeBatch() Batch积累一定量,执行Batch.
  • executeUpdate() 运行增删改操作,返回更新的行数
    这里面还有一堆的set方法用来填充占位符,例如setObject()``setString 下面的函数中有体现

3.2.2 一个相对通用的增删改操作的实现

//一个比较通用的利用PreparedStatement实现的操作
//相对通用的增删改
	public void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同
		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]); //占位符计数是从1开始的. 
			}
			//4.执行
			ps.execute();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			//5.资源的关闭
			JdbcUtils.close(conn, ps); //需要自己实现
		}	
	}

第四章 ResultSet 结果集

用来接收SQL语句的执行结果,一般是SELECT语句的结果

Result中常用的APU

  • next() 是否有下一条记录,有则移动到下一条记录
  • Previous() 移动到上一条记录
  • absolute(int row) 指定到某一行
  • beforeFirst(): 移动resultSet的最前面
  • afterLast() : 移动到resultSet的最后面

然后就是一系列的Get方法了,得到我们想要的数据.例如getInt(int index),getString(int index);
一般我们使用 getInt(String columnName) 来获取对应的操作.这样具有更好的可读性

一个比较通用的select查询函数

public <T> T getInstance(Class<T> clazz,String sql, Object... args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn =JdbcUtils.getConnection();

			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setObject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getObject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				return t;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.close(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();

			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setObject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();
			//创建集合对象
			ArrayList<T> list = new ArrayList<T>();
			while (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getObject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				list.add(t);
			}
			
			return list;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.closeResource(conn, ps, rs);

		}

一般的数据库查询方法

建立对应记录(行)的对象ORM对象关系映射.
假设我们的user表中有如下的字段

  1. 那么我们建立一个类如下:
public class User {
    private int id;
    private String name;
    private String password;
    private String address;
    private  String phone;
}

之后生成对于对应的get和set方法,以及对应的构造函数.
2. 建立对应的DAO接口,里面定义了对应USer类的操作方法.
例如:

public interface UserDAO {
    void insert(Connection connection,User user);
    void deleteById(Connection connection,int id);
    void update(Connection conn,User user);
    ...
}

3.对应DAO的实现UserImpl

public UserImpl implements UserDAO{
     @Override
    public void insert(Connection connection, User user) {
        String sql = "insert into user(name,password,address,phone)values(?,?,?,?)";
        update(conn, sql,user.getName(),user.getPassword(),user.getAddress(),user.getPhone());
    }
}

第五章 JDBC中的事务

5.1 MySQL中的事务

隔离界别 描述
Read uncommitted 允许事务读取被其他事务提交的变更.脏读,可重复读,幻读的问题都会出现
read commited 只允许事务读取已经被其他事务的提交的变更. 可以避免脏读. 但是不可避免重复读,和幻读
repeatable read (可重复读) 确保事务可以多次从一个字段中读取相同的值.在这个事务持续的期间,禁止其他事务对这个字段进行更新.可以避免脏读和不可重复读.但幻读问题依然出现
serializable 串行化 确保事务可以重表中读取相同的行,但是在这个事务持续的时期间,禁止其他事务对该表插入执行,更新,和删除操作.所有的并发问题都可以避免

5.2 在Java中使用事务

 @Test
    public  void testUpdateWithTx() {
        Connection connection =JDBCUtils.getConnection();
        try {
            //输出数据库管理系统的隔离级别
            System.out.println(connection.getTransactionIsolation());
            //设置不自动提交事务
            connection.setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        try {
            String  sql1="update user_table set balance =balance -100 where user=?";
            update2(connection,sql1,"AA");
            //System.out.println(1/0);
            String sql2="update user_table set balance =balance +100 where user=?";
            update2(connection,sql2,"BB");
            //提交事务
            connection.commit();
        } catch (SQLException throwables) {
            //发生异常,进行回滚
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }finally {
           JDBCUtils.closeConncetion(connection,null);
        }

    }

第六章 其他一些常用的方法和类

6.1 ResultSetMetaData类

提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息

  • getColumnCount()字段的个数:
  • getColumnLabel() 字段的标签, 例如Java的命名和数据库管理系统的命名不一致.
     DatabaseMetaData 类中提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息:
  • getURL():返回一个String类对象,代表数据库的URL。
  • getUserName():返回连接当前数据库管理系统的用户名。
  • isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
  • getDatabaseProductName():返回数据库的产品名称。
  • getDatabaseProductVersion():返回数据库的版本号。
  • getDriverName():返回驱动驱动程序的名称。
  • getDriverVersion():返回驱动程序的版本号。

  • getColumnName(int column):获取指定列的名称
  • getColumnCount():返回当前 ResultSet 对象中的列数。
  • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
  • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
  • isNullable(int column):指示指定列中的值是否可以为 null。
  • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

版权声明:本文为king0原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/king0/p/12759092.html