创建JDBC模板简化代码、JDBC应用的事务管理以及连接池的作用

一、创建JDBC模板简化代码

一个简单的查询。要做这么一大堆事情,并且还要处理异常,我们不防来梳理一下: 

1、获取connection 
2、获取statement 
3、获取resultset 
4、遍历resultset并封装成集合 
5、依次关闭connection,statement,resultset。并且还要考虑各种异常 
6、..... 
在面向对象编程的年代里,这种代码简直不能上人容忍。

一堆反复的代码: 

这时候,使用模板模式的时机到了!

。!

 

通过观察我们发现上面步骤中大多数都是反复的。可复用的。仅仅有在遍历ResultSet并封装成集合的这一步骤是可定制的,由于每张表都映射不同的java bean。这部分代码是没有办法复用的,仅仅能定制。能够用一个抽象的父类把它们封装一下:

public abstract class JdbcTemplate {

	//template method
	public final Object execute(String sql) throws SQLException{
		
		Connection con = HsqldbUtil.getConnection();
		Statement stmt = null;
		try {
 
			stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);
			Object result = doInStatement(rs);//abstract method 
			return result;
		}
		catch (SQLException ex) {
			 ex.printStackTrace();
			 throw ex;
		}
		finally {
 
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(!con.isClosed()){
					try {
						con.close();
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
		}
	}
	
	//implements in subclass
	protected abstract Object doInStatement(ResultSet rs);
}

在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中。由子类负责实现。

 
好,我们来定义一个子类,并继承上面的父类: 

public class JdbcTemplateUserImpl extends JdbcTemplate {

	@Override
	protected Object doInStatement(ResultSet rs) {
		List<User> userList = new ArrayList<User>();
		
		try {
			User user = null;
			while (rs.next()) {

				user = new User();
				user.setId(rs.getInt("id"));
				user.setUserName(rs.getString("user_name"));
				user.setBirth(rs.getDate("birth"));
				user.setCreateDate(rs.getDate("create_date"));
				userList.add(user);
			}
			return userList;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}

}

由代码可见,我们在doInStatement()方法中。对ResultSet进行了遍历,最后并返回。 
有人可能要问:我怎样获取ResultSet 并传给doInStatement()方法啊??问这个问题的大多是新手。由于此方法不是由子类调用的,而是由父类调用。并把ResultSet传递给子类的。我们来看一下測试代码: 

		String sql = "select * from User";
		JdbcTemplate jt = new JdbcTemplateUserImpl();
		List<User> userList = (List<User>) jt.execute(sql);

假设我每次用jdbcTemplate时,都要继承一下上面的父类。是不是有些不方面呢? 
那就让我们甩掉abstract这顶帽子吧,这时,就该callback(回调)上场了 。

所谓回调,就是方法參数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。 

以上摘自网友。


二、JDBC应用的事务管理

在介绍查询模板(回调)之前。我们先来看看JDBC应用的事务管理。

JDBC 应用的事务管理——Service层和Dao层事务的传递。 
方式一:跨层传递方法參数——在Service层创建开启事务的连接。并传递到Dao层,最后在Service层提交事务。


方式二:ThreadLocal 绑定连接——使用ThreadLocal进行事务管理——ThreadLocal能够实如今线程范围内实现数据共享。 
方式三:使用Spring进行事务管理。

1、JDBC应用的事务管理——採用跨层传递方法參数 
思想:在Service层创建开启事务的连接,并传递到Dao层。最后在Service层提交事务;
Demo例子1:Service层(Dao层中仅仅要在方法中參数中接收该 连接參数 就好了)

public class BusinessService {  
  /*  
  create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
  )character set utf8 collate utf8_general_ci;
  
  insert into account(name,money) values('aaa',1000);
  insert into account(name,money) values('bbb',1000);
  insert into account(name,money) values('ccc',1000); 
  */  

  public void transfer1(int sourceid,int targetid,double money) throws SQLException{
    Connection conn = null;
    try{
      // 获取连接并开启事务。
      conn = JdbcUtils.getConnection();
      conn.setAutoCommit(false);
      // 将开启事务的连接传递到各层。

AccountDao dao = new AccountDao(conn); Account a = dao.find(sourceid); //select Account b = dao.find(targetid); //select a.setMoney(a.getMoney()-money); b.setMoney(b.getMoney()+money); dao.update(a); //update dao.update(b);//update // 提交事务。

conn.commit(); }finally{ // 关闭连接。 if(conn!=null) conn.close(); } } }


2、JDBC应用的事务管理—— ThreadLocal 绑定连接 
思想:在Service层将开启事务的连接绑定到ThreadLocal中,在当前线程所途径的其它各层从ThreadLocal中获取连接并进行操作,最后线程返回至Service层时。再提交事务,移除绑定的链接.

package dhp.com.util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

public class JdbcUtils {
	private static DataSource ds;
	// 为保证各层的类所使用的ThreadLocal是同一个。建议将其设定成静态的。
	// 可是一定要记得使用后要移出绑定在上面的对象。
	// 事实上就是一个Map集合
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

	static {
		try {
			Properties prop = new Properties();
			InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
			prop.load(in);
			BasicDataSourceFactory factory = new BasicDataSourceFactory();
			ds = factory.createDataSource(prop);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	// 返回数据源
	public static DataSource getDataSource() {
		return ds;
	}
	// 备注:该获取连接的方法,仅当使用ThreadLocal来管理事务连接的情况,由于向静态对象ThreadLocal中绑定了对象,所以当我们不须要管理事务的普通获取连接的方法。就不要用此方法。应该用普通的获取连接的方法。

public static Connection getConnection() throws SQLException { try { // 得到当前线程上绑定的连接 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } public static void startTransaction() { try { // 得到当前线程上绑定连接开启事务 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } // 提交当前线程上的连接 public static void commitTransaction() { try { Connection conn = tl.get(); if (conn != null) { conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } // 回滚当前线程上的连接 public static void rollback() { try { Connection conn = tl.get(); if (conn != null) { conn.rollback(); } } catch (Exception e) { throw new RuntimeException(e); } } // 释放当前线程上的连接 public static void closeConnection() { try { Connection conn = tl.get(); if (conn != null) { conn.close(); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 千万注意。解除当前线程上绑定的链接(从threadlocal容器中移除相应当前线程的链接) tl.remove(); } } // 释放资源 public static void release(Connection conn, Statement st, PreparedStatement pst, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { rs = null; } } if (st != null) { try { st.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { st = null; } } if (pst != null) { try { pst.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { pst = null; } } if (conn != null) { try { conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { conn = null; } } } }


Demo例子2: 採用 ThreadLocal 绑定连接 来管理事务的 Service层的代码。

package dhp.com.service;

import java.sql.SQLException;


import dhp.com.dao.AccountDao;
import dhp.com.dao.impl.AccountDaoImpl;
import dhp.com.model.Account;
import dhp.com.util.JdbcUtils;

public class BusinessService {
	// 用上ThreadLocal的事务管理
	public void transfer(int sourceid, int targetid, float money) {
		try {
			JdbcUtils.startTransaction();
			AccountDao dao = new AccountDaoImpl();
			Account a = (Account) dao.find(sourceid); // select
			Account b = (Account) dao.find(targetid); // select
			a.setMoney(a.getMoney() - money);
			b.setMoney(b.getMoney() + money);
			dao.update(a); // update
			dao.update(b); // update
			JdbcUtils.commitTransaction();
		} catch(SQLException e) {
			JdbcUtils.rollback();
			throw new RuntimeException(e);
        } finally {
			JdbcUtils.closeConnection();
		}
	}
}

JDBC的数据库操作中,一项事务是由一条或是多条表达式所组成的一个不可切割的工作单元。

我们通过提交commit()或是回退rollback()来结束事务的操作。关于事务操作的方法都位于接口java.sql.Connection中。
首先我们要注意。在JDBC中,事务操作默认是自己主动提交。也就是说,一条对数据库的更新表达式代表一项事务操作。操作成功后,系统将自己主动调用commit()来提交,否则将调用rollback()来回退。
其次。在JDBC中,能够通过调用setAutoCommit(false)来禁止自己主动提交。之后就能够把多个数据库操作的表达式作为一个事务。在操作完毕后调用commit()来进行总体提交。

倘若当中一个表达式操作失败。都不会运行到commit(),而且将产生响应的异常。

此时就能够在异常捕获时调rollback()进行回退。这样做能够保持多次更新操作后,相关数据的一致性。

ThreadLocal作用:

因为请求中的一个事务涉及多个 DAO 操作,而这些 DAO 中的 Connection  不能从连接池中获得。假设是从连接池获得的话,两个 DAO 就用到了两个Connection,这种话是没有办法完毕一个事务的。

DAO 中的 Connection 假设是从 ThreadLocal 中获得 Connection 的话那么这些 DAO 就会被纳入到同一个 Connection 之下。当然了,这种话,DAO 中就不能把 Connection 给关了,关掉的话。下一个使用者就不能用了。

ThreadLocal保证了每一个线程都有自己的连接。

三、连接池的作用(就是为了提高性能)
       连接池的作用:连接池是将已经创建好的连接保存在池中,当有请求来时。直接使用已经创建好的连接对数据库进行訪问。

这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。
基本原理是这种:
(1)建立数据库连接池对象(server启动)。


(2)依照事先指定的參数创建初始数量的数据库连接(即:空暇连接数)。


(3)对于一个数据库訪问请求,直接从连接池中得到一个连接。假设数据库连接池对象中没有空暇的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。


(5)关闭数据库。释放全部数据库连接(此时的关闭数据库连接,并不是真正关闭。而是将其放入空暇队列中。

如实际空暇连接数大于初始空暇连接数则释放连接)。
(6)释放数据库连接池对象(server停止、维护期间。释放数据库连接池对象。并释放全部连接)。


1 .连接池的概念和为什么要使用连接池?
    连接池放了N个Connection对象。本质上放在内存其中,在内存中划出一块缓存对象,应用程序每次从池里获得Connection对象,而不是直接从数据里获得。这样不占用server的内存资源。


2 .假设不使用连接池会出现的情况:
a.占用server的内存资源
b.导致server的速度很慢
3 .应用连接池的三种方式:
a.自己定义连接池
b.使用第三方连接池
c.使用server自带的连接池
       连接池一般比直接连接更有优越性,由于它提高了性能的同一时候还保存了宝贵的资源。

在整个应用程序的使用过程,其中反复的打开直接连接将导致性能的下降。而池连接仅仅在server启动时打开一次,从而消除了这样的性能问题。
        连接池主要考虑的是性能,每次获取连接和释放连接都有非常大的工作量。会对性能有非常大影响。而对资源来说起的是反作用,由于保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得。这样不占用server的内存资源。所以一般要建立连接池,而连接的数量要适当。不能太大。太大会过多消耗资源。(所以,考虑2个方面,一个是内存,还有一个是资源)。
       连接池就是为了避免反复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。


详情请看建立连接池的三种方式:http://doc.okbase.net/u011225629/archive/174436.html


好了,一下是我的项目视图和完整代码:



model类:

package dhp.com.model;

public class Account {
	private int id;
	private String name;
	private float 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 float getMoney() {
		return money;
	}

	public void setMoney(float money) {
		this.money = money;
	}

}

数据库配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3308/test
username=root
password=123456

initialSize=10
maxActive=50
maxIdle=20
minIdle=5
maxWait=60000
connectionProperties=useUnicode=true;characterEncoding=utf8
defaultAutoCommit=true

工具类util:

package dhp.com.util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

public class JdbcUtils {
	private static DataSource ds;
	// 为保证各层的类所使用的ThreadLocal是同一个,建议将其设定成静态的。
	// 可是一定要记得使用后要移出绑定在上面的对象。
	// 事实上就是一个Map集合
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

	static {
		try {
			Properties prop = new Properties();
			InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
			prop.load(in);
			BasicDataSourceFactory factory = new BasicDataSourceFactory();
			ds = factory.createDataSource(prop);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	// 返回数据源
	public static DataSource getDataSource() {
		return ds;
	}
	// 备注:该获取连接的方法,仅当使用ThreadLocal来管理事务连接的情况。由于向静态对象ThreadLocal中绑定了对象,所以当我们不须要管理事务的普通获取连接的方法,就不要用此方法。

应该用普通的获取连接的方法。

public static Connection getConnection() throws SQLException { try { // 得到当前线程上绑定的连接 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } public static void startTransaction() { try { // 得到当前线程上绑定连接开启事务 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } // 提交当前线程上的连接 public static void commitTransaction() { try { Connection conn = tl.get(); if (conn != null) { conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } // 回滚当前线程上的连接 public static void rollback() { try { Connection conn = tl.get(); if (conn != null) { conn.rollback(); } } catch (Exception e) { throw new RuntimeException(e); } } // 释放当前线程上的连接 public static void closeConnection() { try { Connection conn = tl.get(); if (conn != null) { conn.close(); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 千万注意,解除当前线程上绑定的链接(从threadlocal容器中移除相应当前线程的链接) tl.remove(); } } // 释放资源 public static void release(Connection conn, Statement st, PreparedStatement pst, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { rs = null; } } if (st != null) { try { st.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { st = null; } } if (pst != null) { try { pst.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { pst = null; } } if (conn != null) { try { conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } finally { conn = null; } } } }


回调接口:

package dhp.com.util;

import java.sql.ResultSet;
import java.sql.SQLException;

public interface RowMapper {
	Object mapRow(ResultSet rs) throws SQLException;
}


增删改查的工具模板:
package dhp.com.util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBCTemplate {
	public static void update(String sql, Object params[]) throws SQLException {
		Connection conn = null;
		PreparedStatement pst = null;
		conn = JdbcUtils.getConnection();
		pst = conn.prepareStatement(sql);
		for (int i = 0; i < params.length; i++) {
			pst.setObject(i + 1, params[i]);
		}
		pst.executeUpdate();
	}
	
	public static Object queryForObject(String sql, Object params[], RowMapper rowMapper) throws SQLException {
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rs = null;
		Object obj = null;  
		conn = JdbcUtils.getConnection();
		pst = conn.prepareStatement(sql);
		for (int i = 0; i < params.length; i++) {
			pst.setObject(i + 1, params[i]);
		}
		rs = pst.executeQuery();
		if(rs.next()){
			obj=rowMapper.mapRow(rs);
		}
		return obj;
		
	}
}


dao的接口层:
package dhp.com.dao;

import java.sql.SQLException;

import dhp.com.model.Account;

public interface AccountDao {
	void add(Account a) throws SQLException;
	void update(Account a) throws SQLException;
	void delete(int id) throws SQLException;
	Object find(int id) throws SQLException;
}

接口实现层:

package dhp.com.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;

import dhp.com.dao.AccountDao;
import dhp.com.model.Account;
import dhp.com.util.JDBCTemplate;
import dhp.com.util.RowMapper;

public class AccountDaoImpl implements AccountDao {

	@Override
	public void add(Account a) throws SQLException {
		String sql = "insert into account(id,name,money) values(?,?,?)";
		Object params[] = { a.getId(), a.getName(), a.getMoney() };
		JDBCTemplate.update(sql, params);
	}

	@Override
	public void update(Account a) throws SQLException {
		String sql = "update account set money=?

where id=?

"; Object params[] = { a.getMoney(), a.getId() }; JDBCTemplate.update(sql, params); } @Override public void delete(int id) throws SQLException{ String sql = "delete from account where id=?"; Object params[] = {id}; JDBCTemplate.update(sql, params); } @Override public Object find(int id) throws SQLException { String sql = "select * from account where id=?"; Object params[] = {id}; Object obj = JDBCTemplate.queryForObject(sql, params, new RowMapper() { @Override public Object mapRow(ResultSet rs) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getFloat("money")); return account; } }); return (Account)obj; } }


service层:

package dhp.com.service;

import java.sql.SQLException;


import dhp.com.dao.AccountDao;
import dhp.com.dao.impl.AccountDaoImpl;
import dhp.com.model.Account;
import dhp.com.util.JdbcUtils;

public class BusinessService {
	// 用上ThreadLocal的事务管理
	public void transfer(int sourceid, int targetid, float money) {
		try {
			JdbcUtils.startTransaction();
			AccountDao dao = new AccountDaoImpl();
			Account a = (Account) dao.find(sourceid); // select
			Account b = (Account) dao.find(targetid); // select
			a.setMoney(a.getMoney() - money);
			b.setMoney(b.getMoney() + money);
			dao.update(a); // update
			dao.update(b); // update
			JdbcUtils.commitTransaction();
		} catch(SQLException e) {
			JdbcUtils.rollback();// 假设中间事务错误发生,进行回滚。保证要么运行成功。要么运行失败
			throw new RuntimeException(e);
        } finally {
			JdbcUtils.closeConnection();
		}
	}
}

測试类:

package dhp.com.test;


import dhp.com.service.BusinessService;

public class Test {
	public static void main(String[] args) {
		BusinessService service = new BusinessService();
		service.transfer(1, 2, 100);
	}
}

最后提一点:

关于关闭Connection是否会自己主动关闭Statement,ResultSet问题 ?

做程序离不开连接数据库,所以一些打开,关闭数据库是常常要执行的操作,打开数据库后,在程序用完后要及时关闭数据库连接资源,以释放内存,避免资源耗尽.但如今有一个问题,即当我们关闭了Connection对象后,Statement,ResultSet对象是否会自己主动关闭问题,对于这个问题,之前我在网上也找了相关资料,说会自己主动关闭,所以一段时间以来,我都是仅仅关闭Connection对象,而没有关闭Statement,ResultSet对象,但程序也能正常执行,程序也没有由于资源耗尽而崩溃,对于这一点,事实上是有原因的:

1)首先,关闭了Connection对象后,是不会自己主动关闭Statement,ResultSet对象的。


try {
Connection con = null;
Statement st = null;
ResultSet rs = null;
con = getConnection();
st = con.createStatement();
rs = st.executeQuery(sql);
}
catch(Exception e) {
 System.out.println("ocurr error");
}
finally {
          con.close();con=null;
 try {
    con.close();
 }
 catch(SQLException se) {
    System.out.println("ocurr close error");
 } 
}

System.out.println("statement object:"+st);
System.out.println("resultset object:"+rs);

上面的代码先获取了连接,然后仅仅关闭了Connection对象,而没有关闭Statement,ResultSet对象,最后两行代码输出Statement,ResultSet对象,是有结果的,表明关闭了Connection对象,而没有关闭Statement,ResultSet对象。
2)Statement对象将由Java垃圾收集程序自己主动关闭,而作为一种好的编程风格,应在不须要Statement对象时显式地关闭它们,这将马上释放DBMS资源,有助于避免潜在的内存问题。
3)ResultSet维护指向其当前数据行的光标.每调用一次next方法,光标向下移动一行.最初它位于第一行之前,因此第一次调用next将把光标置于第一行上,使它成为当前行.随着每次调用next导致光标向下移动一行.依照从上至下的次序获取ResultSet行,在ResultSet对象或其父辈Statement对象关闭之前,光标一直保持有效。
     所以在打开数据库资源后,尽量手工关闭Connection对象和Statement,ResultSet对象,要养成一种良好的编程风格.
注:他们三者之间关闭没有不论什么关联,即先关闭谁没有不论什么先后顺序,能够先关闭他们中的不论什么一个,且关闭当中的不论什么一个对象都不会关闭其它其它对象,但一般养成按关闭ResultSet,Statement,Connection的顺序关闭资源。

posted on 2017-08-11 08:25  yjbjingcha  阅读(221)  评论(0编辑  收藏  举报

导航