• 今天内容安排:
    • 1、添加定区
    • 2、定区分页查询
    • 3、hessian入门 --> 远程调用技术
    • 4、基于hessian实现定区关联客户

1、添加定区

定区可以将取派员、分区、客户信息关联到一起。
页面:WEB-INF/pages/base/decidedzone.jsp

day53_BOS项目_05-LMLPHP
第一步:使用下拉框展示取派员数据,需要修改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>

浏览器效果截图:

day53_BOS项目_05-LMLPHP
第二步:在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);
    }

浏览器效果截图:

day53_BOS项目_05-LMLPHP
第五步:使用数据表格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>

浏览器效果截图:

day53_BOS项目_05-LMLPHP
第六步:在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);
    }

浏览器效果截图:

day53_BOS项目_05-LMLPHP
第七步:为添加/修改定区窗口中的保存按钮绑定事件
    <!-- 添加/修改分区 -->
    <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名称冲突
浏览器截图:

day53_BOS项目_05-LMLPHP
代码截图:
day53_BOS项目_05-LMLPHP
即:关联分区中的复选框的field的名称叫id,定区编码的name名称也叫id,造成冲突,服务器不能够区分开他们哪个id是定区,还是哪个id是分区,如何解决呢?
答:我们应该类比于选择取派员的name的名称staff.id这样,如上图绿色框框中的那样,即我们可以把关联分区中的复选框的field的名称改为subareaid。
即:我们要在Subarea类中提供getSubareaid()方法,就相当于给Subarea类中的字段id重新起个名字,这样返回的json数据中就含有subareaid字段了。
day53_BOS项目_05-LMLPHP
Subarea.java
day53_BOS项目_05-LMLPHP
改过之后,浏览器截图:
day53_BOS项目_05-LMLPHP
第十步:创建定区管理的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文件,取消懒加载

day53_BOS项目_05-LMLPHP

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 proxy = (HelloService) factory.create(HelloService.class"http://localhost:8080/hessian_server/hessian");

        String ret = proxy.sayHello("test");
        System.out.println(ret);

        List<User> list = proxy.findAllUser();
        for (User user : list) {
            System.out.println(user.getId() + "---" + user.getName());
        }
    }
}

4、基于hessian实现定区关联客户

4.1、发布crm服务并测试访问

第一步:创建动态的web项目crm,导入hessian的jar
第二步:创建一个crm数据库和t_customer表

day53_BOS项目_05-LMLPHP
第三步:在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目录,如下图所示:

day53_BOS项目_05-LMLPHP
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 proxy = (CustomerService) factory.create(CustomerService.class, "http://localhost:8080/crm/remoting/customer");
        List<Customer> list = proxy.findnoassociationCustomers();
        for (Customer customer : list) {
            System.out.println(customer);
        }

        // 上面的演示方式:我们手动创建一个代理对象,通过代理对象去调用,然后获取服务端发布的客户数据。
        // 实际的开发方式:我们只需要在applicationContext.xml中配置一下,由spring工厂帮我们去创建代理对象,再将该代理对象注入给action去使用它即可。
        // 如何配置呢?配置相关代码如下:
        /*
        <!-- 配置远程服务的代理对象 -->
        <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 customerService;

第五步:为定区列表页面中的“关联客户”按钮绑定事件,发送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服务实现定区关联客户的业务功能

    // 采用属性驱动的方式,接收页面提交过来的参数customerIds(多个,需要用到数组进行接收)
    private Integer[] customerIds;
    public void setCustomerIds(Integer[] customerIds) {
        this.customerIds = customerIds;
    }

    /**
     * 将未关联定区的客户关联到定区上
     * @return
     */

    public String assignCustomersToDecidedZone() {
        customerService.assignCustomersToDecidedZone(customerIds, model.getId());
        return "list";
    }
10-08 15:05