一 背景
java程序需要操作数据库,而每个数据库都有自己的访问协议
。如果使用socket编程,需要考虑的细节太多了。
为了解决这个问题,java增加了一个抽象层
—JDBC。
之后程序员只需要面向JDBC编程,由JDBC去操作数据库。JDBC是一组规范
,其具体实现是由对应的数据库厂商提供
。我们只需要将提供的实现类加载
,然后就可以按照JDBC提供的API
去操作数据库。

体系结构
JDBC接口(API)包括两个层次:
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

API
JDBC API 是一系列的接口,它使得应用程序能够进行数据库联接,执行SQL语句,并且得到返回结果。

Driver接口
Java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商
使用的,不同数据库厂商提供不同的实现
在程序中不需要直接去访问
实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager
)去调用这些Driver实现
加载与注册 JDBC 驱动
加载 JDBC 驱动
需调用 Class 类的静态方法forName()
,向其传递要加载的 JDBC 驱动的类名
DriverManager 类是驱动程序管理器类
,负责管理驱动程序
通常不用
显式调用 DriverManager 类的registerDriver(
) 方法来注册驱动程序类的实例,因为Driver 接口
的驱动程序类都包含了静态代码块
,在这个静态代码块中,会调用 DriverManager.registerDriver()
方法来注册自身的一个实例
总结
我们用forName加载Driver接口的实现类后,该类的静态代码块会自动找到DriverManager`并注册
Connection
可以调用 DriverManager 类的 getConnection()
方法建立到数据库的连接
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:<子协议>:<子名称>
- 协议:JDBC URL中的协议总是jdbc
- 子协议:子协议用于标识一个数据库驱动程序
-
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息
image.png
常用数据库的JDBC URL
- 对于 Oracle 数据库连接,采用如下形式:
jdbc:oracle:thin:@localhost:1521:sid
- 对于 SQLServer 数据库连接,采用如下形式:
jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=sid
+对于 MYSQL 数据库连接,采用如下形式:
jdbc:mysql://localhost:3306/sid
Statement
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
Statement
PrepatedStatement
-
CallableStatement
用法
- 通过调用 Connection 对象的
createStatement
方法创建该对象 - 该对象用于执行
静态的 SQL 语句
,并且返回执行结果
- Statement 接口中定义了下列方法用于执行 SQL 语句:
- ResultSet excuteQuery(String sql)
- int excuteUpdate(String sql)
ResultSet
- 通过调用 Statement 对象的
excuteQuery()
方法创建该对象 - ResultSet 对象以逻辑表格的形式封装了执行数据库操作的
结果集
,ResultSet 接口由数据库厂商实现
- ResultSet 对象维护了一个指向
当前数据行的游标
,初始的时候,游标在第一行之前
,可以通过 ResultSet 对象的next()
方法移动到下一行 - ResultSet 接口的常用方法:
- boolean next()
- getString()
- …
数据类型转换表

二 用法
创建Connection
File file = new File("src/main/resources/jdbc.properties");
Properties prop = new Properties();
Connection connection = null;
try {
prop.load(new FileInputStream(file));
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pwd = prop.getProperty("password");
try {
Class.forName(driver);
connection = DriverManager.getConnection(url,user,pwd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
Statement用法
Connection connection = getConnection();
try {
Statement statement = connection.createStatement();
String sql = "INSERT INTO jcbc_1 VALUES(NULL ,'XCX')";
statement.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
preparedStatement
- 不用拼接字符串,便于阅读、维护。
- 防止
sql注入
。 - 一般数据库都对预编译语句
提高优化
,因为预编译语句调用的频率较高。数据库在编译预编译语句后会在加到缓存
中,如果之后调用相同的语句,就可以不用再编译。
缓存是对整个数据库有效
,不仅仅局限于一个Connection
。
当然并不是
所有预编译语句都一定会被缓存
,数据库本身会运用一种SQL语句优化策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果。以保存有更多的空间存储新的预编译语句。
Connection connection = getConnection();
String sql = "SELECT id,name,ctime FROM jcbc_1 WHERE id = ? AND name = ?";//占位符
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1,1);//下标从1开始
ps.setString(2,"zyc");
ResultSet resultSet = ps.executeQuery();
// ps.setDate(3,new Date(new java.util.Date().getTime()));Date是java.sql.Date类
while (resultSet.next()){
System.out.println(resultSet.getInt(1));
System.out.println(resultSet.getString(2));
System.out.println(resultSet.getDate(3));
}
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
connection.close();
}
三 元数据结合反射技术
DatabaseMetaData
DatabaseMetaData 类中提供了许多方法用于获得数据源
的各种信息,通过这些方法可以非常详细的了解数据库的信息:
1 . getURL():返回一个String类对象,代表数据库的URL。
- getUserName():返回连接当前数据库管理系统的用户名。
- isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
- getDatabaseProductName():返回数据库的产品名称。
- getDatabaseProductVersion():返回数据库的版本号。
- getDriverName():返回驱动驱动程序的名称。
- getDriverVersion():返回驱动程序的版本号。
ResultSetMetaData
可用于获取关于ResultSet
对象中列的类型和属性信息的对象:
- getColumnName(int column):获取指定
列的名称
- getColumnCount():返回当前 ResultSet 对象中的
列数
。 - getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
- getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
- isNullable(int column):指示指定列中的值是否可以为 null。
- isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
为了实现数据绑定到对象上
复用代码,找去相似代码的共性,抽象
public static <T> T testResultMetaData(Class<T> cla,String sql,Object... objs) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
Connection connection = getConnection();
T t = cla.newInstance();
try {
PreparedStatement ps = connection.prepareStatement(sql);
for(int i=0;i<objs.length;i++){
ps.setObject(i+1,objs[i]);//将传入的参数依次赋值给ps中"?"占位符
}
ResultSet resultSet = ps.executeQuery();
ResultSetMetaData rsmd = resultSet.getMetaData();
if(resultSet.next()){
for(int i =0;i<rsmd.getColumnCount();i++){
String lable = rsmd.getColumnLabel(i+1);
Object value = resultSet.getObject(lable);
//通过反射为空实例赋值
Field field = cla.getDeclaredField(lable);
field.setAccessible(true);//不正规
field.set(t,value);
}
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return t;
}
这里最好不要用反射,性能差。可以用BeanCopier
,参考: Bean复制的几种框架性能比较(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)
生成自动的主键值
Connection connection = getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO jcbc_1 VALUES(null,?,?)",Statement.RETURN_GENERATED_KEYS);
ps.setString(1,"ycy");
long times = new java.util.Date().getTime();
ps.setDate(2,new Date(times));
ps.executeUpdate();//必须执行一次,不然不会去执行插入操作
ResultSet rs = ps.getGeneratedKeys();//这只是用来获取返回结果,不是开始执行操作,和上面不太一样
while (rs.next()){
System.out.println(rs.getInt(1));
}
rs.close();
connection.close();
操作BLOB(Binary Large Object)
添加
try {
is = new FileInputStream("img1.png");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
statement.setBlob(3,is);
statement.executeUpdate();
四 事务
当一个连接对象被创建时,默认
情况下是自动提交事务
:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
用一个Connection实例生成的对象产生的不同Statement去执行操作。
- 通过setAutocommit() 开始事务,取消默认提交
- 通过commit() 提交事务
- 通过rollback()关闭事务
JDBC也可以控制事务级别
connection.getTransactionIsolation();
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//设置后的隔离界别相当于在sql命令行中 使用 set transaction isolation level read committed
五 批量操作
当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性
提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC的批量处理语句包括下面两个方法:
- addBatch(String):添加需要批量处理的SQL语句或是参数;
- executeBatch();执行批量处理语句;
通常我们会遇到两种批量执行SQL语句的情况:
多条SQL语句的批量处理;
一个SQL语句的批量传参;


网友评论