- 今天内容安排:
- 1、添加定区
- 2、定区分页查询
- 3、hessian入门 --> 远程调用技术
- 4、基于hessian实现定区关联客户
1、添加定区
定区可以将取派员、分区、客户信息关联到一起。
页面:WEB-INF/pages/base/decidedzone.jsp
第一步:使用下拉框展示取派员数据,需要修改combobox的URL地址,发送请求
<tr>
<td>选择取派员</td>
<td>
<input class="easyui-combobox" name="staff.id"
data-options="valueField:'id',textField:'name',
url:'${pageContext.request.contextPath}/staffAction_listajax.action'" />
</td>
</tr>
浏览器效果截图:
第二步:在StaffAction中提供listajax()方法,查询没有作废的取派员,并返回json数据
/**
* 查询没有作废的取派员,并返回json数据
* @return
* @throws IOException
*/
public String listajax() throws IOException {
List<Staff> list = staffService.findListNoDelete();
String[] excludes = new String[] {"decidedzones"}; // 我们只需要Staff的id和name即可,其余的都不需要,本例中我们只排除关联的分区对象
this.writeList2Json(list, excludes);
return "none";
}
第三步:在StaffService中提供方法查询没有作废的取派员
/**
* 查询没有作废的取派员,即查询条件:deltag值为“0”
*/
public List<Staff> findListNoDelete() {
// 创建离线条件查询对象
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Staff.class);
// 向离线条件查询对象中封装条件
detachedCriteria.add(Restrictions.eq("deltag", "0"));
return staffDao.findByCriteria(detachedCriteria);
}
第四步:在IBaseDao中提供通用的条件查询方法
IBaseDao.java
// 条件查询(不带分页)
public List<T> findByCriteria(DetachedCriteria detachedCriteria);
BaseDaoImpl.java
/**
* 通用条件查询(不带分页)
*/
public List<T> findByCriteria(DetachedCriteria detachedCriteria) {
return this.getHibernateTemplate().findByCriteria(detachedCriteria);
}
浏览器效果截图:
第五步:使用数据表格datagrid展示未关联到定区的分区数据
decidedzone.jsp
<td valign="top">关联分区</td>
<td>
<table id="subareaGrid" class="easyui-datagrid" border="false" style="300px;height:300px"
data-options="url:'${pageContext.request.contextPath}/subareaAction_listajax.action',
fitColumns:true,singleSelect:false">
<thead>
<tr>
<th data-options="field:'id',30,checkbox:true">编号</th>
<th data-options="field:'addresskey',150">关键字</th>
<th data-options="field:'position',200,align:'right'">位置</th>
</tr>
</thead>
</table>
</td>
浏览器效果截图:
第六步:在SubareaAction中提供listajax()方法,查询未关联到定区的分区数据,并返回json数据
/**
* 查询未关联到定区的分区数据,并返回json数据
* @return
* @throws IOException
*/
public String listajax() throws IOException {
List<Subarea> list = subareaService.findListNotAssociation();
String[] excludes = new String[] {"region", "decidedzone"}; // 本例中我们只排除关联的区域对象和定区对象
this.writeList2Json(list, excludes);
return "none";
}
Service层代码:
/**
* 查询未关联到定区的分区数据,即查询条件:decidedzone值为“null”
*/
public List<Subarea> findListNotAssociation() {
// 创建离线条件查询对象
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Subarea.class);
// 向离线条件查询对象中封装条件
// detachedCriteria.add(Restrictions.eq("decidedzone", "null")); // 基本类型的属性使用eq()和ne()
detachedCriteria.add(Restrictions.isNull("decidedzone")); // 引用类型的属性使用isNull()和isNotNull()
return subareaDao.findByCriteria(detachedCriteria);
}
浏览器效果截图:
第七步:为添加/修改定区窗口中的
保存按钮
绑定事件
<!-- 添加/修改分区 -->
<div style="height:31px;overflow:hidden;" split="false" border="false" >
<div class="datagrid-toolbar">
<a id="save" icon="icon-save" href="#" class="easyui-linkbutton" plain="true" >保存</a>
<script type="text/javascript">
$(function() {
$("#save").click(function() {
var v = $("#addDecidedzoneForm").form("validate");
if (v) {
$("#addDecidedzoneForm").submit(); // 页面会刷新
// $("#addDecidedzoneForm").form("submit"); // 页面不会刷新
}
});
});
</script>
</div>
</div>
第八步:提交上面的添加定区的表单,发现id名称冲突
浏览器截图:
代码截图:
即:
关联分区
中的复选框的field的名称叫id,定区编码
的name名称也叫id,造成冲突,服务器不能够区分开他们哪个id是定区
,还是哪个id是分区
,如何解决呢?答:我们应该类比于
选择取派员
的name的名称staff.id这样,如上图绿色框框中的那样,即我们可以把关联分区
中的复选框的field的名称改为subareaid。即:我们要在Subarea类中提供getSubareaid()方法,就相当于给Subarea类中的字段id重新起个名字,这样返回的json数据中就含有subareaid字段了。
Subarea.java
改过之后,浏览器截图:
第十步:创建定区管理的Action,提供add()方法保存定区,提供subareaid数组属性接收多个分区的subareaid
package com.itheima.bos.web.action;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import com.itheima.bos.domain.Decidedzone;
import com.itheima.bos.web.action.base.BaseAction;
/**
* 定区设置
* @author Bruce
*
*/
@Controller
@Scope("prototype")
public class DecidedzoneAction extends BaseAction<Decidedzone> {
// 采用属性驱动的方式,接收页面提交过来的参数subareaid(多个,需要用到数组进行接收)
private String[] subareaid;
public void setSubareaid(String[] subareaid) {
this.subareaid = subareaid;
}
/**
* 添加定区
* @return
*/
public String add() {
decidedzoneService.save(model, subareaid);
return "list";
}
}
Service层代码:
package com.itheima.bos.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.itheima.bos.dao.IDecidedzoneDao;
import com.itheima.bos.dao.ISubareaDao;
import com.itheima.bos.domain.Decidedzone;
import com.itheima.bos.domain.Subarea;
import com.itheima.bos.service.IDecidedzoneService;
@Service
@Transactional
public class DecidedzoneServiceImpl implements IDecidedzoneService {
// 注入定区dao
@Autowired
private IDecidedzoneDao decidedzoneDao;
// 注入分区dao
@Autowired
private ISubareaDao subareaDao;
/**
* 添加定区,并修改分区的外键
*/
public void save(Decidedzone model, String[] subareaid) {
// 先保存定区表(一个定区含有多个分区)
decidedzoneDao.save(model);
// 再修改分区表的外键,java代码如何体现呢?答:让这两个对象关联下即可。谁关联谁都行。
// 但是在关联之前,我们应该有意识去检查下通过反转引擎自动生成出来的Hibernate配置文件中,谁放弃了维护外键的能力。
// 一般而言:是“一”的一方放弃。所以需要由“多”的一方来维护外键关系。
for (String sid : subareaid) {
// 根据分区id把分区对象查询出来,再让分区对象去关联定区对象model
Subarea subarea = subareaDao.findById(sid); // 持久化对象
// 分区对象 关联 定区对象 --> 多方关联一方
subarea.setDecidedzone(model); // 关联完之后,会自动更新数据库,根据快照去对比,看看我们取出来的持久化对象是否跟快照长得不一样,若不一样,就刷新缓存。
// 从效率的角度讲:我们应该拼接一个HQL语句去更新Subarea,而不是去使用Hibernate框架通过关联的方式更新
// HQL:update Subarea set decidedzone=? where id=? -->
// SQL:update bc_subarea set decidedzone_id=? where id=?
}
}
}
第十一步:配置struts.xml
<!-- 定区管理:配置decidedzoneAction-->
<action name="decidedzoneAction_*" class="decidedzoneAction" method="{1}">
<result name="list">/WEB-INF/pages/base/decidedzone.jsp</result>
</action>
2、定区分页查询
第一步:decidedzone.jsp页面修改datagrid的URL
// 定区标准数据表格
$('#grid').datagrid( {
iconCls : 'icon-forward',
fit : true,
border : true,
rownumbers : true,
striped : true,
pageList: [30,50,100],
pagination : true,
toolbar : toolbar,
url : "${pageContext.request.contextPath}/decidedzoneAction_pageQuery.action",
idField : 'id',
columns : columns,
onDblClickRow : doDblClickRow
});
第二步:在DecidedzoneAction中提供分页查询方法
/**
* 定区的分页查询
* @return
* @throws IOException
*/
public String pageQuery() throws IOException {
decidedzoneService.pageQuery(pageBean);
String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "subareas", "decidedzones"};
this.writePageBean2Json(pageBean, excludes);
return "none";
}
第三步:修改Decidedzone.hbm.xml文件,取消懒加载
3、hessian入门 --> 远程调用技术
-
Hessian是一个轻量级的 remoting on http 工具,使用简单的方法提供了
RMI(Remote Method Invocation 远程方法调用)
的功能。相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议(Remote Procedure Call Protocol 远程过程调用协议)
,因为采用的是二进制协议,所以它很适合于发送二进制数据
。 -
常见的远程调用的技术:
- 1、webservice(CXF框架、axis框架),偏传统,基于soap(简单对象访问协议)协议,传输的是xml格式的数据,数据冗余比较大,传输效率低。现在也支持json。
- 2、httpclient --> 电商项目:淘淘商城,大量使用
- 3、hessian --> http协议、传输的是二进制数据,冗余较少,传输效率较高。
- 4、dubbo --> 阿里巴巴
-
Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。自开源后,已有不少非阿里系公司在使用Dubbo。
-
Tengine是由淘宝网发起的Web服务器项目。它在
Nginx
的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。 -
hessian有两种发布服务的方式:
- 1、使用hessian框架自己提供的HessianServlet发布:com.caucho.hessian.server.HessianServlet
- 2、和spring整合发布服务:org.springframework.web.servlet.DispatcherServlet
-
hessian入门案例
服务端开发:
第一步:创建一个java web项目,并导入hessian的jar包
第二步:创建一个接口
public interface HelloService {
public String sayHello(String name);
public List<User> findAllUser();
}
第三步:提供上面接口的实现类
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
System.out.println("sayHello方法被调用了");
return "hello " + name;
}
public List<User> findAllUser() {
List<User> list = new ArrayList<User>();
list.add(new User(1, "小艺"));
list.add(new User(2, "小军"));
return list;
}
}
第四步:在web.xml中配置服务
<servlet>
<servlet-name>hessian</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-class</param-name>
<param-value>com.itheima.HelloServiceImpl</param-value>
</init-param>
<init-param>
<param-name>home-api</param-name>
<param-value>com.itheima.HelloService</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hessian</servlet-name>
<url-pattern>/hessian</url-pattern>
</servlet-mapping>
客户端开发:
第一步:创建一个客户端项目(普通java项目即可),并导入hessian的jar包
第二步:创建一个接口(和服务端接口对应)
public interface HelloService {
public String sayHello(String name);
public List<User> findAllUser();
}
第三步:使用hessian提供的方式创建远程代理对象调用服务
public class Test {
public static void main(String[] args) throws MalformedURLException {
// 通过hessian提供的工厂类创建一个代理对象,通过这个代理对象可以远程调用服务
HessianProxyFactory factory = new HessianProxyFactory();
HelloService remoteProxy= (HelloService) factory.create(HelloService.class, "http://localhost:8080/hessian_server/hessian");
String ret = remoteProxy.sayHello("test");
System.out.println(ret);
List<User> list = remoteProxy.findAllUser();
for (User user : list) {
System.out.println(user.getId() + "---" + user.getName());
}
}
}
4、基于hessian实现定区关联客户
4.1、发布crm服务并测试访问
第一步:创建动态的web项目crm,导入hessian的jar
第二步:创建一个crm数据库和t_customer表
第三步:在web.xml中配置spring的DispatcherServlet
<!-- hessian发布服务的方式:和spring整合发布服务:org.springframework.web.servlet.DispatcherServlet -->
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
第四步:提供接口CustomerService和Customer类、Customer.hbm.xml映射文件
CustomerService.java
package cn.itcast.crm.service;
import java.util.List;
import cn.itcast.crm.domain.Customer;
// 客户服务接口
public interface CustomerService {
// 查询未关联定区客户
public List<Customer> findnoassociationCustomers();
// 查询已经关联指定定区的客户
public List<Customer> findhasassociationCustomers(String decidedZoneId);
// 将未关联定区客户关联到定区上
public void assignCustomersToDecidedZone(Integer[] customerIds, String decidedZoneId);
}
第五步:为上面的CustomerService接口提供实现类
package cn.itcast.crm.service.impl;
import java.util.List;
import org.hibernate.Session;
import cn.itcast.crm.domain.Customer;
import cn.itcast.crm.service.CustomerService;
import cn.itcast.crm.utils.HibernateUtils;
public class CustomerServiceImpl implements CustomerService {
public List<Customer> findnoassociationCustomers() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
String hql = "from Customer where decidedzone_id is null";
List<Customer> customers = session.createQuery(hql).list();
session.getTransaction().commit();
session.close();
return customers;
}
public List<Customer> findhasassociationCustomers(String decidedZoneId) {
Session session = HibernateUtils.openSession();
session.beginTransaction();
String hql = "from Customer where decidedzone_id=?";
List<Customer> customers = session.createQuery(hql).setParameter(0, decidedZoneId).list();
session.getTransaction().commit();
session.close();
return customers;
}
public void assignCustomersToDecidedZone(Integer[] customerIds, String decidedZoneId) {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 取消定区所有关联客户
String hql2 = "update Customer set decidedzone_id=null where decidedzone_id=?";
session.createQuery(hql2).setParameter(0, decidedZoneId).executeUpdate();
// 进行关联
String hql = "update Customer set decidedzone_id=? where id=?";
if (customerIds != null) {
for (Integer id : customerIds) {
session.createQuery(hql).setParameter(0, decidedZoneId).setParameter(1, id).executeUpdate();
}
}
session.getTransaction().commit();
session.close();
}
}
第六步:在WEB-INF目录提供spring的配置文件remoting-servlet.xml
<!-- 通过配置的方式对外发布服务 -->
<!-- 业务接口实现类 -->
<bean id="customerService" class="cn.itcast.crm.service.impl.CustomerServiceImpl" />
<!-- 注册hessian服务 -->
<bean id="/customer" class="org.springframework.remoting.caucho.HessianServiceExporter">
<!-- 业务接口实现类 -->
<property name="service" ref="customerService" />
<!-- 业务接口 -->
<property name="serviceInterface" value="cn.itcast.crm.service.CustomerService" />
</bean>
第七步:发布crm服务
第八步:在hessian_client客户端调用crm服务获得客户数据
注意:拷贝接口CustomerService代码文件放到客户端中,同时必须在hessian_client客户端新建和crm服务端一样的实体Bean目录,如下图所示:
hessian_client客户端调用代码如下:
package com.itheima;
import java.net.MalformedURLException;
import java.util.List;
import org.junit.Test;
import com.caucho.hessian.client.HessianProxyFactory;
import cn.itcast.crm.domain.Customer;
public class TestService {
@Test
public void test1() throws MalformedURLException {
// 通过hessian提供的工厂类创建一个代理对象,通过这个代理对象可以远程调用服务
HessianProxyFactory factory = new HessianProxyFactory();
CustomerService remoteProxy= (CustomerService) factory.create(CustomerService.class, "http://localhost:8080/crm/remoting/customer");
List<Customer> list = remoteProxy.findnoassociationCustomers();
for (Customer customer : list) {
System.out.println(customer);
}
// 上面的演示方式:我们手动创建一个代理对象,通过代理对象去调用,然后获取服务端发布的客户数据。
// 实际的开发方式:我们只需要在applicationContext.xml中配置一下,由spring工厂帮我们去创建代理对象,再将该代理对象注入给action、service,他们再去使用该代理对象即可。
// 如何配置呢?配置相关代码如下:
/*
<!-- 配置远程服务的代理对象 -->
<bean id="customerService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceInterface" value="cn.itcast.bos.service.ICustomerService"/>
<property name="serviceUrl" value="http://localhost:8080/crm/remoting/customer"/>
</bean>
*/
}
}
客户端控制台输出:
cn.itcast.crm.domain.Customer@59b746f
cn.itcast.crm.domain.Customer@20f92649
cn.itcast.crm.domain.Customer@45409388
cn.itcast.crm.domain.Customer@1295e93d
cn.itcast.crm.domain.Customer@3003ad53
cn.itcast.crm.domain.Customer@41683cc5
cn.itcast.crm.domain.Customer@226dcb0f
cn.itcast.crm.domain.Customer@562e5771
服务端控制台输出:
Hibernate:
select
customer0_.id as id0_,
customer0_.name as name0_,
customer0_.station as station0_,
customer0_.telephone as telephone0_,
customer0_.address as address0_,
customer0_.decidedzone_id as decidedz6_0_
from
t_customer customer0_
where
customer0_.decidedzone_id is null
4.2、在bos项目中调用crm服务获得客户数据
第一步:在bos项目中导入hessian的jar包
第二步:从crm项目中复制CustomerService接口和Customer类到bos项目中
第三步:在spring配置文件中配置一个远程服务代理对象,调用crm服务
<!-- 配置远程服务的代理对象 -->
<bean id="customerService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceInterface" value="com.itheima.bos.crm.CustomerService"/>
<property name="serviceUrl" value="http://localhost:8080/crm/remoting/customer"/>
</bean>
第四步:将上面的代理对象通过注解方式注入到BaseAction中
@Autowired
protected CustomerService remoteProxy;
第五步:为定区列表页面中的“关联客户”按钮绑定事件,发送2次ajax请求访问DecidedzoneAction,在DecidedzoneAction中调用hessian代理对象,通过代理对象可以远程访问crm获取客户数据,获取数据后进行解析后,填充至左右下拉框中去
// 设置全局变量:存储选中一个定区时的 定区id
var decidedzoneid;
// 关联客户窗口
function doAssociations(){
// 在打开关联客户窗口之前判断是否选中了一个定区,即获得选中的行
var rows = $("#grid").datagrid("getSelections");
if (rows.length == 1) {
// 打开窗口
$("#customerWindow").window('open');
// 清空窗口中的下拉框内容
$("#noassociationSelect").empty();
$("#associationSelect").empty();
// 发送ajax请求获取未关联到定区的客户(左侧下拉框)
var url1 = "${pageContext.request.contextPath}/decidedzoneAction_findnoassociationCustomers.action";
$.post(url1, {}, function(data) {
// alert(data); // json数据
// 解析json数据,填充至左侧下拉框中去
for (var i = 0; i < data.length; i++) {
var id = data[i].id;
var name = data[i].name;
$("#noassociationSelect").append("<option value='" + id + "'>" + name + "</option>");
}
}, 'json');
decidedzoneid = rows[0].id;
// 发送ajax请求获取关联到当前选中定区的客户(右侧下拉框)
var url2 = "${pageContext.request.contextPath}/decidedzoneAction_findhasassociationCustomers.action";
$.post(url2, {"id":decidedzoneid}, function(data) {
// alert(data); // json数据
// 解析json数据,填充至右侧下拉框中去
for (var i = 0; i < data.length; i++) {
var id = data[i].id;
var name = data[i].name;
$("#associationSelect").append("<option value='" + id + "'>" + name + "</option>");
}
}, 'json');
} else {
// 没有选中或选中多个,提示信息
$.messager.alert("提示信息","请选择一条定区记录进行操作","warning");
}
}
第六步:为“左右移动按钮”绑定事件
<td>
<input type="button" value="》》" id="toRight"><br/>
<input type="button" value="《《" id="toLeft">
<script type="text/javascript">
$(function() {
// 为右移动按钮绑定事件
$("#toRight").click(function() {
$("#associationSelect").append($("#noassociationSelect option:selected"));
$("#associationSelect>option").removeAttr("selected"); // 移除它们默认的选中状态
});
// 为右移动按钮绑定事件
$("#toLeft").click(function() {
$("#noassociationSelect").append($("#associationSelect option:selected"));
$("#noassociationSelect>option").removeAttr("selected"); // 移除它们默认的选中状态
});
});
</script>
</td>
第七步:为关联客户窗口中的“关联客户”按钮绑定事件
<script type="text/javascript">
$(function() {
// 为关联客户按钮绑定事件
$("#associationBtn").click(function() {
// 在提交表单之前,选中右侧下拉框中所有的选项
$("#associationSelect option").attr("selected", "selected"); // attr(key, val) 给一个指定属性名设置值
// 在提交表单之前设置隐藏域的值(定区id)
$("input[name=id]").val(decidedzoneid);
// 提交表单
$("#customerForm").submit();
});
});
</script>
第八步:在定区Action中接收提交的参数,调用crm服务实现定区关联客户的业务功能
/**
* 调用代理对象,查询未关联到定区的客户
* @return
* @throws IOException
*/
public String findnoassociationCustomers() throws IOException {
List<Customer> list = remoteProxy.findnoassociationCustomers();
String[] excludes = new String[] {"station", "address"};
this.writeList2Json(list, excludes);
return "none";
}
/**
* 调用代理对象,查询已经关联到指定定区的客户
* @return
* @throws IOException
*/
public String findhasassociationCustomers() throws IOException {
List<Customer> list = remoteProxy.findhasassociationCustomers(model.getId());
String[] excludes = new String[] {"station", "address"};
this.writeList2Json(list, excludes);
return "none";
}
// 采用属性驱动的方式,接收页面提交过来的参数customerIds(多个,需要用到数组进行接收)
private Integer[] customerIds;
public void setCustomerIds(Integer[] customerIds) {
this.customerIds = customerIds;
}
/**
* 调用代理对象,将未关联定区的客户关联到定区上
* @return
*/
public String assignCustomersToDecidedZone() {
remoteProxy.assignCustomersToDecidedZone(customerIds, model.getId());
return "list";
}