JDBC 概述(Java DataBase Connectivity)
持久化:把数据保存到可掉电式存储设备中以供以后使用。
持久化过程大多通过各种数据库完成。将内存中的数据存储在数据库中,也可以存储在磁盘文件、XML数据文件汇总。
JPA: JavaEE 的规范,Java persistence api:Java 持久化 API
为什么用 JDBC 规范?
加载数据库的驱动,调用驱动包获取数据库了解对象,驱动包由数据库自己去实现,sun 公司规定 JDBC 的接口和代码规范,从而去做到统一的 Java 代码去使用不同的数据库。只需要安装不同的数据库驱动。不管什么数据库,都用 JDBC 基准(工具和接口),去访问该数据库,但怎么访问是由数据库驱动包去实现。
程序员不需要对特定的数据库差异去做处理。Java 连接数据库的标准。
注意:在开发中,使用到的关于 JDBC 类的接口/对象 都是导入的 java.sql 或者 javax.sql 包里的类。
JDBC 步骤:
- 加载注册驱动
- 获取数据库连接对象
- 获取预编译语句对象
- 执行SQL命令
- 释放资源
加载注册驱动
1 2 3 4 5 6 7 8 9 10 11 12
| @Test public void testConnection() throws Exception { Driver d = new Driver(); DriverManager.deregisterDriver(d); Connection conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456"); System.out.println(conn); }
|
上述代码是有问题的:如果此时,我们更换数据库,就会出现编译错误。所以使用Class.forName(); 去加载驱动会更加适合
1 2
| Class.forName("com.mysql.jdbc.Driver");
|
获取预编译语句对象
Connection对象
Statement createStatement()
创建静态语句对象
PrepareStatement prepareStatement(String sql)
获取预编译语句对象
1 2
| Statement st = conn.createStatement();
|
使用预编译的好处:


预编译语句对象优点:
- 缓存预编译SQL,更快的编译SQL(有些数据库不支持)
- 安全
执行SQL命令
Statement对象
int executeUpdate(String sql)
执行DML/DDL操作
ResultSet executeQuery(String sql)
执行DQL操作
关闭源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| finally { try { if (st != null) { st.close(); } } catch (SQLexception e) { e.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } }
|
预编译语句对象

DQL
1 2 3 4 5 6 7
| ResultSet rs = ps.executeQuery(); while (rs.next()) { String name = rs.getString("name"); int age = rs.getInt("age"); }
rs.close();
|
Dao(Data Access Object)
一个表对应一个 DAO


夹在业务逻辑与数据库资源中间的接口。
我们把之前增删改查的操作,抽取到一个类中,以后想要做增删改查,直接调用这个类的方法即可。
为了规范抽取之后的方法名字等问题,会定义一个接口来规范增删改查。
这里只写了 impl.java 的查询,增删改都大同小异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public Product get(Long id) { Connection conn = JDBCUtil.getConnection(); PreparedStatement pStatement = null; ResultSet rs = null; String sql = "SELECT * FROM product WHERE id=?"; try { pStatement = conn.prepareStatement(sql); pStatement.setObject(1, id); rs = pStatement.executeQuery(); if (rs.next()) { Product p = new Product(); p.setProductName(rs.getString("productName")); p.setSupplier(rs.getString("supplier")); p.setBrand(rs.getString("brand")); p.setCutoff(rs.getString("cutoff")); p.setCostPrice(rs.getString("costPrice")); p.setDir_id(rs.getLong("dir_id")); p.setSalePrice(rs.getDouble("salePrice")); p.setId(id); return p; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.close(new AutoCloseable[] {rs, pStatement, conn}); } return null; }
|
Dao 接口中的方法
- 增:
void save(String name, int age);
void save(Student stu);
Student 类是一个 javabean,描述数据库表结构的一个类,把这一类javabean 叫做domain,保存的时候 student 的对象 id 为 null
- 删:
void delete(Long id)
- 改:
void update(Long id, Student newStu
; 修改的时候 Student 对象的 id 就是要改的那条记录
- 查单个:
Student get(Long id);
- 差多个:
List<Student> listAll();
规范 Dao 组件的类名和包名


Dao 规范的开发流程
- 创建连接数据库的配置文件

- 在工具类读入配置文件中的内容,创建连接对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package cn.lizhaoloveit.JDBC._Dao.util;
import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties;
public class JDBCUtil { private JDBCUtil() {} private static Properties ps = new Properties(); static { InputStream in = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("db.properties"); try { ps.load(in); Class.forName(ps.getProperty("driverClassName")); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() { try { return DriverManager.getConnection(ps.getProperty("url"), ps.getProperty("username"), ps.getProperty("password")); } catch (SQLException e) { e.printStackTrace(); } return null; } }
|
- 根据数据库的表创建javabean对象
- 在dao中写出增删改查接口

1 2 3 4 5 6 7 8 9 10 11 12
| public interface IProductDAO { void insert(Product p); void delete(Long id); void update(Product p); Product get(Long id); List<Product> gets(); }
|
- 根据接口写出实现类并写测试类逐一进行测试(写一个方法测试一个)
优化
连接数据库因为每次都要写,所以应该在工具类中事先写好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private static String driverClassName = "com.mysql.jdbc.Driver"; private static String url = "jdbc:mysql:///javaweb"; private static String username = "root"; private static String password = "123456";
static { try { Class.forName(driverClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
public static Connection getConnection() { }
|

事务 (Transaction)
每个逻辑操作单元全部完成,数据的一致性可以保证,当单元中某一部分操作失败,整个事务应全部视为错误,所有从起点后的操作,全部回退到开始状态。
开启事务 -> commit 永久保存,或者 -> rollback 回退。
事务的 ACID 属性:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation) 事务内部操作及使用的数据对并发的其他事务是隔离的。
- 持久性(Durability) 一个事务一旦被提交,它对数据库中的改变是永久的。
- 默认情况下,事务在执行完 DML 操作就自动提交。
- 查询操作是不需要事务的,但是一般在开发中都把查询放入事务
- MySQL中,只有 InnoDB 存储引擎支持事务,支持外键,MyISAM 不支持事务。
事务操作
模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| try { conn.setAutoCommit(false); 操作1... 操作2... 操作N... conn.commit(); } catch (Exception e) { conn.rollback(); } finally { }
|
开启事务,就有一个事务锁的存在,必须要调用 commit 或者 rollback 才会释放
获取自动生成的主键
- 设置一个标记,告诉数据库,需要自动生成的主键,在获取预编译语句对象的时候,传递标记 Statement.RETURN_GENERATED_KEYS.

连接池
目前操作数据库的方式:
获取数据库连接对象,执行SQL,解析结果集,关闭资源(结果集对象,预编译语句对象,数据库连接对象)。
为什么必须使用数据库连接池?
JDBC 数据库连接 (Connection 对象) 使用 DriverManager 来获取,每次向数据库建立连接都要讲 Connection 加载到内存,再验证用户名密码。非常耗时,创建成本大。
对于每一次数据库连接,使用完后都要断开,否则程序出现异常未关闭,会导致数据库系统内存泄露。最终将导致重启数据库。
当前的操作数据库的方式,花费了大量资源和时间创建数据库连接对象,使用一次就关闭,没有充分使用数据库连接对象。
连接池是一个容器,装数据库连接对象。需要对连接对象进行管理,达到最高的效率。

Java 中使用 javax.sql.DataSource 接口表示连接池。
DataSource 和 Connection Pool 是同一个
DataSource 只是个接口,由各大服务器厂商实现(Tomcat,JBoss)
常用的DataSource实现:
- DBCP: Spring 框架推荐
- druid: 阿里巴巴的连接池 (号称Java 语言中性能最好的连接池)
获取 Connection 对象
未使用连接池:Connection conn = DriverManager.getConnection(url, username, password);
使用了连接池:dataSource对象.getConnection();
返回连接对象已经是一个功能增强的连接对象了。
只要获取了 Connection 连接对象接下来操作是一模一样的。
释放 Connection对象(Connection对象.close()):
未使用连接池,是和数据库服务器断开,使用了连接池,还给连接池。

自己设计一个连接池:
DBCP 连接池
操作步骤:
- 导入相关 jar 包

- 获取连接池对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private static Properties ps = new Properties(); private static BasicDataSource dsSource = null;
static { dsSource = new BasicDataSource(); InputStream in = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("db.properties"); try { ps.load(in); dsSource.setDriverClassName(ps.getProperty("driverClassName")); dsSource.setUrl(ps.getProperty("url")); dsSource.setUsername(ps.getProperty("username")); dsSource.setPassword(ps.getProperty("password")); } catch (Exception e) { e.printStackTrace(); } }
|
整个应用中只有一个连接池对象,所有客户端(多线程)都使用该连接池对象,而连接池对象是线程安全的,数据库连接对象是不安全的,多个线程不能使用一个数据库连接对象。所以在获取数据库连接对象时,每个线程都是拿的唯一的那个。
连接池的快捷创建方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| private static Properties ps = new Properties(); private static BasicDataSource dsSource = null;
static { dsSource = new BasicDataSource(); InputStream in = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("db.properties"); try { ps.load(in); ds = BasicDataSourceFactory.createDataSource(ps); } catch (Exception e) { e.printStackTrace(); } }
public static Connection getConnection() { try { Connection conn = dsSource.getConnection(); System.out.println(conn); return conn; } catch (Exception e) { e.printStackTrace(); } return null; }
|
Druid 连接池
druid: 是阿里巴巴研发出来的号称 Java 语言性能最高的连接池。
wiki地址
支持:MySQL、Oracle、DB1、sql Server等
支持对配置文件的密码加密
导入 jar druid-1.0.x.jar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private static Properties ps = new Properties(); private static DataSource dsSource = null;
static { InputStream in = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("db.properties"); try { ps.load(in); dsSource = DruidDataSourceFactory.createDataSource(ps); } catch (Exception e) { e.printStackTrace(); } }
|
JDBC 的缺陷
- JDBC DAO中的代码是重复的。
- SQL 写死了,无法使用一套通用的代码针对不同类的不同条件的查询
- 结果集不通用。
查询获取结果集后,我们需要把结果集中的字段和值一一对应的存入我们的数据类中,此时,可以使用内省,通过字节码对象来生成字段名,获取结果集的值。
1 2 3 4 5 6 7 8 9 10 11 12
| while (rs.next()) { Product p = new Product(); BeanInfo info = Introspector.getBeanInfo(Product.class, Object.class); PropertyDescriptor[] pds = info.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { Object value = rs.getObject(pd.getName()); Method m = pd.getWriteMethod(); m.invoke(p, value); } list.add(p); }
|