Java数据库编程
1. 数据库编程基本流程
(1)加载数据库厂商的驱动程序
例如:对于Mysql数据库,Class.forName("com.mysql.jdbc.Driver");
(2)获得数据库连接对象
Connection connection = DriverManager.getConnection(url,username,password);
(3)创建数据库执行语句对象
Statement statement = connection.createStatement();
(4)执行SQL语句,获得结果集对象
ResultSet result = statement.executeQuery("SELECT * FROM tb_test");
(5)关闭数据库连接
使用完ResultSet、Statement以及Connection对象后,需要及时调用它们的close方法来释放资源。关闭Statement对象时,会自动释放由其获得的所有ResultSet对象,关闭Connection对象也会自动释放由其获得的Statement对象。因此,调用Connection的close方法就可以释放与其相关的所有资源。
由于数据库连接使用完后必须关闭,因此通常将调用close方法放在finally子句中。
2. JDBC获取自增主键
很多时候,我们插入一个记录的主键是自增的列,我们可能紧接着需要获得这个主键,JDBC提供了统一的接口:
statement.executeUpdate("INSERT....",Statement.RETURN_GENERATED_KEYS);
ResultSet result = statement.getGeneratedKeys();
其中,参数Statement.RETURN_GENERATED_KEYS指定返回刚插入的键,然后调用getGeneratedKeys方法返回这些键。
3. 执行事务
事务表示具有原子性的SQL语句的组合,这些SQL语句要么全部执行成功,要么就全部不执行。
默认情况下,我们创建的Connection对象处于自动提交模式,即执行了一条SQL更新语句,就立即提交更新数据库。执行事务时,我们需要设置Connection对象不自动提交:connection.setAutoCommit(false);
然后我们就可以执行多条SQL更新语句,这些对数据库的更新只有在调用Connection对象的commit方法后才会生效,如果执行这些SQL语句中出现了错误,就调用rollback方法回滚数据库。
4. 数据库连接池
当在多线程环境下进行数据库编程时,就需要注意同步的问题。
使用一个全局的Connection对象若不进行同步就会导致并发的各种问题,而对这个全局Connection进行同步,通常效率又是很低的。我们可以使用局部的Connection对象,每次执行数据库任务时都创建一个新的连接,使用完后立即关闭,这样会导致频繁的创建关闭数据库连接,效率也会比较低。
更好的方式使用数据库连接池来管理数据库连接,数据库连接池可以有效地利用闲置的数据库连接。想要连接数据库时,通过数据库连接池请求一个Connection对象,这个Connection可能是新创建的,也可能是之前创建尚未关闭的。关闭Connection对象时并不是真的与数据库断开连接,而是归入到数据库连接池的闲置连接中。
实现一个数据库连接池最重要的问题就是解决调用Connection的close方法时,如何不真正关闭数据库连接。我们可以使用Java动态代理来解决这个问题。
由于Connection是一个接口,而DriverManager.getConnection方法返回的Connection的具体类型我们又不知道,我们就无法直接通过覆盖来实现新的close方法。我们当然也可以自己实现Conneciton接口,但这样我们就需要实现许多接口方法,而实际上我们只需要改变close方法的实现。
Java反射库中的Proxy类为我们提供了这样的能力:可以构造一个实现一些接口的类,并指定一个调用处理器(实现InvocationHandler接口)来拦截对所有接口方法的调用。
下面给出一个简易实现的数据库连接池:
public class DBConnectionPool { private String jdbcUrl; private String username; private String password; private LinkedList<Connection> pool; //保存闲置的Connection对象 public DBConnectionPool(String driver,String jdbcUrl) throws ClassNotFoundException { this.jdbcUrl = jdbcUrl; Class.forName(driver); pool = new LinkedList<Connection>(); } public DBConnectionPool(String driver,String jdbcUrl,String username,String password) throws ClassNotFoundException { this.jdbcUrl = jdbcUrl; this.username = username; this.password = password; Class.forName(driver); pool = new LinkedList<Connection>(); }
//为了可以在多线程环境下使用,对一些方法使用synchronized关键字同步 public synchronized Connection getConnection() throws SQLException { Connection conn = null; if(pool.size() > 0) conn = pool.poll(); else { final Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
//创建代理对象,并强制转换为Connection接口类型,InvocationHandler接口表示调用处理器 conn = (Connection)Proxy.newProxyInstance(null,new Class[]{Connection.class},new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName() == "close" && method.getParameterTypes().length == 0) { pool.add((Connection)proxy); //执行close方法时,替换原方法的逻辑 return null; } else //执行其他方法时,保持原方法的逻辑 return method.invoke(connection,args); } }); } return conn; } public synchronized Connection getConnection(String username, String password) throws SQLException { Connection conn = null; if(pool.size() > 0) conn = pool.poll(); else { final Connection connection = DriverManager.getConnection(jdbcUrl, username, password); conn = (Connection)Proxy.newProxyInstance(null,new Class[]{Connection.class},new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName() == "close" && method.getParameterTypes().length == 0) { pool.add((Connection)proxy); return null; } else return method.invoke(connection,args); } }); } return conn; } public synchronized void reduce() throws SQLException { int size = pool.size(); while(pool.size() > (size / 2) ) pool.poll().close(); } public synchronized void close() throws SQLException { while(pool.size() > 0) pool.poll().close(); } }
参考资料 《Java核心技术》