JDBC

JDBC 概述(Java DataBase Connectivity)


持久化:把数据保存到可掉电式存储设备中以供以后使用。

持久化过程大多通过各种数据库完成。将内存中的数据存储在数据库中,也可以存储在磁盘文件、XML数据文件汇总。

JPA: JavaEE 的规范,Java persistence api:Java 持久化 API

为什么用 JDBC 规范?
加载数据库的驱动,调用驱动包获取数据库了解对象,驱动包由数据库自己去实现,sun 公司规定 JDBC 的接口和代码规范,从而去做到统一的 Java 代码去使用不同的数据库。只需要安装不同的数据库驱动。不管什么数据库,都用 JDBC 基准(工具和接口),去访问该数据库,但怎么访问是由数据库驱动包去实现。
程序员不需要对特定的数据库差异去做处理。Java 连接数据库的标准。

注意:在开发中,使用到的关于 JDBC 类的接口/对象 都是导入的 java.sql 或者 javax.sql 包里的类。

JDBC 步骤:


  1. 加载注册驱动
  2. 获取数据库连接对象
  3. 获取预编译语句对象
  4. 执行SQL命令
  5. 释放资源

加载注册驱动


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);

// 获取数据库连接对象,url:jdbc:mysql//ip:port/数据库名
// jdbc:mysql://localhost:3306/test,如果数据库服务器在本机,并且端口号是3306,
// 可以省略主机和端口,jdbc:mysql:///test
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();

使用预编译的好处:

预编译语句对象优点:

  1. 缓存预编译SQL,更快的编译SQL(有些数据库不支持)
  2. 安全

执行SQL命令


Statement对象

  • int executeUpdate(String sql) 执行DML/DDL操作
  • ResultSet executeQuery(String sql) 执行DQL操作
1
2
// 执行SQL命令
st.executeUpdate(sql);

关闭源


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 也是资源,
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. 创建连接数据库的配置文件

  1. 在工具类读入配置文件中的内容,创建连接对象
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");
// 将流载入 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;
}
}
  1. 根据数据库的表创建javabean对象
  2. 在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. 根据接口写出实现类并写测试类逐一进行测试(写一个方法测试一个)

优化

连接数据库因为每次都要写,所以应该在工具类中事先写好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1.连接数据库四要素,需要使用配置文件保存
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";

// 2.在静态代码块中加载注册驱动
static {
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

// 3.提供一个公共方法,返回数据库连接对象
public static Connection getConnection() {

}

事务 (Transaction)


每个逻辑操作单元全部完成,数据的一致性可以保证,当单元中某一部分操作失败,整个事务应全部视为错误,所有从起点后的操作,全部回退到开始状态。

开启事务 -> commit 永久保存,或者 -> rollback 回退。

事务的 ACID 属性:

  1. 原子性(Atomicity)
  2. 一致性(Consistency)
  3. 隔离性(Isolation) 事务内部操作及使用的数据对并发的其他事务是隔离的。
  4. 持久性(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 才会释放

获取自动生成的主键


  1. 设置一个标记,告诉数据库,需要自动生成的主键,在获取预编译语句对象的时候,传递标记 Statement.RETURN_GENERATED_KEYS.

连接池


目前操作数据库的方式:

获取数据库连接对象,执行SQL,解析结果集,关闭资源(结果集对象,预编译语句对象,数据库连接对象)。

为什么必须使用数据库连接池?

JDBC 数据库连接 (Connection 对象) 使用 DriverManager 来获取,每次向数据库建立连接都要讲 Connection 加载到内存,再验证用户名密码。非常耗时,创建成本大。

对于每一次数据库连接,使用完后都要断开,否则程序出现异常未关闭,会导致数据库系统内存泄露。最终将导致重启数据库。

当前的操作数据库的方式,花费了大量资源和时间创建数据库连接对象,使用一次就关闭,没有充分使用数据库连接对象。

连接池是一个容器,装数据库连接对象。需要对连接对象进行管理,达到最高的效率。

Java 中使用 javax.sql.DataSource 接口表示连接池。

DataSource 和 Connection Pool 是同一个

DataSource 只是个接口,由各大服务器厂商实现(Tomcat,JBoss)

常用的DataSource实现:

  1. DBCP: Spring 框架推荐
  2. druid: 阿里巴巴的连接池 (号称Java 语言中性能最好的连接池)

获取 Connection 对象


未使用连接池:Connection conn = DriverManager.getConnection(url, username, password);

使用了连接池:dataSource对象.getConnection(); 返回连接对象已经是一个功能增强的连接对象了。

只要获取了 Connection 连接对象接下来操作是一模一样的。

释放 Connection对象(Connection对象.close()):

未使用连接池,是和数据库服务器断开,使用了连接池,还给连接池。

自己设计一个连接池:

DBCP 连接池


操作步骤:

  1. 导入相关 jar 包

  1. 获取连接池对象
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);
// 加载驱动
// 创建连接池对象
// 使用相对路径加载配置文件(从classpath(bin)找,也就是src源文件夹下)
ds = BasicDataSourceFactory.createDataSource(ps);
} catch (Exception e) {
e.printStackTrace();
}
}


// 获取 Connection 链接 对象
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 的缺陷

  1. JDBC DAO中的代码是重复的。
  2. SQL 写死了,无法使用一套通用的代码针对不同类的不同条件的查询
    • 解决方案:将 SQL 也写到配置文件中
  3. 结果集不通用。

查询获取结果集后,我们需要把结果集中的字段和值一一对应的存入我们的数据类中,此时,可以使用内省,通过字节码对象来生成字段名,获取结果集的值。

1
2
3
4
5
6
7
8
9
10
11
12
while (rs.next()) {
Product p = new Product();
// 获取 p 的 javabean 对象
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);
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/07/04/JDBC/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论