通向架構師的道路(第二十天)萬能框架spring (二)
(點擊
上方公眾號
,可快速關注)
來源:袁鳴凱,
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中的描述改成如下內容:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.10</version>
</dependency>
第二步
增加兩個jar包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>
3.3 開始配置ibatis與spring結合
打開/src/main/resources/spring/datasource下的datasource.xml,增加如下幾行
<bean id="iBatisSessionFactory" class="org.sky.ssi.ibatis.IBatis3SQLSessionFactoryBean" scope="singleton">
<property name="configLocation" value="sqlmap.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="iBatisDAOSupport" class="org.sky.ssi.ibatis.IBatisDAOSupport">
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
此處,我們需要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 <p>
* with spring 3. Since all environment configurations have been moved to <p>
* spring, this class takes the responsibility to get environment information<p>
* from spring configuration to generate SqlSessionFactory.
* @author lifetragedy
*
*/
public class IBatis3SQLSessionFactoryBean implements FactoryBean<SqlSessionFactory>, 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<SqlSessionFactory> 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
* <p>
* DAO implementation proxy.
*
* @author lifetragedy
*
* @param <T>
*/
public class IBatisDAOSupport<T> {
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<T> clazz) {
mapper = getSqlSession().getMapper(clazz);
return mapper;
}
public T getMapper(Class<T> 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的文件,內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//ibatis.apache.org//DTD Config 3.0//EN" "//ibatis.apache.org/dtd/ibatis-3-config.dtd">
<configuration>
<mappers>
<mapper resource="ibatis/index.xml" />
<mapper resource="ibatis/login.xml" />
</mappers>
</configuration>
然後我們在/src/main/resources 目錄下建立index.xml與login.xml這2個xml文件。
看到這兒,有人會問了:為什麼不把這兩個xml文件也建立在spring目錄下?
原因很簡單:
在datasource.xml文件內我們已經通過
<bean id="iBatisSessionFactory" class="org.sky.ssi.ibatis.IBatis3SQLSessionFactoryBean" scope="singleton">
<property name="configLocation" value="sqlmap.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
這樣的方式把iBatis委託給了spring,iBatis的核心就是這個sqlmap.xml文件了,而在這個sqlmap.xml文件已經引用了login.xml與index.xml文件了。
而我們的web.xml文件里有這麼一句:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/**/*.xml</param-value>
</context-param>
因此如果我們再把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文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"//ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="org.sky.ssi.dao.LoginDAO">
<select id="validLogin" resultType="int" parameterType="java.util.Map">
<![CDATA[
SELECT count(1) from t_login where login_id= #{loginId} and login_pwd=#{loginPwd}
]]>
</select>
</mapper>
該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<String, Object> 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<LoginDAO> implements LoginDAO {
public int validLogin(Map<String, Object> 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文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"//ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="org.sky.ssi.dao.StudentDAO">
<select id="getAllStudent" resultType="org.sky.ssi.dbo.StudentDBO">
<![CDATA[
SELECT student_no studentNo, student_name studentName from t_student
]]>
</select>
<update id="addStudent" parameterType="java.util.Map">
insert into t_student(student_no, student_name)values(seq_student_no.nextval,#{stdName})
</update>
<update id="delStudent" parameterType="java.util.Map">
delete from t_student where student_no=#{stdNo}
</update>
</mapper>
它指向了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<StudentDBO> getAllStudent() throws Exception;
public void addStudent(Map<String, Object> paraMap) throws Exception;
public void delStudent(Map<String, Object> 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<StudentDAO> implements StudentDAO {
@Override
public List<StudentDBO> 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<String, Object> 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<String, Object> 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<String, Object> paraMap) throws Exception;這樣的結構,請大家注意。
四、beta工程中的增加功能
4.1 增加了一個filter
在我們的web.xml文件中
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>org.sky.ssi.filter.LoginFilter</filter-class>
<init-param>
<param-name>exclude</param-name>
<param-value>/jsp/login/login.jsp,
/login.do
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
有了這個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文件中
<bean
id="methodLoggerAdvisor"
class="org.sky.ssi.util.LoggerAdvice" >
</bean>
<aop:config>
<aop:aspect
id="originalBeanAspect"
ref="methodLoggerAdvisor" >
<aop:pointcut
id="loggerPointCut"
expression="execution(* org.sky.ssi.service.impl.*.*(..))" />
<aop:around
method="aroundAdvice"
pointcut-ref="loggerPointCut" />
</aop:aspect>
</aop:config>
這樣,我們的dao層、service層、有錯就只管往外throw,框架一方面在接到相關的exception會進行資料庫事務的自動回滾外,還會自動把service層拋出的exception記錄在log文件中。
五、測試我們的工程
確認我們的StudentServiceImpl中刪除學生的delStudent方法內容如下:
public void delStudent(String[] stdNo) throws Exception {
for (String s : stdNo) {
Map<String, Object> paraMap = new HashMap<String, Object>();
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的人終於被刪了,呵呵。
系列
通向架構師的道路(第一天)之 Apache 整合 Tomcat
通向架構師的道路(第二天)之 apache tomcat https 應用
通向架構師的道路(第三天)之 apache 性能調優
通向架構師的道路(第四天)之 Tomcat 性能調優
通向架構師的道路(第五天)之 tomcat 集群 – 群貓亂舞
通向架構師的道路(第六天)之漫談基於資料庫的許可權系統的設計
通向架構師的道路(第七天)之漫談使用 ThreadLocal 改進你的層次的劃分
通向架構師的道路(第八天)之 Weblogic 與 Apache 的整合與調優
通向架構師的道路(第九天)之 Weblogic 的集群與配置
通向架構師的道路 ( 第十天 ) 之 Axis2 Web Service ( 一 )
通向架構師的道路 ( 第十一天 ) 之 Axis2 Web Service ( 二 )
通向架構師的道路 ( 第十二天 ) 之 Axis2 Web Service ( 三 )
通向架構師的道路 ( 第十三天 ) Axis2 Web Service 安全初步
通向架構師的道路 ( 第十四天 ) Axis2 Web Service 安全之 rampart
通向架構師的道路 ( 第十五天 ) IBM Websphere 的安裝與優化
通向架構師的道路 ( 第十六天 ) IBM Websphere 與 IBM HttpServer 的集成
通向架構師的道路 ( 第十七天 ) IBM Websphere 集群探秘 – WASND
通向架構師的道路(第十八天)萬能框架 Spring ( 一 )
看完本文有收穫?請轉發分享給更多人
關注「ImportNew」,提升Java技能
※演算法餘暉
※如何編寫 bash completion script
TAG:ImportNew |