1. 数据库连接池
JDBC部分的前两个总结主要总结了一下JDBC的基本操作,而且有个共同点,就是应用程序都是直接获取数据库连接的。这会有个弊端:用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设一个网站每天有10万次访问量,那么数据库服务器就需要创建10万次连接,这极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、脱机。
为了解决上述问题,数据库连接池的技术便得到了广泛的使用。为了优化性能,应用程序一启动,就向数据库要一批连接放到池(也就是一个集合而已)中,当用户箱操作数据库的时候,直接找连接池要,当完成对数据库的操作后,再把连接还给连接池供其他用户使用。
有了上面的思路,我们可以自己写一个数据库的连接池,主要使用动态代理技术。
2. 自己编写的数据库连接池
2.1 连接池JdbcPool类的实现
编写连接池需要实现Java.sql.DateSource接口。DateSource接口中定义了两个重载的getConnection方法:Connection getConnection();
方法和 Connection getConnection(String username, String password);
方法,实现连接池功能的步骤如下:
- 在DateSource构造函数或者静态代码块中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中;
- 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户;
- 当用户使用完Connection,调用Connection.close()方法时,Connection对象应保证将自己返回到LinkedList中(编程的难点,动态代理实现),而不要把conn还给数据库。
public class JdbcPool implements DataSource { private static LinkedList<Connection> list = new LinkedList<Connection>();
static{
try{
InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);//加载配置文件 String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password"); Class.forName(driver); //加载驱动
//连接池中创建10个Connection
for(int i = 0; i < 10; i++){
Connection conn = DriverManager.getConnection(url, username, password);
System.out.println("获取到了连接" + conn);
list.add(conn);
}
} catch(Exception e){
e.printStackTrace();
throw new ExceptionInInitializerError(e);
}
} /*
* 用动态代理,返回一个代理对象出去,拦截close方法的调用,对close进行增强
*/
@Override
public synchronized Connection getConnection() throws SQLException {
if(list.size() > 0){
final Connection conn = list.removeFirst();//删掉并返回给conn
// return conn;//这里不能直接return,因为用户使用完了后,调用conn.close()会操作数据库,并没有把这个conn返回给连接池中
System.out.println("池大小是" + list.size()); //下面用动态代理技术来写:
//动态代理技术:使用的是拦截技术
return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
//这里的第二个参数原来为conn.getClass.getInterface(),不过会出错,最终改成了new Class[]{Connection.class}
//原因见帖子:http://blog.csdn.net/njchenyi/article/details/3091092
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable { if(!method.getName().equals("close")){//如果判断不是调用close方法,不管就是了
return method.invoke(conn, args);
}
else{//如果是调用close方法,将conn放到连接池里
list.add(conn);
System.out.println(conn + "被还到池中");
System.out.println("池大小为" + list.size());
return null;
}
}
}); }
else{
throw new RuntimeException("对不起,数据库忙");
}
}
//下面是其他需要实现的方法,默认生成即可,不用写代码
@Override
......
}
2.2 Jdbc工具类JdbcUtils的实现
有了上面的连接池,我们在工具类中获取Collection就不用像原来那样使用DriverManager来获取Connection了,我们可以直接使用自己的连接池了,如下:
public class JdbcUtils { private static JdbcPool pool = new JdbcPool();//定义一个连接池
public static Connection getConnection() throws SQLException {
return pool.getConnection();//直接从连接池中获取一个Connection
} public static void release(Connection conn, Statement st, ResultSet rs) {
if(rs != null){
try{
rs.close();
}catch(Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st != null){
try{
st.close();
}catch(Exception e) {
e.printStackTrace();
}
st = null;
} if(conn != null){
try{
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}
2.3 写一个测试用例
我们写一个模拟转账的程序来测试一下自己写的连接池能不能正常使用:
public class Demo1 {
//模拟转账
@Test
public void changeAccount () {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Savepoint sp = null; try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//相当于开启事务start transaction
String sql1 = "update account set money=money-100 where name=?";
st = conn.prepareStatement(sql1);
st.setString(1, "aaa");
st.executeUpdate(); // sp = conn.setSavepoint();
// int x = 1 / 0; //故意让程序抛异常,用来抓取然后手动回滚,测回滚用的 String sql2 = "update account set money=money+100 where name=?";
st = conn.prepareStatement(sql2);
st.setString(1, "bbb");
st.executeUpdate(); conn.commit();
}catch(Exception e){
try {
conn.rollback(sp);//回滚到指定位置,该位置之前的sql都被有效处理
conn.commit();//回滚了要记得提交
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
3. 开源数据库连接池
现在很多web服务器(Weblogic,WebSphere, Tomcat)都提供了DataSource的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。也有一些开源组织提供了数据源的独立实现:如DBCP数据库连接池和C3P0数据库连接池(spring中用的就是这个)。使用这些连接池时不需要编写连接数据库的代码,直接从数据源获得数据库的连接,程序员编程时也尽量使用这些数据源的实现,以提升程序的数据库访问性能。
下面我们来学习一下这些开源数据库的连接池。
3.1 DPCP连接池
DBCP是Apache软件基金组织下的开源连接池实现,使用DBCP数据源需要在程序中加入下面两个jar包(dbcp的jar包下载地址:http://download.csdn.net/detail/eson_15/9525736):
commons-dbcp.jar
:连接池的实现;commons-pool.jar
:连接池实现的依赖库
Tomcat的连接池正是采用DBCP连接池来实现的,该数据库连接池既可以与应用服务器整合使用,也可以独立使用,下面我们使用DBCP来改写上面的JdbcUtils工具类:
public class JdbcUtils_DBCP { private static DataSource ds = null;//定义数据源
static{
try{
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties prop = new Properties();
prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory();//数据源工厂
ds = factory.createDataSource(prop);//工厂产生数据源
System.out.println(ds);//打印出来瞧瞧是何方神圣~~ }catch(Exception e){
throw new ExceptionInInitializerError(e);
} } public static synchronized Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();//从数据源中拿一个Connection来用~
return conn;
} public static void release(Connection conn, Statement st, ResultSet rs) {
if(rs != null){
try{
rs.close();
}catch(Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st != null){
try{
st.close();
}catch(Exception e) {
e.printStackTrace();
}
st = null;
} if(conn != null){
try{
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}
从上面代码中可以看出,获得了数据源dataSource后,就可以直接通过这个数据源拿到Connection,说明拿之前连接池中已经放好了一些Connection了,这些都已经被DBCP封装好了,我们不用去管,我们需要做的就是在配置文件中做一些配置,DBCP会根据配置文件中的配置去初始化数据库连接池的。我们看一下都需要配置啥:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/databasename
username=root
password=root #初始化连接:10个
initialSize=10 #最大连接数量
maxActive=50 #最大空闲连接
maxIdle=20 #最小空闲连接
minIdle=5 #超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
上面就是DBCP连接池的基本配置,我们只要配置好了,它就会自己根据配置文件中的配置进行初始化。
3.2 C3P0连接池
用C3P0数据库连接池,需要导入下面两个jar包(c3p0的jar包下载地址:http://download.csdn.net/detail/eson_15/9525734):
c3p0-0.9.5.1.jar
mchange-commons-java-0.2.10.jar
这样就可以使用C3P0来改写JdbcUtils工具类了:
public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; static { try {
//配置文件可以用properties文件,也可以用xml,这里使用xml配置,下面给出配置好的xml(要放在类路径下)
ds = new ComboPooledDataSource(); //使用默认配置
//ds = new ComboPooledDataSource("mysql"); //指定配置 } catch (Exception e) {
throw new ExceptionInInitializerError(e);
} } public static synchronized Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();
return conn;
} public static void release(Connection conn, Statement st, ResultSet rs) {....}
}
下面看一下C3P0的配置文件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/databasename</property>
<property name="user">root</property>
<property name="password">root</property> <property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement
caching -->
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him -->
</default-config> <named-config name="mysql"> <!--mysql的配置,在new ComboPooledDataSource()时候括号中指定,所以还可以再对oracle进行配置-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property>
<property name="user">root</property>
<property name="password">root</property> <property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement
caching -->
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him -->
</named-config>
</c3p0-config>
我们可以看出,C3P0的配置文件中可以指定哪个数据库,也可以指定默认的数据库,这就很方便了,我们在获得数据源的时候就可以直接用自己配置的参数指定即可。
4.Tomcat配置数据源
这种方式在开发中也用的比较多。Tomcat服务器在启动时可以帮我们创建一个池,这样我们可以直接利用这个连接池,但是需要进行配置。在META-INF目录下新建一个context.xml文档(也可以在WEB-INF目录下的web.xml中进行配置),然后在里面进行如下配置
<Context>
<Resource name="jdbc/EmployeeDB"
auth="Container"
type="javax.sql.DataSource" username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/day16"
maxTotal="8"
maxIdle="4"/>
</Context>
其他参数可以参照dbcp的配置文件进行设置,因为tomcat连接池内部就是dbcp。然后新建一个servlet,在servlet中编写如下代码(Tomcat连接池的模板代码):
try {
Context initCtx = new InitialContext();// 初始化jndi
Context envCtx = (Context) initCtx.lookup("java:comp/env");// 得到jndi容器
DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");// 从容器中检索连接池
Connection conn = ds.getConnection();
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}
启动服务器,运行该servlet即可在控制台打印出连接信息。
注:如果程序抛出异常,可能是eclipse版本问题,低版本需要将工程中MySQL-connector-java-5.1.26-bin.jar包拷贝到Tomcat服务器的lib文件夹下。
好了,关于JDBC的内容就介绍这么多吧~如有错误之处,欢迎留言指正~
原本以为jdbc比较简单但是 test出的问题也多,后续可能在JavaWeb基础总结里面 再次总结。