学习笔记:javaEE基础9 JDBC

学习来源 Java数据库开发与实战应用

主要是对java操作数据库的一些基本介绍,已经使用JUnit进行单元测试的一些基础知识


JDBC 基础

几大对象

DriverManager 驱动管理类

  • 注册驱动

注册方式如下,使用反射技术

1
Class.forName("com.mysql.jdbc.Driver");

如果使用如下代码注册会注册两次

1
DriverManager.registerDriver(new Driver());

因为查看 com.mysql.jdbc.Driver 的源代码如下

1
2
3
4
5
6
7
8
9
10
11
12
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

它已经自动注册了,所以只要在运行的时候把这个类加载进来就可以了, static块 会在类第一次加载进虚拟机的时候执行


  • 获得连接

用来获得 Connection 对象,获取连接的代码如下

1
2
Connection conn
= DriverManager.getConnection("jdbc:mysql://localhost:3306/imoocjdbctest", "root", "root");

有三个参数,第一个是数据库连接的url,第二个是用户名,第三个是密码

当然,推荐将这些配置写到 .properties 文件中,动态读取

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
public class JdbcUtil {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;

/**
* 初始化数据
*/
static {
Properties properties = new Properties();
InputStream inputStream = JdbcUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}

/**
* 注册驱动
*/
public static void loadClass() throws ClassNotFoundException {
Class.forName(driverClass);
}

/**
* 获取Connection对象
*/
public static Connection getConnection() throws Exception{
loadClass();
return DriverManager.getConnection(url ,username, password);
}
}

jdbc.properties文件放在src目录(classpath)下就行了,文件内容如下

1
2
3
4
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/imoocjdbctest
username=root
password=root

Connection 连接对象

  • 创建执行SQL语句对象
  • 进行事务管理
    • setAutoCommit(boolean autoCommit) 是否自动提交事务
    • commit() 事务提交
    • rollback() 事务回滚

原则上 尽量晚创建,早释放


Statement 执行语句

  • 分类:
    • Statement 普通的执行语句
    • PreparedStatement 预编译语句(推荐):防止sql注入,提高系统执行效率
    • CallableStatement 执行sql中的存储过程,继承自 PreparedStatement

第三个有点奇怪,数据库存储过程是什么东西,说白了就相当于是一个函数,比如说我有一个插入员工信息的mysql过程,要传入两个参数,传出一个参数,那么java代码里就这么写

1
2
3
4
5
CallableStatement stmt = con.prepareCall("{call insertEmployee(?,?,?)}");
stmt.setInt(1, id);
stmt.setString(2, name);
stmt.registerOutParameter(3, java.sql.Types.VARCHAR);
stmt.executeUpdate();

ResultSet 结果集

  • next 判断是否有下一条记录
  • get…

记得释放资源

释放资源的内容写在 finally 块中,最好封装成一个函数

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 static void release(Statement stmt, Connection conn, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}

if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}

if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}

使用连接池

使用第三方连接池,可以有效地管理连接数据库的资源,提高系统信息效率,这里使用 c3p0 连接池,进行连接的方法如下

1
2
3
4
5
6
7
    // 保证单例
private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();

public static Connection getConnection() throws Exception{
Connection conn = dataSource.getConnection();
return conn;
}

注意,需要使用静态单例单例,否则会出现 too many connections 的报错,具体分析见这篇文章:c3p0数据库连接池如何正确的关闭资源(“too many connections”的解决办法)s

和之前的1配置文件一样,将其放到classpath的根目录下,文件名是 c3p0-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/imoocjdbctest</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>

而且获取连接之后也需要关闭的,调用 close() 方法不会关闭与数据库的 TCP 连接,而是将连接还回到池中去。虽然都是Connection对象,但归根到底它的 close() 方法只是一个接口,连接池有自己的实现,用单步调试就可以发现单纯用mysql驱动获取的 Connection.close() 和 用连接池获得的 Connection.close() 内容不一样,虽然我看不懂具体实现的原理…


单元测试

注解

标识注解

  • @Ignore 不测试该方法
  • @Test 测试该方法
  • `@Suite.SuiteClasses` 同时测试多个类中的测试,例子如下
1
2
3
4
5
6
7
8
9
// 几个测试案例一起测试
@RunWith(Suite.class)
@Suite.SuiteClasses({
Testcase2.class,
Testcase1.class,
Testcase3.class,
})
public class TestAll {
}

设置顺序

  • @Before 表示在所有方法运行前运行的方法;
  • @After 表示在所有的方法运行之后执行的方法;
  • @FixMethodOrder 指定类中测试方法运行的排序方式,有以下几个选项
    • @FixMethodOrder(MethodSorters.JVM) 根据文件中方法出现的先后顺序进行测试
    • @FixMethodOrder(MethodSorters.NAME_ASCENDING) 根据方法名的拼写排序的顺序进行测试
    • @FixMethodOrder(MethodSorters.DEFAULT) 默认,可能会乱序

测试工具

  • @Test(expected = Exception.class) 预计会抛出异常
  • @Test(timeout = 1000) 预计运行时间在1000ms内,超出就算测试失败
  • assert... 断言,预期结果和实际结果进行比较
  • fail 主动失败