一、背景
Java开发人员现在对数据库的操作一般会用到诸如像Hibernate,Mybatis,SpringJdbcTemplate等ORM组件,但是这些组件是怎样从原始的编码方式一步步封装过来的呢?
二、最原始的编码方式
如下面代码所示:在笔者05年刚毕业的时候,曾经是这样写Jdbc访问数据库的.
/**
*
*类描述:用来测试jdbc连接的类(oracle,mysql)
*创建人:keven
*@versionV1.0
*/
public class DBConnectionTest{
/**
*一个数据库的连接我们要具备三项条件,一个是数据库地址,
*一个是访问数据库的用户名和密码
*一个是数据库的类型
*@Title: testDBConnection
*@Description:TODO
*/
public static void testDBConnection(){
Connection con = null;//一个数据库连接
PreparedStatement pre = null;//预编译对象
ResultSet result = null;//结果集
try{
//加载MySQL的驱动程序
Class.forName("com.mysql.jdbc.Driver");
//连接数据库的路径(关键字+IP地址+数据库名称)
String url="jdbc:mysql://127.0.0.1/open";
//数据库的用户名
String user="root";
//数据库密码
String password="123456";
System.out.println("现在开始连接MYSQL数据库");
//获取连接
con= DriverManager.getConnection(url,user,password);
System.out.println("连接MYSQL数据库成功");
String sql="SELECT * FROM USER";
//实例化预编译语句对象
pre=con.prepareStatement(sql);
//pre.setInt(1, deptId);
//返回结果集
result=pre.executeQuery();
//将结果集放到我们的java对象
while(result.next()){
System.out.println(result.getObject(1)+"----"+result.getObject(2)
+"----"+result.getObject(3)+"----"+result.getObject(4)+"----");
}
}catch(Exceptione) {
e.printStackTrace();
}finally{
try{
if(result!=null){
result.close();
}
if(pre!=null){
pre.close();
}
if(con!=null){
con.close();
}
}catch(Exceptione) {
e.printStackTrace();
}
}
}
public static void main(String[]args) {
DBConnectionTest.testDBConnection();
}
}
三、封装的过程和思路
总得来说,Java组件封装的原则就是高内聚,低耦合,直白一点的解释就是将重复性的代码提取出去作为工具类,尽量减少类与类之间的固定依赖.
1)DbUtil工具类
通过查看最原始编码方式的代码,我们可以看出,获取数据库的连接和关闭数据库连接的代码,在每一次操作中都需要,所以我们可以思考一下,将这部分代码提取出去.
*新建DbUtil工具类,用于数据库的开连接和关连接
/**
*类描述:封装第一步 我们把取得连接和关闭连接抽取出来成为独立的方法放入工具类里面
*第二步:我们是否要考虑将我们的数据库连接的四要素(类型,用户名 密码 ,地址 抽取出来做成配置文件的方式)
*创建人:keven
*@versionV1.0
*/
public class DbUtil {
public static Connection getConnection() throws Exception {
Connection con;
//加载MySQL的驱动程序
Class.forName("com.mysql.jdbc.Driver");
//连接数据库的路径(关键字+IP地址+数据库名称)
String url="jdbc:mysql://127.0.0.1/open";
//数据库的用户名
String user="root";
//数据库密码
String password="123456";
System.out.println("现在开始连接MYSQL数据库");
//获取连接
con= DriverManager.getConnection(url,user,password);
return con;
}
/**
*关闭数据库连接
*@Title: closeConnection
*@Description:TODO
*@paramcon
*@parampre
*@paramresult
*/
public static void closeConnection(Connection con, PreparedStatement pre,
ResultSet result) {
try{
if(result!=null){
result.close();
}
if(pre!=null){
pre.close();
}
if(con!=null){
con.close();
}
}catch(Exceptione) {
//TODOAuto-generated catch block
e.printStackTrace();
}
}
}
通过工具类的封装,我们可以继续在工具类里面将数据库的信息通过配置文件加载,以及启用流行的连接池技术,在这里不在赘述.
2)增删改方法的封装
在封装了DbUtil工具类的基础上,我们试着做一个单表的增删改查,请看以下代码:
/**
*关于用户表的增删改查方法
*@authorkeven
*/
public class UserDaoTest{
/**
* user表的增加方法
*/
public int addUser(User user){
int rows= 0;
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
try{
con= DbUtil.getConnection();
String addSql=" INSERT INTO
USER(NAME,AGE,SEX)VALUES(?,?,?)";
pre=con.prepareStatement(addSql);
pre.setString(1,user.getName());
pre.setInt(2,user.getAge());
pre.setString(3,user.getSex());
rows=pre.executeUpdate();
}catch(Exceptione){
e.printStackTrace();
}finally{
DbUtil.closeConnection(con,pre,result);
}
return rows;
}
/**
* user表的删除方法
*/
public int delUser(int userId){
int rows= 0;
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
try{
con= DbUtil.getConnection();
String deleteSql="DELETE FROM USER WHERE ID = ?";
pre=con.prepareStatement(deleteSql);
pre.setInt(1,userId);
rows=pre.executeUpdate();
}catch(Exceptione){
e.printStackTrace();
}finally{
DbUtil.closeConnection(con,pre,result);
}
returnrows;
}
/**
* user表的更新方法
*/
public int updateEmp(Useruser){
int rows= 0;
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
try{
con= DbUtil.getConnection();
String updateSql=" UPDATE USER SET NAME =?, AGE=?,SEX=? WHERE ID =?
";
pre=con.prepareStatement(updateSql);
pre.setString(1,user.getName());
pre.setInt(2,user.getAge());
pre.setString(3,user.getSex());
pre.setInt(4,user.getId());
rows=pre.executeUpdate();
}catch(Exceptione){
e.printStackTrace();
}finally{
DbUtil.closeConnection(con,pre,result);
}
return rows;
}
/**
* user表的查询方法
*/
public List getUserList(){
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
List userList=new ArrayList();
try{
con= DbUtil.getConnection();
String sql="SELECT
ID,NAME,AGE,SEX FROM USER ";
//实例化预编译语句对象
pre=con.prepareStatement(sql);
//pre.setInt(1, deptNo);
//返回结果集
result=pre.executeQuery();
//将结果集放到我们的java对象
User user;
while(result.next()){
user=new User();
user.setId(result.getInt("id"));
user.setName(result.getString("name"));
user.setAge(result.getInt("age"));
user.setSex(result.getString("sex"));
userList.add(user);
}
}catch(Exceptione) {
//TODOAuto-generated catch block
e.printStackTrace();
}finally{
//逐一将上面的对象全部关闭,因为如果不关闭的话会影响数据库的性能,并且占用资源
//逐一关闭的顺序 最后使用的最先关闭
DbUtil.closeConnection(con,pre,result);
}
return userList;
}
}
进一步观察增删改方法,除了Sql语句和参数传入的不同,其他代码其实也是重复的,我们是否可以考虑将这些公用的代码也提取出去呢?
伟大的Java程序员们都是”懒鬼”,一切都是为了少些一些重复的代码以提高工作效率.
我们可以新建一个模板类JdbcTemplate,对增删改方法进行封装,外部只需要传入sql语句和sql语句需要用到的参数.
/**
*类描述:jdbcdao模板类
*通过参数的动态传入来简化dao类的代码
* 1:sql
* 2:sql需要传入的变量的值
*创建人:keven
*/
public class JdbcTemplate{
/**
*封装增删改的模板方法
*@Title: updateTemplate
*@Description:TODO
*@paramsql
*@paramparams
*@return
*/
public int updateTemplate(String sql,Object[] params){
int rows= 0;
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
try{
con= DbUtil.getConnection();
pre=con.prepareStatement(sql);
//设置sql所需要的参数
if(params!=null){
for(int i=0;i
pre.setObject(i+1,params[i]);
}
}
rows=pre.executeUpdate();
}catch(Exceptione){
e.printStackTrace();
}finally{
DbUtil.closeConnection(con,pre,result);
}
return rows;
}
}
通过上面步骤的封装,我们再来看看让对单表增删改的操作是如何方便简单的,新建一个单表增删改的测试类,继承我们封装的模板类,代码如下:
/**
*继承了模板类的单表增删改操作
*@authorkeven
*
*/
public class UserDaoTemplateTest extends JdbcTemplate {
/**
* user表的增加方法
*@Title:adduser
*@Description:TODO
*@paramuser
*@return
*/
public int addUser(User user){
String addSql=" INSERT INTO
USER(NAME,AGE,SEX)VALUES(?,?,?)";
Object[]params=newObject[]{user.getName(),user.getAge(),user.getSex()};
return this.updateTemplate(addSql,params);
}
/**
* user表的删除方法
*@Title:deluser
*@Description:TODO
*@paramuserNo
*@return
*/
public int deluser(int userId){
String deleteSql="DELETE FROM USER WHERE ID=?";
Object[]params=newObject[]{userId};
return this.updateTemplate(deleteSql,params);
}
/**
* user表的修改方法
*@Title:updateuser
*@Description:TODO
*@paramuser
*@return
*/
public int updateUser(Useruser){
String updateSql=" UPDATE USER SET NAME =?, AGE=?,SEX=? WHERE ID =?
";
Object[]params=newObject[]{user.getName(),user.getAge(),user.getSex(),user.getId()};
return this.updateTemplate(updateSql,params);
}
}
回过头看看我们的封装过程和代码,是不是对于开发人员来讲,越来越简单,代码写的越来越少,这就是Java在实际开发过程中需要用到大量前辈们封装的组件的原因.
3)查询方法的封装
在增删改方法的封装过程当中,我们发现,增删改的操作,方法的返回值是固定的,但是查询方法的返回值是不固定的,查询不同的表,返回的是不同对象,也有可能是返回的其他类型的值.
通过以上分析,我们封装查询方法的时候,只能返回一个固定格式的对象或者列表,让执行查询的人来解析固定格式的结果得到自己想要的返回值.
两种方式:
a:返回一个List结构
在JdbcTemplate模板类面新加查询模板方法
/**
*利用Map结构来获取每行的记录 以List返回
*@Title:queryForList
*@Description:TODO
*@paramsql
*@paramparams
*@return
*/
public List queryForList(String sql,Object[]params){
List mapList=new ArrayList();
Connection con=null;//一个数据库连接
PreparedStatement pre=null;//预编译对象
ResultSet result=null;//结果集
try{
con= DbUtil.getConnection();
pre=con.prepareStatement(sql);
//设置sql所需要的参数
if(params!=null){
for(inti=0;i
pre.setObject(i+1,params[i]);
}
}
result=pre.executeQuery();
//怎么讲result的结果集返回给mapList
//1:得到结果集里面每一个元数据的对象
ResultSetMetaData metaData=result.getMetaData();
//获取结果集的列数
int columnNum=metaData.getColumnCount();
Map mapObj;
while(result.next()){
mapObj=newHashMap();
for(int i= 0;i //根据索引获取列名(map的key) String columnName=metaData.getColumnLabel(i+1); //根据列名或者值(map的value) Object value=result.getObject(columnName); mapObj.put(columnName,value); } mapList.add(mapObj); } }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtil.closeConnection(con,pre,result); } return mapList; } 这种封装方式在执行查询时候,获取的结果是List>结构的值,需要自己再进行转化,但是对于查询来说,就非常的简单了。 在UserDaoTemplateTest类里面新加查询方法 //查询方法如果要封装成模板类 //主要要去思考每一个查询的方法返回的类型是不一样的 //1:List> //2:我们需要再Dao层就做到返回我们需要的List public List getUserListMap(){ String sql="SELECT ID,NAME,AGE,SEX FROM USER "; Object[]params=new Object[]{}; return this.queryForList(sql,params); } B:返回一个接口的匿名内部类 这种方式,封装起来稍微复杂一些,但是对于查询方来说,就可以直接在查询方法里面获取自己想要的对象,返回List, 非常简单。 步骤: *新建一个接口RowMapper,成员是一个匿名的内部类 /** *通过传入ResultSet对象讲每一条记录通过泛型映射成对应的对象 使用的是接口的匿名内部类 */ public interface RowMapper{ publicT mappingRow(ResultSet rs,int rownum)throwsSQLException; } *在JdbcTemplate模板类里面新增模板查询方法 public List queryForList(RowMappermapper,Stringsql,Object[]params){ List returnResult=new ArrayList(); Connection con=null;//一个数据库连接 PreparedStatement pre=null;//预编译对象 ResultSet result=null;//结果集 try{ con= DbUtil.getConnection(); pre=con.prepareStatement(sql); //设置sql所需要的参数 if(params!=null){ for(int i=0;i pre.setObject(i+1,params[i]); } } result=pre.executeQuery(); int rownum= 0; while(result.next()){ rownum++; returnResult.add(mapper.mappingRow(result,rownum)); } }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtil.closeConnection(con,pre,result); } return returnResult; } *查询的时候,通过实现匿名的内部类来获取结果,直接映射到Java对象当中 ,如代码所示,在UserDaoTemplateTest中进行查询 /** *利用接口里面匿名内部类的实现来讲结果集赋给对象的值移到dao层实现 *@Title:getuserList *@Description:TODO *@paramdeptNo *@return */ public List getUserList(){ List userList=new ArrayList(); Stringsql="SELECT ID,NAME,AGE,SEX FROM USER "; Object[]params=newObject[]{}; userList= this.queryForList(newRowMapper(){ public User mappingRow(ResultSet rs,int rownum)throwsSQLException { Useruser=newUser(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setAge(rs.getInt("age")); user.setSex(rs.getString("sex")); return user; } },sql,params); return userList; } 最终的代码目录结构如下,希望对大家的学习有所帮助。 通过以上的封装过程,我们可以了解到Java封装组件的一个基本思路,有助于大家以后在用到相关的ORM组件时,对它们有一个更深得到认识,当然,本篇文章封装的代码只是冰山一角,如果需要达到像Mybatis和Hibernate等组件的高度,还有很长的一段的路要走,有兴趣的同学可以查看一下SpringJdbcTemplate的源码,其中的思想是跟它不谋而合的。四、总结