设计一个数据库连接池的几个步骤和要点
一:步骤
// 静态代码块中直接加载数据库驱动 static { try { String driver = ConfigurationManager.getProperty(Constants.JDBC_DRIVER); Class.forName(driver); }catch (Exception e){ e.getStackTrace(); } }
2.实现工具类的单例化,保证这个类在系统中只能有一个实例,数据库连接池也只有一份
/** * 实现JDBCHelper的单例化 */ private static JDBCHelper instance = null; //获取单例 public static JDBCHelper getInstance(){ if(instance == null){ synchronized (JDBCHelper.class){ if(instance == null){ instance = new JDBCHelper(); } } } return instance; }
3.创建数据库连接池
//创建数据库连接池 private LinkedListdatasource = new LinkedList ();
4.在构造函数当中创建连接池
* JDBCHelper在整个程序运行申明周期中,只会创建一个实例 * 在这一次创建实例的时候,会调用自己的构造方法 * 在构造方法中去创建自己唯一的一个数据库连接池 */ private JDBCHelper(){ /** * 第一步:获取数据库连接池的大小,就是数据库连接池中要放多少个数据库连接 * 这个可以通过在配置文件中配置的方式,来灵活设定 */ int dataSourceSize = ConfigurationManager.getInteger( Constants.JDBC_DATASOURCE_SIZE); //然后创建制动数量的数据库连接,并放入数据库连接池中 for(int i = 0; i < dataSourceSize; i++){ String url = null; String userName = null; String passWord = null; url = ConfigurationManager.getProperty(Constants.JDBC_URL); userName = ConfigurationManager.getProperty(Constants.JDBC_USERNAME); passWord = ConfigurationManager.getProperty(Constants.JDBC_PASSWORD); try { Connection connection = DriverManager.getConnection(url,userName,passWord); datasource.push(connection); } catch (SQLException e) { e.printStackTrace(); } } }
5.创建一个获取连接的方法
/** * 提供获取数据库连接的方法 * 有可能现在连接池中所的连接都用完了,暂时获取不到连接 * 所以在我们的代码中要有一个简单的等待机制,去等待获取到数据库连接 */ public synchronized Connection getConnection(){ while (datasource.size() == 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return datasource.poll(); }
6.创建执行增删改的sql语句的方法
public int executeUpdate(String sql, Object[] param){ int rtn = 0; Connection conn = null; PreparedStatement pStat = null; try { conn = getConnection(); conn.setAutoCommit(false); pStat = conn.prepareStatement(sql); if (param != null && param.length > 0) { for (int i = 0; i < param.length; i++) { pStat.setObject(i + 1, param[i]); } } rtn = pStat.executeUpdate(); conn.commit(); } catch(SQLException e){ e.printStackTrace(); }finally { datasource.push(conn); } return rtn; }
7. 创建执行查询sql语句的方法
public void executeQuery(String sql, Object[] param, QueryCallback callback){ Connection conn = null; PreparedStatement pStat = null; ResultSet rs = null; try{ conn = getConnection(); pStat = conn.prepareStatement(sql); for(int i=0; ilength;i++){ pStat.setObject(i+1,param[i]); } rs = pStat.executeQuery(); callback.process(rs); }catch (Exception e){ e.getStackTrace(); }finally { if(conn != null) { datasource.push(conn); } } }
8. 创建执行批量执行sql语句的方法
public int[] executeBatch(String sql, List
9. 创建一个回调接口,处理执行完返回的数据
/** * 静态内部类 * 查询回调接口 */ public interface QueryCallback { /** * 处理返回结果 * @param rs * @throws Exception */ void process(ResultSet rs) throws Exception; }
二、要点
关于PreparedStatement接口,需要重点记住的是:
1. PreparedStatement可以写参数化查询,比Statement能获得更好的性能。
2. 对于PreparedStatement来说,数据库可以使用已经编译过及定义好的执行计划,这种预处理语句查询比普通的查询运行速度更快。
3. PreparedStatement可以阻止常见的SQL注入式攻击。
4. PreparedStatement可以写动态查询语句
5. PreparedStatement与java.sql.Connection对象是关联的,一旦你关闭了connection,PreparedStatement也没法使用了。
6. “?” 叫做占位符。
7. PreparedStatement查询默认返回FORWARD_ONLY的ResultSet,你只能往一个方向移动结果集的游标。当然你还可以设定为其他类型的值如:”CONCUR_READ_ONLY”。
8. 不支持预编译SQL查询的JDBC驱动,在调用connection.prepareStatement(sql)的时候,它不会把SQL查询语句发送给数据库做预处理,而是等到执行查询动作的时候(调用executeQuery()方法时)才把查询语句发送个数据库,这种情况和使用Statement是一样的。
9. 占位符的索引位置从1开始而不是0,如果填入0会导致java.sql.SQLException invalid column index异常。所以如果PreparedStatement有两个占位符,那么第一个参数的索引时1,第二个参数的索引是2.
查询操作如何调用回调接口:
//测试查询语句 final MaptestUser = new HashMap (); String sql = "select name,age from user_test where age = ?"; jdbc.executeQuery(sql,new Object[]{21},new JDBCHelper.QueryCallback(){ @Override public void process(ResultSet rs) throws Exception { if (rs.next()){ String name = rs.getString(1); int age = rs.getInt(2); /** * 匿名内部类的重要知识点 * 就是在内部类中使用外部的成员如变量等, * 那么必须将此变量设置为final类型才能访问 * 不然是访问不了的 */ testUser.put("name",name); testUser.put("age",age); } } }); System.out.println(testUser.get("name") + ":" + testUser.get("age")); }