JDBC学习总结
JDBC总结
好记性,不如烂笔头(烂键盘).不知为何我的e键莫名被磨没了.
概述
JDBC(Java Database Connectivity) 是Java语言为操作数据库定义的一套通用的API.充分体现了Java的一次编译,处处运行的特点. JDBC的API主要集中在java.sql
和javax.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基本步骤
- 注册驱动
- 建立数据库连接
- 准备SQL语句
- 获取SQL语句发送器
- 发送并执行SQL语句,得到结果集
- 处理结果集
- 关闭资源
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
表中有如下的字段
- 那么我们建立一个类如下:
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):指示是否自动为指定列进行编号,这样这些列仍然是只读的。