1.场景
在一个HR(人力资源)应用项目中客户提出,当选择一个部门或时分公司的时候,要把这个部门或者分公司下的所有员工都显示出来,而且不使用分页,方便他们进行业务处理。在显示全部员工的时候, 只需要显示名称即可,但是也需要提供如下功能:在必要的时候可以选择并查看某位员工的详细信息(user表中的所有字段)。
实现起来也非常简单,只需要查询对应deptid下的user表就可以了。但是实现看起来简单,功能也正确,但是蕴涵了一个比较大的问题。那就是,一次性访问的数据条数过多,而且每条描述的数据量又很大的话,将会消耗较多的内存。而且从用户的角度来说,有很大的随机性。客户有可能访问每一条数据,也有可能一条都不访问。也就是说,一次性访问很多条数据,消耗了大量内存,但是很可能是浪费掉了,客户根本就不会去访问那么多数据,对于每条数据,客户只需要查看姓名而已。
那么该怎么实现,才能既把用户数据的姓名显示出来,而又能节省内存空间?当然还要实现在客户想要看到更多数据的时候,能够正确访问到数据呢?这就是我们接下来要讲的代理模式。
2.解决方案
2.1.代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。
2.2.代理模式的结构和说明
- Proxy:代理对象,通常具有如下功能。实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用(代理对象中有具体对象的成员变量),可以在需要的时候调用具体的目标对象。可以控制对具体对象的访问,并可以负责创建和删除它。
- Subject:具体接口,定义代理和具体对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。
- RealSubject:具体的目标对象,真正实现目标接口要求的功能。
在运行时刻一种可能 的代理结构的对象图如下图所示
也就是在客户的时候见到的时候Subject接口。接口调用代理Proxy,然后代理再调用具体对象RealSubject。
3.使用代理重写实例
(1)用户数据对象接口,就是对用户数据对象属性操作的getter/setter方法。
UserModelApi.java
- public interface UserModelApi {
- public String getUserId() ;
- public void setUserId(String userId);
- public String getUserName() ;
- public void setUserName(String userName) ;
- public String getDeptId() ;
- public void setDeptId(String deptId);
- public String getSex() ;
- public void setSex(String sex) ;
- }
(3)接下来定义代理对象
Proxy.java
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- /**
- * 代理对象,代理用户数据对象
- */
- public class Proxy implements UserModelApi {
- /**
- * 持有被代理的具体的目标对象
- */
- private UserModel realSubject = null;
- private boolean loaded = false;//标志位,表示是否被加载过
- /**
- * 构造方法,传入被代理的具体的目标对象
- * @param realSubject 被代理的具体的目标对象
- */
- public Proxy(UserModel realSubject) {
- this.realSubject = realSubject;
- }
- // 代理对象中只保存userId和userName属性,其他属性要从实体对象中获取
- @Override
- public String getUserId() {
- // TODO Auto-generated method stub
- return realSubject.getUserId();
- }
- @Override
- public void setUserId(String userId) {
- // TODO Auto-generated method stub
- realSubject.setUserId(userId);
- }
- @Override
- public String getUserName() {
- // TODO Auto-generated method stub
- return realSubject.getUserName();
- }
- @Override
- public void setUserName(String userName) {
- // TODO Auto-generated method stub
- realSubject.setUserName(userName);
- }
- @Override
- public String getDeptId() {
- // TODO Auto-generated method stub
- // 获取的非proxy对象中拥有的属性,则需要再次查询数据库获取
- if (!loaded)// 判断数据是否加载过。
- {
- reload();
- this.loaded = true;
- }
- return realSubject.getDeptId();
- }
- @Override
- public void setDeptId(String deptId) {
- // TODO Auto-generated method stub
- realSubject.setDeptId(deptId);
- }
- @Override
- public String getSex() {
- // TODO Auto-generated method stub
- if (!loaded) {
- reload();
- this.loaded = true;
- }
- return realSubject.getSex();
- }
- @Override
- public void setSex(String sex) {
- // TODO Auto-generated method stub
- realSubject.setSex(sex);
- }
- /**
- * 重新查询数据库以获取完整的用户数据
- */
- private void reload() {
- System.out.println("重新查询数据库获取完整的用户数据,userId=="
- + realSubject.getUserId());
- Connection conn = null;
- try {
- conn = DBConnection.getInstance().getConnection();
- String sql = "select * from tbl_user where userid=?";
- PreparedStatement ps = conn.prepareStatement(sql);
- ps.setString(1, realSubject.getUserId());
- ResultSet rs = ps.executeQuery();
- while (rs.next()) {
- //只需要重新获取除了userId和name外的数据
- realSubject.setDeptId(rs.getString("deptid"));
- realSubject.setSex(rs.getString("sex"));
- }
- rs.close();
- ps.close();
- } catch (Exception err) {
- err.printStackTrace();
- } finally {
- try {
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- @Override
- public String toString(){
- return "userId="+getUserId()+",name="+getUserName()
- +",depId="+getDeptId()+",sex="+getSex()+"\n";
- }
- }
UserModel.java
- /**
- * 描述用户数据的对象
- */
- public class UserModel implements UserModelApi {
- private String userId;//用户编号
- private String userName;//用户姓名
- private String deptId;//部门编号
- private String sex;//性别
- public String getUserId() {
- return userId;
- }
- public void setUserId(String userId) {
- this.userId = userId;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public String getDeptId() {
- return deptId;
- }
- public void setDeptId(String deptId) {
- this.deptId = deptId;
- }
- public String getSex() {
- return sex;
- }
- public void setSex(String sex) {
- this.sex = sex;
- }
- //重写Object类的toString()方法
- @Override
- public String toString(){
- return "userId="+userId+",name="+userName+",depId="+deptId+",sex="+sex+"\n";
- }
- }
(4)定义数据库连接对象
DBConnection.java
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.SQLException;
- public class DBConnection {
- //数据库连接单例,饿汉式
- private static DBConnection instance=new DBConnection();
- //静态代码块,用户注册数据库连接驱动
- static {
- try {
- Class.forName("com.mysql.jdbc.Driver");
- } catch (ClassNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- //获取数据库连接实例。
- public static DBConnection getInstance() {
- return instance;
- }
- /**
- * 获取数据库连接,可以直接通过
- * DBConnection.getInstance().getConnection();
- * 来获取数据库连接
- */
- public Connection getConnection() throws SQLException
- {
- return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
- }
- }
UserManager.java
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.util.ArrayList;
- import java.util.Collection;
- /**
- * 实现示例要求的功能
- */
- public class UserManager {
- /**
- * 根据部门编号来获取该部门下的所有人员
- * @param depId 部门编号
- * @return 该部门下的所有人员
- */
- public Collection<UserModelApi> getUserById(String deptId) throws Exception {
- Collection<UserModelApi> col = new ArrayList<UserModelApi>();
- Connection conn = null;
- try {
- conn = DBConnection.getInstance().getConnection();
- //只需要查询userId和name两个值就可以了
- String sql = "select u.userid,u.name from tbl_user u ,tbl_dept d where u.deptid=d.deptid and u.deptid LIKE ?";
- PreparedStatement ps = conn.prepareStatement(sql);
- ps.setString(1, deptId+"%");
- ResultSet rs = ps.executeQuery();
- while (rs.next()) {
- //这里是创建的代理对象,而不是直接创建UserModel的对象
- Proxy proxy=new Proxy(new UserModel());
- //只是设置userId和name两个值就可以了
- proxy.setUserId(rs.getString("userid"));
- proxy.setUserName(rs.getString("name"));
- col.add(proxy);
- }
- rs.close();
- ps.close();
- } finally {
- conn.close();
- }
- return col;
- }
- }
Client.java
- import java.util.Collection;
- public class Client {
- /**
- * @param args
- * @throws Exception
- */
- public static void main(String[] args) throws Exception {
- // TODO Auto-generated method stub
- UserManager userManager=new UserManager();
- Collection<UserModelApi> col=userManager.getUserById("0101");
- //如果只是显示用户名称,那么不需要重新查询数据库
- for(UserModelApi umApi : col){
- System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getUserName());
- }
- //如果访问非用户编号和用户姓名外的属性,那就会重新查询数据库
- for(UserModelApi umApi : col){
- System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getUserName()+",所属部门:="+umApi.getDeptId());
- }
- }
- }
(7)运行结果:
- 用户编号:=user0001,用户姓名:=张三1
- 用户编号:=user0002,用户姓名:=张三2
- 用户编号:=user0003,用户姓名:=张三3
- 重新查询数据库获取完整的用户数据,userId==user0001
- 用户编号:=user0001,用户姓名:=张三1,所属部门:=010101
- 重新查询数据库获取完整的用户数据,userId==user0002
- 用户编号:=user0002,用户姓名:=张三2,所属部门:=010101
- 重新查询数据库获取完整的用户数据,userId==user0003
- 用户编号:=user0003,用户姓名:=张三3,所属部门:=010102
如果只是访问用户编号和用户信命的数据,是不需要重新查询数据库的。只有当访问到这两个数据以外的数据的时候,才需要重新查询数据库以获得完整的数据。
(9)1+N次查询
上述实例存在一个潜在的问题,那就是如果客户对每条用户数据都要求查看详细数据的话,那么总的查询数据库的次数会是1+N次。
第一次查询,获得了N条数据的用户编号与姓名,然后展示给客户看。如果这个时候,客户对每条数据都点击查看详细信息的话,那么每一条数据都需要重新查询数据库,那么最后总的查询数据库的次数就是1+N次了。