在实际开发项目的过程中,总是会需要连接数据库,而连接数据库的主要目的就是通过获取的连接来执行 SQL
语句,但是在有关数据库的开发中,与数据库的连接是十分重要的资源,如果每次都是在需要连接时创建,使用完之后就立即销毁的话,这样就会耗费大量的资源,比较好的做法就是使用的一个容器来管理与数据库的连接,应用初始化的时候便新建一定数量的连接放到容器中,当有程序需要连接的时候,便直接从这个容器中获取,程序使用完之后也不是立即销毁该连接,而是将连接放回到容器当中,以供其它程序获取使用,当然,如果在某一时刻需要获取连接的数量增加,甚至大于容器中初始化时创建的连接数量,这时候管理数据库连接的容器也会一次性增加一定量的数据库连接,不够的时候就会以此方法递增,上面所说的管理数据库连接的容器就是数据库连接池了。
1.JDBC的事务管理
JDBC
中事务的管理是交给 Connection
(连接)对象来完成的,其中和事务管理相关的主要是以下几个方法:
void setAutoCommit(boolean autoCommit)
如果传入的参数是false,则代表关闭自动提交功能,也就是开启事务,手动提交事务
void commit()
这个便是提交事务的方法
void rollback()
这个方法用来执行回滚事务,当一个完整的事务在执行过程中出现异常时,我们就需要使用这个方法进行事务的回滚了。
2.JDBC事务管理案例
下面写一个转账案例来说明一个事务完整的重要性,在数据库中可以看到相关账户的数据是否变化。
首先定义一个账户类(Account):
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Account() {
super();
}
public Account(int id, String name, double money) {
super();
this.id = id;
this.name = name;
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money
+ "]";
}
}
接下来的程序将会演示完成一个转账操作,主要分两步,一步便是将一个账户中的数量减少,另一步便是将另一个账户中的数量增加,如果在这两步操作的过程中发生错误,就很可能导致钱数不一致,但使用事务管理的话,就可以使两步操作要么一起成功,要么一起失败。
public class TransactionException {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstm = null;
try {
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql = "update account set money = money+? where name =?";
pstm = conn.prepareStatement(sql);
pstm.setDouble(1, -1000);
pstm.setString(2, "aaa");
pstm.executeUpdate();
int i = 1 / 0;
pstm.setDouble(1, 1000);
pstm.setString(2, "bbb");
pstm.executeUpdate();
conn.commit();
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtils.release(pstm, conn);
}
}
}
正常的逻辑应该是两者都顺利提交,不过依照上面的逻辑,即使发生错误,也会执行回滚操作,以便让它们两者都不提交,这样就能保证不发生错误,程序最后也会释放掉所有的数据库资源。JDBCUtils
这个类是抽象出来操作数据库的工具类。
public class JDBCUtils {
private static final String driverClassName;
private static final String url;
private static final String user;
private static final String password;
static {
Properties prop = new Properties();
try {
prop.load(new FileInputStream(new File( "src/db.properties")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
driverClassName = prop.getProperty("driverClassName");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
}
/**
* 加载数据库驱动
*/
public static void loadDriver() {
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接
* @return
*/
public static Connection getConnection() {
Connection conn = null;
try {
loadDriver();
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源
* @param statement 语句执行对象
* @param conn 数据库连接对象
*/
public static void release(Statement statement, Connection conn) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
/**
* 释放连接
* @param rs 结果集对象
* @param statement 语句执行对象
* @param conn 数据库连接对象
*/
public static void release(ResultSet rs, Statement statement,
Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
使用上面这个工具类的时候,会首先加载数据库配置文件中的信息。
driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/test4
user = root
password = 123456
3.数据库连接池
这里主要是介绍两种数据库连接池的使用,就是 Druid
和 C3P0
连接池的使用,重点的话就是各个数据库连接池的配置文件应该怎么写,以及应该放到什么地方,其实关于配置文件的书写,它们各自的 API
文档都给予了详细的说明。
3.1 Druid连接池的使用
要想使用 Druid
数据库连接池,第一步必须做的就是导入相应的 jar
包了,只有导入了相应的 jar
包才能使用其中相应的功能,关于 jar
包的获取当然还是去官网下载是最好的,其次的话就是关于数据库的操作都应该导入相应数据库的驱动包,MySQL
数据库就应该导入 MySQL
的驱动包,Oracle
数据库就应该导入 Oracle
的驱动包,因此使用连接池时要注意需要导入的驱动包是两个,一个是连接池的 jar
包,另一个则是数据库的驱动包。
首先看下面的代码,就是使用 Druid
连接池获取驱动并执行相关数据库操作了。
public class DruidDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstm = null;
ResultSet rs = null;
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
try {
conn = dataSource.getConnection();
String sql = "select * from account";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + "\t"
+ rs.getString("name") + "\t" + rs.getDouble("money"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.release(rs, pstm, conn);
}
}
}
从上面的程序中很明显的可以看出,现在是直接将与数据库进行连接的几个属性写入了代码之中,包括驱动类名称、数据库连接地址、用户名以及密码,这样在平时自己编程时是没有问题的,不过在实际开发中一般不使用这种方式,因为如果有需要要修改这些属性的时候,那我们就必须修改源代码了,更好的做法是将这些属性信息放到一个配置文件当中,然后在程序启动时再去加载这些配置信息。
druid.property
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test4
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
其中具体的属性设置当然还是要根据自己的实际要求来设置。
下面是加载配置文件信息再进行相关数据库操作的案例。
public class DruidDemo2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstm = null;
ResultSet rs = null;
Properties properties = new Properties();
try {
properties.load(new FileInputStream(new File("src/druid.properties")));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
conn = dataSource.getConnection();
String sql = "select * from account";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("id")+"\t"+rs.getString("name")+"\t"+rs.getDouble("money"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.release(rs, pstm, conn);
}
}
}
3.2 C3P0连接池的使用
想要使用 c3p0
数据库连接池,首先也是要导入相应的 jar
包,同时和上面一样的就是凡是要使用数据库的操作,也都必须要导入数据库相关 jar
包。下面就是一个使用 c3p0
数据库连接池的案例程序:
public class C3p0Demo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstm = null;
ResultSet rs = null;
ComboPooledDataSource cpds = new ComboPooledDataSource();
try {
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
cpds.setUser("root");
cpds.setPassword("123456");
conn = cpds.getConnection();
String sql = "select * from account";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + "\t"
+ rs.getString("name") + "\t" + rs.getDouble("money"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.release(rs, pstm, conn);
}
}
}
上面的代码直接将与数据库的连接属性都写到了程序之中,平时开发中肯定是不会这样做的,因为这样不利于程序的维护,通常都是将连接池相关的属性放到一个配置文件当中,然后在程序运行时自动去加载,需要注意的是 c3p0
数据库连接池配置文件放置的位置,这里是创建的 Java
工程,所以应该将相关的配置文件放到 src
目录下,具体的配置文件应该如何书写以及应该存放的位置,API
中都会详细地介绍。
c3p0-config.xml
<?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/test</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="automaticTestTable">con_test</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
</c3p0-config>
只要像上面这样正确地配置了相关属性,以及将配置文件放到了恰当地位置,程序在启动时便会自动加载该配置文件。
public class C3p0Demo2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstm = null;
ResultSet rs = null;
ComboPooledDataSource cpds = new ComboPooledDataSource();
try {
conn = cpds.getConnection();
String sql = "select * from account";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + "\t"
+ rs.getString("name") + "\t" + rs.getDouble("money"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.release(rs, pstm, conn);
}
}
}
4.JDBC工具类改进
下面想说一下 JDBC
的工具类,其实上面的 JDBCUtils
就是一个工具类,不过在我们使用连接池之后,比如 c3p0
连接池,连接池是为了减少频繁地创建连接以及销毁连接,但我们之前在使用连接池的时候,每次运行程序都会创建一个连接池,这样就是适得其反了,因为很显然频繁地创建连接池比创建连接是更耗费资源的,因此这里应该做优化,最主要的就是连接池只用创建一个就好了。
public class JDBCUtils2 {
private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
/**
* 返回连接池对象
* @return
*/
public static ComboPooledDataSource getDataSource() {
return dataSource;
}
/**
* 获取数据库连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 释放资源
* @param statement 语句执行对象
* @param conn 数据库连接对象
*/
public static void release(Statement statement, Connection conn) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
/**
* 释放连接
* @param rs 结果集对象
* @param statement 语句执行对象
* @param conn 数据库连接对象
*/
public static void release(ResultSet rs, Statement statement,
Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
5.总结
关于数据库连接池,重点就在于理解它解决了什么问题,以及如何去使用它,其实在各个连接池的 API
中都会给出如何书写配置信息的详细介绍,当然配置文件的放置位置也是应该要注意的。