(點選上方公眾號,可快速關註)
來源:袁鳴凱,
blog.csdn.net/lifetragedy/article/details/8122621
一、前言
上次講了Struts結合Spring並使用Spring的JdbcTemplate來搭建工程框架後我們面臨著jar庫無法管理,工程釋出不方便,jar包在工程內太佔空間,jar包衝突,管理,甚至漏包都問題。於是我們在講“萬能框架spring(二)”前,傳授了一篇番外篇,即講利用maven來管理我們的jar庫。
從今天開始我們將結合“萬能框架spring(一)”與番外篇maven來更進一步豐富我們的ssx框架,那麼今天講的是使用iBatis3結合SS來構建我們的ssi框架,我們把這個框架命名為beta吧。
二、SSI框架
還記得我們在第十八天中講到的我們的框架的架構圖嗎?上面這張是我們今天的架構圖,除了Struts,Spring層,我們需要變換的是DAO層即把原來的SQL這部分換成iBatis,我們在次使用的是iBatis版本3。
由於我們在第十八天中已經說了這樣的一個框架的好處其中就有:
層中相關技術的替換不影響到其它層面
所以對於我們來說我們需要改動的程式碼只有datasource.xml與dao層的2個介面兩個類,那我們就一起來看看這個基於全註解的SSi框架是怎麼樣搭起來的吧。
三、搭建SSI框架
3.1建立工程
我們還是使用maven來建立我們的工程
建完後照著翻外篇《第十九天》中的“四、如何讓Maven構建的工程在eclipse裡跑起來”對工程進行設定。
3.2 增加iBatis3的jar相關包
開啟pom.xml
第一步
找到“slf4j”,將它在pom中的描述改成如下內容:
org.slf4j
slf4j-api
1.5.10
第二步
增加兩個jar包
org.slf4j
slf4j-log4j12
1.5.10
3.3 開始配置ibatis與spring結合
開啟/src/main/resources/spring/datasource下的datasource.xml,增加如下幾行
此處,我們需要4個類,它們是:
org.sky.ssi.ibatis.IBatis3SQLSessionFactoryBean類
package org.sky.ssi.ibatis;
import java.io.IOException;
import java.io.Reader;
import javax.sql.DataSource;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
/**
*
* IBatis3SQLSessionFactoryBean is responsible for integrating iBatis 3
* with spring 3. Since all environment configurations have been moved to
* spring, this class takes the responsibility to get environment information
* from spring configuration to generate SqlSessionFactory.
* @author lifetragedy
*
*/
public class IBatis3SQLSessionFactoryBean implements FactoryBean
, InitializingBean{
rivate String configLocation;
private DataSource dataSource;
private SqlSessionFactory sqlSessionFactory;
private boolean useTransactionAwareDataSource = true;
private String environmentId = “development”;
public String getConfigLocation() {
return configLocation;
}
public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
public boolean isUseTransactionAwareDataSource() {
return useTransactionAwareDataSource;
}
public void setUseTransactionAwareDataSource(
boolean useTransactionAwareDataSource) {
this.useTransactionAwareDataSource = useTransactionAwareDataSource;
}
public String getEnvironmentId() {
return environmentId;
}
public void setEnvironmentId(String environmentId) {
this.environmentId = environmentId;
}
public SqlSessionFactory getObject() throws Exception {
return this.sqlSessionFactory;
}
public Class
getObjectType() {
return SqlSessionFactory.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = this.buildSqlSessionFactory(configLocation);
}
protected SqlSessionFactory buildSqlSessionFactory(String configLocation)
throws IOException {
if (configLocation == null) {
throw new IllegalArgumentException(
“configLocation entry is required”);
}
DataSource dataSourceToUse = this.dataSource;
if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
}
Environment environment = new Environment(environmentId, new IBatisTransactionFactory(dataSourceToUse), dataSourceToUse);
Reader reader = Resources.getResourceAsReader(configLocation);
XMLConfigBuilder parser = new XMLConfigBuilder(reader, null, null);
Configuration config = parser.parse();
config.setEnvironment(environment);
return new DefaultSqlSessionFactory(config);
}
}
org.sky.ssi.ibatis.IBatisDAOSupport
package org.sky.ssi.ibatis;
import javax.annotation.Resource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;
/**
* Base class for all DAO class. The subclass extends this class to get
*
* DAO implementation proxy.
*
* @author lifetragedy
*
* @param
*/
public class IBatisDAOSupport
{
protected Logger log = Logger.getLogger(this.getClass());
@Resource
private SqlSessionFactory ibatisSessionFactory;
private T mapper;
public SqlSessionFactory getSessionFactory() {
return ibatisSessionFactory;
}
protected SqlSession getSqlSession() {
return ibatisSessionFactory.openSession();
}
public T getMapper(Class
clazz) {
mapper = getSqlSession().getMapper(clazz);
return mapper;
}
public T getMapper(Class
clazz, SqlSession session) {
mapper = session.getMapper(clazz);
return mapper;
}
/**
* close SqlSession
*/
protected void closeSqlSession(SqlSession sqlSession) throws Exception {
try {
if (sqlSession != null) {
sqlSession.close();
sqlSession = null;
}
} catch (Exception e) {
}
}
}
org.sky.ssi.ibatis.IBatisTransaction
package org.sky.ssi.ibatis;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.datasource.DataSourceUtils;
public class IBatisTransaction implements Transaction{
private DataSource dataSource;
private Connection connection;
public IBatisTransaction(DataSource dataSource, Connection con, boolean autoCommit){
this.dataSource = dataSource;
this.connection = con;
}
public Connection getConnection(){
eturn connection;
}
public void commit()
throws SQLException{ }
public void rollback()
throws SQLException{ }
public void close()
throws SQLException{
if(dataSource != null && connection != null){
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
}
org.sky.ssi.ibatis.IBatisTransactionFactory
package org.sky.ssi.ibatis;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
public class IBatisTransactionFactory implements TransactionFactory{
private DataSource dataSource;
public IBatisTransactionFactory(DataSource dataSource){
this.dataSource = dataSource;
}
public void setProperties(Properties properties){ }
public Transaction newTransaction(Connection connection, boolean flag){
return new IBatisTransaction(dataSource,connection,flag);
}
}
此三個類的作用就是在datasource.xml檔案中描述的,把spring與datasource.xml中的datasource和transaction連線起來,此處尤其是“IBatis3SQLSessionFactoryBean”的寫法,它透過spring中的“註入”特性,把iBatis的配置註入進spring並委託spring的context來管理iBatis(此屬網上沒有的資料,全部為本人在歷年工程中的經驗總結,並且已經在至少3個專案中進行了整合使用與相關測試)。
建立iBatis配置檔案
我們先在/src/main/resources目錄下建立一個叫sqlmap.xml的檔案,內容如下:
然後我們在/src/main/resources 目錄下建立index.xml與login.xml這2個xml檔案。
看到這兒,有人會問了:為什麼不把這兩個xml檔案也建立在spring目錄下?
原因很簡單:
在datasource.xml檔案內我們已經透過
這樣的方式把iBatis委託給了spring,iBatis的核心就是這個sqlmap.xml檔案了,而在這個sqlmap.xml檔案已經取用了login.xml與index.xml檔案了。
而我們的web.xml檔案裡有這麼一句:
contextConfigLocation
/WEB-INF/classes/spring/**/*.xml
因此如果我們再把ibatis/index.xml與ibatis/login.xml再建立到src/main/resources/spring目錄下,spring於是會在容器啟動時試圖載入這兩個xml檔案,然後一看這兩個xml檔案不是什麼spring的bean,直接拋錯,對吧?
其們等一會再來看login.xml檔案與index.xml檔案,我們先來搞懂iBatis呼叫原理.
3.4 iBatis呼叫原理
1)iBatis就是一個dao層,它又被稱為sqlmapping,它的sql是書寫在一個.xml檔案內的,在該xml檔案內會將相關的sql系結到相關的dao類的方法。
2)在呼叫結束時我們需要在finally塊中關閉相關的sql呼叫。
我們來看一個例子。
login.xml檔案
/span>
PUBLIC “-//ibatis.apache.org//DTD Mapper 3.0//EN”
“http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd”>
SELECT count(1) from t_login where login_id= #{loginId} and login_pwd=#{loginPwd}
]]>
該DAO指向了一個介面org.sky.ssi.dao.LoginDAO,該dao接受一個sql,並且接受一個Map型別的引數。
那麼我們來看該DAO
LoginDao.java
package org.sky.ssi.dao;
import java.util.Map;
public interface LoginDAO {
public int validLogin(Map
paraMap) throws Exception;
}
LoginImpl.java
package org.sky.ssi.dao.impl;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.sky.ssi.dao.LoginDAO;
import org.sky.ssi.ibatis.IBatisDAOSupport;
import org.springframework.stereotype.Repository;
@Repository
public class LoginDAOImpl extends IBatisDAOSupport
implements LoginDAO {
public int validLogin(Map
paraMap) throws Exception {
SqlSession session = this.getSqlSession();
try {
return this.getMapper(LoginDAO.class, session).validLogin(paraMap);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
} finally {
this.closeSqlSession(session);
}
}
}
很簡單吧,一切邏輯都在xml檔案內。
一定記得不要忘了在finally塊中關閉相關的sql呼叫啊,要不然將來工程出了OOM的錯誤不要怪我啊.
3.5 index模組
Index.xml檔案
/span>
PUBLIC “-//ibatis.apache.org//DTD Mapper 3.0//EN”
“http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd”>
SELECT student_no studentNo, student_name studentName from t_student
]]>
insert into t_student(student_no, student_name)values(seq_student_no.nextval,#{stdName})
delete from t_student where student_no=#{stdNo}
它指向了StudentDAO這個介面
StudentDAO.java
package org.sky.ssi.dao;
import org.sky.ssi.dbo.StudentDBO;
import org.sky.ssi.student.form.*;
import java.util.*;
public interface StudentDAO {
public List
getAllStudent() throws Exception;
public void addStudent(Map
paraMap) throws Exception;
public void delStudent(Map
paraMap) throws Exception;
}
StudentDAOImpl.java
package org.sky.ssi.dao.impl;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.sky.ssi.dao.StudentDAO;
import org.sky.ssi.ibatis.IBatisDAOSupport;
import org.sky.ssi.dbo.StudentDBO;
import org.springframework.stereotype.Repository;
@Repository
public class StudentDAOImpl extends IBatisDAOSupport
implements StudentDAO {
@Override
public List
getAllStudent() throws Exception {
SqlSession session = this.getSqlSession();
try {
return this.getMapper(StudentDAO.class, session).getAllStudent();
} catch (Exception e) {
throw new Exception(e);
} finally {
this.closeSqlSession(session);
}
}
public void addStudent(Map
paraMap) throws Exception {
SqlSession session = this.getSqlSession();
try {
this.getMapper(StudentDAO.class, session).addStudent(paraMap);
} catch (Exception e) {
throw new Exception(e);
} finally {
this.closeSqlSession(session);
}
}
public void delStudent(Map
paraMap) throws Exception {
SqlSession session = this.getSqlSession();
try {
this.getMapper(StudentDAO.class, session).delStudent(paraMap);
} catch (Exception e) {
throw new Exception(e);
} finally {
this.closeSqlSession(session);
}
}
}
3.6 Service介面微微有些改變
為了演示給大家看 iBatis接受多個引數的例子因此我們把原來的如:login(String loginId, String loginPwd)這樣的方法改成了public int validLogin(Map
四、beta工程中的增加功能
4.1 增加了一個filter
在我們的web.xml檔案中
LoginFilter
org.sky.ssi.filter.LoginFilter
exclude
/jsp/login/login.jsp,
/login.do
有了這個filter我們就不用在我們的web工程中每一個action、每 個jsp裡進行“使用者是否登入”的判斷了,它會自動根據配置除去“exclude”中的相關web resource,全部走這個“是否登入”的判斷。
註意此處這個exclude是筆者自己寫的,為什麼要exclude?
如果你不exclude,試想一個使用者在login.jsp中填入相關的登入資訊後點一下login按鈕跳轉到了login.do,而這兩個web resource由於沒有被“排除”出“需要登入校驗”,因此每次你一呼叫login.jsp, login.do這個filter就都會強制要求你再跳轉到login.jsp,那麼我們一個使用者從login.jsp登入完後再跳回login.jsp再跳回,再跳回,如此重覆,進入死迴圈。
4.2 增加了一個自動記錄異常的日誌功能
在我們的applicationContext.xml檔案中
id=”methodLoggerAdvisor”
class=”org.sky.ssi.util.LoggerAdvice” >
id=”originalBeanAspect”
ref=”methodLoggerAdvisor” >
id=”loggerPointCut”
expression=”execution(* org.sky.ssi.service.impl.*.*(..))” />
method=”aroundAdvice”
pointcut-ref=”loggerPointCut” />
這樣,我們的dao層、service層、有錯就只管往外throw,框架一方面在接到相關的exception會進行資料庫事務的自動回滾外,還會自動把service層丟擲的exception記錄在log檔案中。
五、測試我們的工程
確認我們的StudentServiceImpl中刪除學生的delStudent方法內容如下:
public void delStudent(String[] stdNo) throws Exception {
for (String s : stdNo) {
Map
paraMap = new HashMap ();
paraMap.put(“stdNo”, s);
studentDAO.delStudent(paraMap);
throw new Exception(“force system to throw a exception”);
}
}
我們把beta工程新增入我們在eclipse中配好的j2eeserver中去並啟動起來。
在IE中輸入:http://localhost:8080/beta/index.do。 系統直接跳到login介面
我們輸入相關的使用者名稱寫密碼。
我們選中“13號學生高樂高”與“9號學生”,點“deletestudent”按鈕。
後臺拋錯了,檢視資料庫內的資料
資料還在,說明我們的iBatis的事務已經在spring中啟作用了.
再次更改StudentServiceImpl.java類中的delStudent方法,把“throw new Exception(“force system to throw a exception”);”註釋掉,再來執行
我們再次選 中9號和13號學生,點deletestudent按鈕,刪除成功,這個夠13的人終於被刪了,呵呵。
系列
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能