- 今天内容安排:
- 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="width:300px;height:300px" data-options="url:'${pageContext.request.contextPath}/subareaAction_listajax.action', fitColumns:true,singleSelect:false"> <thead> <tr> <th data-options="field:'id',width:30,checkbox:true">编号</th> <th data-options="field:'addresskey',width:150">关键字</th> <th data-options="field:'position',width: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"; }