一浙滤、背景
Java開發(fā)人員現(xiàn)在對數(shù)據(jù)庫的操作一般會用到諸如像Hibernate,Mybatis,SpringJdbcTemplate等ORM組件,但是這些組件是怎樣從原始的編碼方式一步步封裝過來的呢?
二、最原始的編碼方式
如下面代碼所示:在筆者05年剛畢業(yè)的時候,曾經(jīng)是這樣寫Jdbc訪問數(shù)據(jù)庫的.
/**
*
*類描述:用來測試jdbc連接的類(oracle,mysql)
*創(chuàng)建人:keven
*@versionV1.0
*/
public?class?DBConnectionTest{
/**
*一個數(shù)據(jù)庫的連接我們要具備三項條件楞慈,一個是數(shù)據(jù)庫地址,
*一個是訪問數(shù)據(jù)庫的用戶名和密碼
*一個是數(shù)據(jù)庫的類型
*@Title: testDBConnection
*@Description:TODO
*/
public?static?void?testDBConnection(){
Connection con =?null;//一個數(shù)據(jù)庫連接
PreparedStatement pre =?null;//預(yù)編譯對象
ResultSet result =?null;//結(jié)果集
try{
//加載MySQL的驅(qū)動程序
Class.forName("com.mysql.jdbc.Driver");
//連接數(shù)據(jù)庫的路徑(關(guān)鍵字+IP地址+數(shù)據(jù)庫名稱)
String url="jdbc:mysql://127.0.0.1/open";
//數(shù)據(jù)庫的用戶名
String user="root";
//數(shù)據(jù)庫密碼
String password="123456";
System.out.println("現(xiàn)在開始連接MYSQL數(shù)據(jù)庫");
//獲取連接
con= DriverManager.getConnection(url,user,password);
System.out.println("連接MYSQL數(shù)據(jù)庫成功");
String sql="SELECT * FROM USER";
//實例化預(yù)編譯語句對象
pre=con.prepareStatement(sql);
//pre.setInt(1, deptId);
//返回結(jié)果集
result=pre.executeQuery();
//將結(jié)果集放到我們的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組件封裝的原則就是高內(nèi)聚,低耦合,直白一點的解釋就是將重復性的代碼提取出去作為工具類,盡量減少類與類之間的固定依賴.
1)DbUtil工具類
通過查看最原始編碼方式的代碼,我們可以看出,獲取數(shù)據(jù)庫的連接和關(guān)閉數(shù)據(jù)庫連接的代碼,在每一次操作中都需要,所以我們可以思考一下,將這部分代碼提取出去.
*新建DbUtil工具類,用于數(shù)據(jù)庫的開連接和關(guān)連接
/**
*類描述:封裝第一步 我們把取得連接和關(guān)閉連接抽取出來成為獨立的方法放入工具類里面
*第二步:我們是否要考慮將我們的數(shù)據(jù)庫連接的四要素(類型氮唯,用戶名 密碼 褒侧,地址 抽取出來做成配置文件的方式)
*創(chuàng)建人:keven
*@versionV1.0
*/
public?class?DbUtil {
public?static?Connection getConnection()?throws?Exception {
Connection con;
//加載MySQL的驅(qū)動程序
Class.forName("com.mysql.jdbc.Driver");
//連接數(shù)據(jù)庫的路徑(關(guān)鍵字+IP地址+數(shù)據(jù)庫名稱)
String url="jdbc:mysql://127.0.0.1/open";
//數(shù)據(jù)庫的用戶名
String user="root";
//數(shù)據(jù)庫密碼
String password="123456";
System.out.println("現(xiàn)在開始連接MYSQL數(shù)據(jù)庫");
//獲取連接
con= DriverManager.getConnection(url,user,password);
return?con;
}
/**
*關(guān)閉數(shù)據(jù)庫連接
*@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();
}
}
}
通過工具類的封裝,我們可以繼續(xù)在工具類里面將數(shù)據(jù)庫的信息通過配置文件加載,以及啟用流行的連接池技術(shù),在這里不在贅述.
2)增刪改方法的封裝
在封裝了DbUtil工具類的基礎(chǔ)上,我們試著做一個單表的增刪改查,請看以下代碼:
/**
*關(guān)于用戶表的增刪改查方法
*@authorkeven
*/
public?class?UserDaoTest{
/**
* user表的增加方法
*/
public?int?addUser(User user){
int?rows= 0;
Connection con=null;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
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;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
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;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
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;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
List?userList=new?ArrayList();
try{
con= DbUtil.getConnection();
String sql="SELECT
ID,NAME,AGE,SEX FROM USER ";
//實例化預(yù)編譯語句對象
pre=con.prepareStatement(sql);
//pre.setInt(1, deptNo);
//返回結(jié)果集
result=pre.executeQuery();
//將結(jié)果集放到我們的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{
//逐一將上面的對象全部關(guān)閉良风,因為如果不關(guān)閉的話會影響數(shù)據(jù)庫的性能,并且占用資源
//逐一關(guān)閉的順序 最后使用的最先關(guān)閉
DbUtil.closeConnection(con,pre,result);
}
return?userList;
}
}
進一步觀察增刪改方法,除了Sql語句和參數(shù)傳入的不同,其他代碼其實也是重復的,我們是否可以考慮將這些公用的代碼也提取出去呢?
偉大的Java程序員們都是”懶鬼”,一切都是為了少些一些重復的代碼以提高工作效率.
我們可以新建一個模板類JdbcTemplate,對增刪改方法進行封裝,外部只需要傳入sql語句和sql語句需要用到的參數(shù).
/**
*類描述:jdbcdao模板類
*通過參數(shù)的動態(tài)傳入來簡化dao類的代碼
* 1:sql
* 2:sql需要傳入的變量的值
*創(chuàng)建人:keven
*/
public?class?JdbcTemplate{
/**
*封裝增刪改的模板方法
*@Title: updateTemplate
*@Description:TODO
*@paramsql
*@paramparams
*@return
*/
public?int?updateTemplate(String sql,Object[] params){
int?rows= 0;
Connection con=null;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
try{
con= DbUtil.getConnection();
pre=con.prepareStatement(sql);
//設(shè)置sql所需要的參數(shù)
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);
}
}
回過頭看看我們的封裝過程和代碼,是不是對于開發(fā)人員來講,越來越簡單,代碼寫的越來越少,這就是Java在實際開發(fā)過程中需要用到大量前輩們封裝的組件的原因.
3)查詢方法的封裝
在增刪改方法的封裝過程當中,我們發(fā)現(xiàn),增刪改的操作,方法的返回值是固定的,但是查詢方法的返回值是不固定的,查詢不同的表,返回的是不同對象,也有可能是返回的其他類型的值.
通過以上分析,我們封裝查詢方法的時候,只能返回一個固定格式的對象或者列表,讓執(zhí)行查詢的人來解析固定格式的結(jié)果得到自己想要的返回值.
兩種方式:
a:返回一個List結(jié)構(gòu)
在JdbcTemplate模板類面新加查詢模板方法
/**
*利用Map結(jié)構(gòu)來獲取每行的記錄 以List返回
*@Title:queryForList
*@Description:TODO
*@paramsql
*@paramparams
*@return
*/
public?List??queryForList(String sql,Object[]params){
List?mapList=new?ArrayList();
Connection con=null;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
try{
con= DbUtil.getConnection();
pre=con.prepareStatement(sql);
//設(shè)置sql所需要的參數(shù)
if(params!=null){
for(inti=0;i
pre.setObject(i+1,params[i]);
}
}
result=pre.executeQuery();
//怎么講result的結(jié)果集返回給mapList
//1:得到結(jié)果集里面每一個元數(shù)據(jù)的對象
ResultSetMetaData metaData=result.getMetaData();
//獲取結(jié)果集的列數(shù)
int?columnNum=metaData.getColumnCount();
Map?mapObj;
while(result.next()){
mapObj=newHashMap();
for(int?i= 0;i<columnName;i++){
//根據(jù)索引獲取列名(map的key)
String columnName=metaData.getColumnLabel(i+1);
//根據(jù)列名或者值(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;
}
這種封裝方式在執(zhí)行查詢時候,獲取的結(jié)果是List>結(jié)構(gòu)的值,需要自己再進行轉(zhuǎn)化闷供,但是對于查詢來說烟央,就非常的簡單了。
在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:返回一個接口的匿名內(nèi)部類
這種方式歪脏,封裝起來稍微復雜一些疑俭,但是對于查詢方來說,就可以直接在查詢方法里面獲取自己想要的對象婿失,返回List,
非常簡單钞艇。
步驟:
*新建一個接口RowMapper,成員是一個匿名的內(nèi)部類
/**
*通過傳入ResultSet對象講每一條記錄通過泛型映射成對應(yīng)的對象 使用的是接口的匿名內(nèi)部類
*/
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;//一個數(shù)據(jù)庫連接
PreparedStatement pre=null;//預(yù)編譯對象
ResultSet result=null;//結(jié)果集
try{
con= DbUtil.getConnection();
pre=con.prepareStatement(sql);
//設(shè)置sql所需要的參數(shù)
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;
}
*查詢的時候豪硅,通過實現(xiàn)匿名的內(nèi)部類來獲取結(jié)果哩照,直接映射到Java對象當中 ,如代碼所示懒浮,在UserDaoTemplateTest中進行查詢
/**
*利用接口里面匿名內(nèi)部類的實現(xiàn)來講結(jié)果集賦給對象的值移到dao層實現(xiàn)
*@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;
}
最終的代碼目錄結(jié)構(gòu)如下飘弧,希望對大家的學習有所幫助。
四砚著、總結(jié)
通過以上的封裝過程次伶,我們可以了解到Java封裝組件的一個基本思路,有助于大家以后在用到相關(guān)的ORM組件時稽穆,對它們有一個更深得到認識冠王,當然,本篇文章封裝的代碼只是冰山一角舌镶,如果需要達到像Mybatis和Hibernate等組件的高度柱彻,還有很長的一段的路要走,有興趣的同學可以查看一下SpringJdbcTemplate的源碼乎折,其中的思想是跟它不謀而合的绒疗。