拿之前上课老师讲的东西复习下发现收获颇多,就从封装这点出发他的东西都足以让人佩服,小弟就拿出来一边当复习,一般与诸君分享
首先我们知道java对数据库的操作有着固定的步骤1加载驱动,2获得连接,3创建statement,4创建结果集,之后是相应的关闭操作,关闭顺序与打开顺序相反。那么我们又该如何从以上的步骤中抽离出共同要使用的部分加以调用呢?下面先看代码
package com.itany.util; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; public class JDBCUtil { private static DataSource ds; private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); static { // 从文件读取 在类加载路径下datasource.properties Properties p = new Properties(); try { p.load(JDBCUtil.class.getClassLoader().getResourceAsStream( "datasource.properties")); ds = BasicDataSourceFactory.createDataSource(p); } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError("JDBCUtil初始化失败"); } } // 一个线程一个连接,一个事务一个连接 public static Connection getConnection() { Connection con = null; try { con = threadLocal.get(); if (con == null) { con = ds.getConnection(); threadLocal.set(con); } } catch (SQLException e) { e.printStackTrace(); } return con; } public static void close(ResultSet rs, PreparedStatement ps, Connection con) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 关闭当前线程的Connection public static void close() { try { threadLocal.get().close(); threadLocal.remove(); } catch (SQLException e) { e.printStackTrace(); } } }
该工具类是对连接做专门封装的类。
可以看到这里使用了加载配置文件的方法加载驱动并获得数据集,数据库的相关设置如驱动名,连接URL,数据库用户名,数据库用户密码,以及最大连接数的设置等都可以写到配置文件中,配置文件的使用使得数据库的加载设置与程序代码分离,简化我们的程序跨数据库时的操作,同时也不容易出错。
这里使用ThreadLocal来对得到的连接进行保存,ThreadLocal可以完美胜任多线程的任务,也是极大的方便我们对数据库进行多个链接的操作。ThreadLocal作为连接缓存,减少了对数据库的频繁访问次数,可以提升程序的效率。大家可以看到这里数据集的获取方法写在java静态初始化器static中,这里很巧妙的应用了单例模式使得我们可以只进行“一次连接”然后多次使用。
最后连接的关闭工作也在这个类里进行,具体实现不在赘言。下面要说下对数据库基本操作增删改的封装过程。
package com.itany.util; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.itany.util.JDBCUtil; public class JDBCTemplate { public void update(String sql,Object... params){ Connection con=null; PreparedStatement ps=null; try { con=JDBCUtil.getConnection(); ps=con.prepareStatement(sql); for (int i = 0; i < params.length; i++) { setParam(ps,i+1,params[i]); } ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.close(null, ps, null); } } public List query(String sql,RowMapper rm ,Object ...params){ List<Object> list=new ArrayList<Object>(); Connection con=null; PreparedStatement ps=null; ResultSet rs=null; try { con=JDBCUtil.getConnection(); ps=con.prepareStatement(sql); for (int i = 0; i < params.length; i++) { setParam(ps, i+1, params[i]); } rs=ps.executeQuery(); while(rs.next()){ Object obj=rm.mapRow(rs); list.add(obj); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, null); } return list; } public Object save(String sql,Object... params){ Connection con=null; PreparedStatement ps=null; ResultSet rs=null; try { con=JDBCUtil.getConnection(); ps=con.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS); for (int i = 0; i < params.length; i++) { setParam(ps,i+1,params[i]); } ps.executeUpdate(); rs=ps.getGeneratedKeys(); if(rs.next()){ return rs.getObject(1); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, null); } return null; } private void setParam(PreparedStatement ps,int index,Object param) throws SQLException{ if(param instanceof String){ ps.setString(index, (String)param); }else if(param instanceof Date){ Date d=(Date)param; java.sql.Date date=new java.sql.Date(d.getTime()); ps.setDate(index, date); }else{ ps.setObject(index, param); } } }
实际上,数据库的增和改都是执行相同的executeUpdate()操作,不论是自己写的或者是相关框架的底层都是用这个方法,这里之所以将增和改的方法分开是为了save的时候保留所save的对象,而查的操作与这两个方法不同,查询是要得到结果的所以还有个对结果的封装,但是三个方法都具有相同的设置参数的行为,因此这里将设置参数抽离出来封装成setParam的方法,方便大家共同调用,这个方法对传入的参数做了简单的转换工作,以完成对不同属性参数的设置。三个基本操作内部依然按照数据库的相关操作步骤,获得连接,创建statement,创建结果集然后依次关闭等,不过这里query方法对结果处理上还使用了orm的元素,通过一个关系实体的映射接口实现数据库数据到java实体对象的映射,下面给出接口的定义以及具体的实现类供大家参考
package com.itany.util; import java.sql.ResultSet; public interface RowMapper { public Object mapRow(ResultSet rs)throws Exception ; }
package com.itany.util; import java.sql.ResultSet; import com.itany.entity.Product; public class ProductRowMapper implements RowMapper { public Object mapRow(ResultSet rs) throws Exception { Product product=new Product(); product.setEp_id(rs.getInt("ep_id")); product.setEp_name(rs.getString("ep_name")); product.setEp_description(rs.getString("ep_description")); product.setEp_price(rs.getDouble("ep_price")); product.setEp_stock(rs.getInt("ep_stock")); product.setEpc_id(rs.getInt("epc_id")); product.setEpc_child_id(rs.getInt("ep_child_id")); product.setEp_file_name(rs.getString("ep_file_name")); product.setEp_count(rs.getInt("ep_count")); product.setEp_discount(rs.getFloat("ep_discount")); return product; } }