1. 程式人生 > >SSH專案實戰OA-dubbo

SSH專案實戰OA-dubbo

Dubbo是一個分散式服務框架,致力於提供高效能和透明化的RPC遠端服務呼叫方案,以及SOA服務治理方案。簡單的說,dubbo就是個服務框架,本質上是個遠端服務呼叫的分散式框架.由於我們子系統中web工程和service工程是釋出在不同的Tomcat,所以需要使用Dubbo來發布和引用服務.

安裝Zookeeper

Dubbo一般會使用Zookeeper作為註冊中心,所以我們需要在linux虛擬機器中安裝Zookeeper服務.

安裝方法可以參考我的一篇部落格:

Ubuntu下安裝zookeeper

 

Dubbo採用全Spring配置方式,透明化接入應用,對應用沒有任何API侵入,只需用Spring載入Dubbo的配置即可,Dubbo基於Spring的Schema擴充套件進行載入。

如果不用Dubbo,單一工程中spring的配置可能如下:

<bean id="xxxService" class="com.xxx.XxxServiceImpl" />

<bean id="xxxAction" class="com.xxx.XxxAction">

    <property name="xxxService" ref="xxxService" />

</bean>

一旦用了Dubbo,在本地服務的基礎上,只需做簡單配置,即可完成遠端化服務。例如,將上面的配置檔案拆分成兩份,將服務定義部分放在服務提供方remote-provider.xml,將服務引用部分放在服務消費方remote-consumer.xml,並在提供方增加暴露服務配置<dubbo:service>,在消費方增加引用服務配置<dubbo:reference>。所以,我們要是使用了Dubbo的話,就要分為釋出服務和呼叫服務兩部分了,釋出服務方式如下:

<!-- 和本地服務一樣實現遠端服務 -->

<bean id="xxxService" class="com.xxx.XxxServiceImpl" />

<!-- 增加暴露遠端服務配置,interface:指定服務的介面,ref:指定介面的實現類 -->

<dubbo:service interface="com.xxx.XxxService" ref="xxxService" />

 

(表現層)呼叫服務的方式如下:

<!-- 增加引用遠端服務配置,

interface:指定服務的介面-->

<dubbo:reference id="xxxService" interface="com.xxx.XxxService" />

<!-- 和本地服務一樣使用遠端服務 -->

<bean id="xxxAction" class="com.xxx.XxxAction">

    <property name="xxxService" ref="xxxService" />

</bean>

 

為了更好說明dubbo的使用方法,讓我們能先構建出一個完整的功能,如根據Id查詢使用者資訊,實現service,dao層和controller

構建service層和dao

首先在OA-system-interface中建立包com.QEcode.OA.service,並在該包下,新建一個介面UserService

 

然後我們在OA-system-servicecom.QEcode.OA.service.impl包下新建一個實現類,如下圖所示。

寫完發現,我們還沒建立UserDao,所以還要在OA-system-daosrc/main/java目錄下新建包com.QEcode.OA.dao.impl,並在dao包下新建介面UserDao,impl包下新建實現類UserDaoImpl

此外,由於我們需要使用註解來將Dao的實現類注入到spring容器中,以及spring幫我們封裝好的hibernate session物件HibernateTemplate,所以需要新增spring的依賴.

那麼為什麼要使用HibernateTemplate?

我們使用HibernateTemplate,有一個很重要的原因就在於我們不想直接控制事務,不想直接去獲取,開啟Session,開始一個事務,處理異常,提交一個事務,最後關閉一個Session.HibernateTemplate Hibernate操作進行封裝,我們只要簡單的條用HibernateTemplate 物件,傳入hql和引數,就獲得查詢介面,至於事務的開啟,關閉,都交給HibernateTemplate  物件來處理我們自己只專注於業務,不想去作這些重複而繁瑣的操作。

 <!-- Spring -->

       <dependency>

           <groupId>org.springframework</groupId>

           <artifactId>spring-context</artifactId>

       </dependency>

 <dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-orm</artifactId>

</dependency>

 

現在OA.system.dao工程的pom.xml檔案如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.QEcode</groupId>
    <artifactId>OA-system</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>OA-system-dao</artifactId>
  <dependencies>
  	<dependency>
  		 <groupId>com.QEcode</groupId>
    	<artifactId>OA-system-pojo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  	<!-- hibernate -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
	</dependency>
  	<!-- MySql -->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      </dependency>
      <!-- 連線池 -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
      </dependency>
      <!-- Spring -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
       </dependency>
       <dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
		</dependency>
      
      
  </dependencies>
  
</project>


現在來寫我們的持久層userDao-------等等,先不要急著寫userDao.讓我們思考一個問題?每一個Dao都需要有增刪改查的操作,而這些操作大致上是相同,那麼我們需要在每個Dao類中都重複寫上同樣的程式碼嗎?顯然,這是不符合可重用性原則的,我們可以把這些功能一樣的程式碼抽取出來,構成一個BaseDao類,然後讓每一個Dao實現類都繼承BaseDao,那麼我們就不用每次都寫增刪改查了.

下面讓我們來新建這個BaseDao.

dao包下新建介面BaseDao,並在impl包下新建BaseDaoImpl

由於BaseDao是一個抽取dao基本功能的介面,有著增刪改查等方法,但是這有個問題,那就是hibernate的方法大多數都需要傳入一個實體類的Class物件,並且當我們查詢到結果時,需要返回一個實體類物件.

比如根據id查詢user的方法:User getById(Long userId),需要返回一個User物件,可是,BaseDao是被所有的Dao所實現,其他Dao需要返回的就不是User物件,而是它自己的實體類物件,所以,我們不能在BaseDao中限定實體類的型別.那麼要怎麼辦呢?可能有人會想到了,那就是泛型<T>.BaseDao,所有實體型別都用泛型來表示,只有當BaseDao被實現時,由實現類根據其本身的型別來指定T的具體型別.

BaseDao具體實現如下:

public interface BaseDao<T> {
   
    /**
     * @Description:儲存實體
     * @param entity
     */
    public void save(T entity);
   
    /**
     * @Description:修改實體
     * @param entity
     */
    public void update(T entity);
   
    /**
     * @Description:根據id刪除實體
     * @param id
     */
    public void delete(Long id);
   
   
    /**
     * @Description:根據id查詢實體
     * @param id 實體id
     * @return
     */
    public T findById(long id);
   
   
    /**
     * @Description:根據多個id獲取多個實體
     * @param ids
     * @return
     */
    public List<T> findByIds(Long[] ids);
   
    /**
     * @Description:查詢實體列表
     * @return
     */
    public List<T> findAll();
   
 }


下面是BaseDaoImpl的內容:現在我們已經寫出了介面,那麼就要實現BaseDaoImpl,它實現了BaseDao介面。

public interface BaseDao<T> {
   
    /**
     * @Description:儲存實體
     * @param entity
     */
    public void save(T entity);
   
    /**
     * @Description:修改實體
     * @param entity
     */
    public void update(T entity);
   
    /**
     * @Description:根據id刪除實體
     * @param id
     */
    public void delete(Long id);
   
   
    /**
     * @Description:根據id查詢實體
     * @param id 實體id
     * @return
     */
    public T findById(long id);
   
   
    /**
     * @Description:根據多個id獲取多個實體
     * @param ids
     * @return
     */
    public List<T> findByIds(Long[] ids);
   
    /**
     * @Description:查詢實體列表
     * @return
     */
    public List<T> findAll();
   
 }

現在我們已經實現了BaseDaoImpl...........並沒有,細心一點就會發現Class<T> clazz是一個空值,我們需要給它賦值.那麼clazz是什麼呢,它是繼承了BaseDaoImpl類的dao中實體類的Class物件,UserDaoImpl繼承了BaseDaoImpl,那麼clazz=User.Class.而想要獲取到Class物件,就要設及到反射了.

如果不瞭解反射,可以參考我的一篇部落格:

Java基礎-反射

我們可以在建立BaseDaoImpl的時候,初始化clazz物件

public BaseDaoImpl(){
    //利用反射得到T的型別
    ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
    this.clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}

getGenericSuperclass方法的作用是返回直接繼承的父類(包含泛型引數

通過下面的一個例子來了解.getGenericSuperclass()

package cn.test;
public class Test{
}
class Person<T> {
}
class Student extends Person<Test> {
}

 

有兩個類PersonStudent,並且Student繼承了Person,那麼Student.class.getGenericSuperclass()返回的是cn.test.Person<cn.test.Test>

完整的BaseDaoImpl類如下:

public class BaseDaoImpl<T> implements BaseDao<T> {
    @Resource(name="hibernateTemplate")
    protected HibernateTemplate hibernateTemplate;
    
    private Class<T> clazz;
    
    
    public BaseDaoImpl(){
	//利用反射得到T的型別
	ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
	this.clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }
    
    /**
     * @Description:儲存實體
     * @param entity
     */
    public void save(T entity){
	hibernateTemplate.save(entity);
    }
    
    /**
     * @Description:修改實體
     * @param entity
     */
    public void update(T entity){
	hibernateTemplate.update(entity);
    }

    /**
     * @Description:根據id刪除實體
     * @param id
     */
    public void delete(Long id){
	Object object = hibernateTemplate.get(clazz, id);
	hibernateTemplate.delete(object);
    }
    
    
    /**
     * @Description:根據id查詢實體
     * @param id 實體id
     * @return
     */
    public T findById(long id){

	return hibernateTemplate.get(clazz , id);

    }
    
    /**
     * @Description:根據多個id獲取多個實體
     * @param ids
     * @return
     */
    public List<T> findByIds(Long[] ids){
	//防止空指標異常
	if (ids == null || ids.length ==0) {
	    //返回一個空的集合
	    return Collections.EMPTY_LIST;
	}
	
	List<T> list = new ArrayList<T>();
	for (Long id : ids) {
	    list.add(hibernateTemplate.get(clazz, id));
	}
	return list;
    }
    /**
     * @Description:查詢實體列表
     * @return
     */
    public List<T> findAll(){
	return (List<T>) hibernateTemplate.find("from "+clazz.getSimpleName());
    }
}

抽取出BaseDaoBaseDaoImpl,讓我們來修改一下我們原來的Dao

 

 

,我們的UserDao已經改造好了,別忘了將UserDaoImpl注入到spring容器中,並且當我們要建立其他Dao類的時候,也要和UserDao一樣繼承BaseDao.

對了,我們在Dao層使用了註解來將dao注入到spring容器中,那麼我們必須要讓spring容器啟動時掃描dao,而我們之前是沒有配置的,所以我們要在OA-system-service下的applicationContext-dao.xml中新增包掃描

 <!-- 配置spring容器建立時要掃描的包 -->

    <context:component-scan base-package="com.QEcode.OA.dao"></context:component-scan>  

現在applicationContext-dao.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        ">
     <!-- 載入配置檔案 -->
    <context:property-placeholder location="classpath:resource/*.properties"/>    
     <!-- 配置spring容器建立時要掃描的包 -->
    <context:component-scan base-package="com.QEcode.OA.dao"></context:component-scan>   
     <!-- 配置hibernateTemplate,用於持久層 -->
   <bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
   		<property name="sessionFactory" ref="sessionFactory"></property>
   </bean>
   <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
   		<property name="dataSource" ref="dataSource"/>
		<!-- hibernate引數設定 -->
		<property name="hibernateProperties">
			<props>
				<!-- 資料庫方言 -->
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<!-- 顯示sql語句-->
				<prop key="hibernate.show_sql">true</prop>
				<!-- 格式化SQL語句 -->
				<prop key="hibernate.format_sql">true</prop>
				<!-- create:根據對映關係生成表 -->
				<prop key="hibernate.hbm2ddl.auto">update</prop>
				<prop key="current_session_context_class">org.springframework.orm.hibernate5.SpringSessionContext</prop>
			</props>
		</property>
   		 <!-- 使用註解後,用該方式指定實體類的包 -->
   		 <property name="packagesToScan">
   		 	<array>
   		 		<value>com.QEcode.OA.pojo</value>
   		 	</array>
   		 </property>
   </bean>
	 <!-- 資料庫連線池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="maxActive" value="${jdbc.maxActive}" />
        <property name="minIdle" value="${jdbc.minIdle}" />
    </bean>   
</beans>


接下來,讓我們回到service層,修改一下方法名為findById

 

不過,既然在Dao層我們知道了要將一些重複的程式碼抽取出來,形成一個BaseDao,那麼在service,我們也要學會把重複的程式碼抽取出來,不過service層有點不同,那就是每個service的功能是一樣的,但是具體實現是不同的,所以我們只能將介面抽取出來,而不能把實現類也抽取出來.由於service介面是放在OA-system-interface,所以在OA-system-interface下新建一個介面BaseService,並且讓UserService繼承BaseService

我們將Service層常用的方法抽取出來,放到BaseService

public interface BaseService <T> {
    /**
     * @Description:查詢所有
     * @return
     */
    public List<T> findAll();
    
    /**
     * @Description:刪除
     * @param role
     */
    public void delete(Long id);

    /**
     * @Description:增加
     * @param role
     */
    public void save(T entity);

    /**
     * @Description:根據id查詢
     * @param roleId
     * @return
     */
    public T findById(Long id);

    /**
     * @Description:更新
     * @param role
     */
    public void update(T entity);

    /**
     * @Description:根據id陣列查詢
     * @param roleIds
     * @return
     */
    public List<T> findByIds(Long[] entityIds);
}


 

不要忘了在UserServiceImpl中重寫BaseService的方法.

現在我們只需要實現findById方法,其他方法等需要的時候再寫.還要,要記得將UserServiceImpl注入到spring容器中

 

構建controller

OA-system-web工程中的com.QEcode.OA.controller包下建立UserAction.

UserAction內容如下:

 

@Controller
public class UserAction {
    @Autowired
    private UserService userService;
    
    private User user;

    public String findById(){
	user = userService.findById(user.getUserId());
	return "success";
    }
    
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
    
}

 

此外還要在struts.xml中配置action

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
	<!-- 配置Struts2常量 -->
	<!-- 禁用動態方法呼叫 -->
    <constant name="struts.enable.DynamicMethodInvocation" value="false"/>
    <!-- 開啟開發模式,只在專案開發階段配置 -->
    <constant name="struts.devMode" value="true" />
    <!-- 配置訪問字尾為action -->
    <constant name="struts.action.extension" value="action"/>
    <!-- 把主題配置成simple -->
	<constant name="struts.ui.theme" value="simple" />
	
	<!-- 使用者 UserAction -->
	<package name="userAction" extends="struts-default" namespace="/user">
		<action name="userAction_*" class="userAction" method="{1}">
			<result name="{1}">/WEB-INF/jsp/userAction/{1}.jsp</result>
			<result name="success">/WEB-INF/success.jsp</result>
		</action>
	</package>
	
	
	
	
	
</struts>

我們已經構建了一個完整的功能,不過現在我們是無法使用的,因為wen工程和service工程是執行在不同的Tomcat中,web工程是無法直接呼叫service中的方法的.要想成功執行,必須使用dubbo來發布和引用服務

 

 

===============================================================================================

在寫部落格的時候,可能在專案中有一些問題沒有被發現,在我修改後,忘記寫到部落格上,所以我將這個專案上傳到github上,大家可以在github上獲取專案的程式碼

下面是github地址,大家Fork and Star

OA-Reconsitution