面向 Java 开发人员的 db4o 指南:第 7 部分:事务、分布和安全性

通过直接在面向对象的数据库(如 db4o)中存储对象,Java™ 开发人员可以获得很多好处。如果 OODBMS 缺乏对事务的支持或不能在分布式环境中使用数据(并保证其安全性),您可能不会过多地使用它。在 面向 Java 开发人员的 db4o 指南 的最后一期中,Ted Neward 展示了如何使用 db4o 处理 3 个与 Java 企业开发密切相关的问题:事务、分布式数据管理和 Web 应用程序安全性。

 

关于本系列
信息存储和检索作为同义语伴随 RDBMS 已经有 10 余年了,但现在情况有所改变。Java 开发人员为所谓的对象关系型阻抗失配而沮丧,也不再有耐心去尝试解决这个问题。加上可行替代方案的出现,就导致了人们对对象持久性和检索的兴趣的复苏。 面向 Java 开发人员的 db4o 指南 是对开放源码数据库 db4o 的详尽介绍,db4o 可以充分利用当前的面向对象的语言、系统和理念。要实践本系列的示例,需要下载 db4o,可以从 db4o 主页 下载获得。

在本系 列中,我介绍了使用 db4o 进行面向对象数据管理的基本要素。但是还有一点没有讨论,那就是如何在 Web 应用程序中使用 OODBMS,以及与在 Swing 或 SWT 中使用 OODBMS 有何不同。可以说,我忽略的这些内容是 Java(或 .NET)开发人员不能忽略的。

一定程度上,我应该关注 OODBMS 最引人注目的功能:面向对象数据的存储、操作和检索。同样地,OODBMS 供应商也试图实现诸如事务管理和安全性之类的核心功能。这些功能与各种 RDBMS 的相应功能类似,并且具有类似的选项范围。

面向 Java 开发人员的 db4o 指南 的最后一期中,我将要讨论任何数据存储系统(面向对象的、关系的,等等)都应该拥有的 3 个特性。了解 db4o 如何支持应用程序安全性、分布和事务。

多客户端连接

迄 今为止,我为本系列编写的代码都假设数据库只有一个客户端。换句话说,只会为数据库创建一个逻辑连接,所有的交互都通过该连接来完成。对于要访问配置数据 库或逻辑存储系统的 Swing 或 SWT 应用程序,这是一个非常合理的假设。但是对于 Web 应用程序,即使是所有的存储都是在 Web 表示层完成的程序,这个假设也不太符合实际。

在 db4o 系统中,打开第二个数据库逻辑连接非常简单,即使当数据库驻留在本地磁盘上时也是如此。我只需要首先添加一个调用创建 ObjectServer ,并从 ObjectServer 获取 ObjectContainer 对象。让 ObjectServer 在端口 0 上监听,这样会告诉它以 “嵌入式” 模式运行,并且在进行下一个探察测试时不会打开(或威胁)实际的 TCP/IP 端口。


清单 1. 嵌入式连接

                    
@Test public void letsTryMultipleEmbeddedClientConnections()
    {
        ObjectServer server = Db4o.openServer("persons.data", 0);
        
        try
        {
            ObjectContainer client1 = server.openClient();
            Employee ted1 = (Employee)
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next();
            System.out.println("client1 found ted: " + ted1);
            
            ObjectContainer client2 = server.openClient();
            Employee ted2 = (Employee)
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next();
            System.out.println("client2 found ted: " + ted2);
            
            ted1.setTitle("Lord and Most High Guru");
            client1.set(ted1);
            System.out.println("set(ted1)");

            System.out.println("client1 found ted1: " +
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());

            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
                
            client1.commit();
            System.out.println("client1.commit()");

            System.out.println("client1 found ted1: " +
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());

            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
                
            client2.ext().refresh(ted2, 1);
            System.out.println("After client2.refresh()");
            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
            
            client1.close();
            client2.close();
        }
        finally
        {
            server.close();
        }
    }

 

刷新对象视图

注意,当运行该测试时,我向测试代码中添加了一些输出行。需要对发生的行为进行跟踪,因为 db4o 需要一直保持对打开对象的引用。还应该确保了解内存中对象的更新是在什么时候和什么位置 “传递” 给第二个客户端的。

适用用例:当我进行 set(ted1) 调用时,db4o 系统修改其内部状态,以了解 ted1 是脏对象并需要更新。但是,在使用 ObjectContainer 上的 commit() 方法提交显式事务之前,它不会进行实际更新。此时,数据被写入磁盘,但是跟磁盘上的数据相比,内存中 client2 的对象视图仍然是陈旧的(回顾一下探察测试的输出。在阅读本系列时,您已经 在流览器旁边的控制台窗口运行了该代码)。

改进方法很简单:client2 使用 extension 对象上的 refresh() 方法刷新它在内存中的对象图视图,extension 对象由 ext() 返回。注意激活深度是一个重要因素:当刷新对象时,您希望 db4o 深入到对象图的哪个深度?在本例中,逐步降低深度(single step down)就可以检索修改的 Employee 了,但是对于不同的情形,应该重新考虑。

一旦其对象视图被刷新,client2 就会了解到所做的更改。快速查询将会显示出公司领导的新头衔。

 




 

跨多个进程

大多数时候,多个客户端不会只具有单个进程,而是可以跨多个进程。例如,在典型的客户机-服务器风格中,将有多个客户机而不是一个 servlet 容器与单个服务器会话。在 db4o 中实现此功能的方法与 清单 1 大体相同。惟一的区别在于需要用一个非 0 的端口号来打开服务器。该端口号表示服务器监听的 TCP/IP 端口。所有基于 TCP/IP 的通信都是这样,当进行连接时,客户端必须指定主机名和端口。

 



 

安全性

自然地,当涉及一个端口时,就得考虑安全性,因为您不能允许 “所有人” 连接服务器并进行查询。在传统的 RDBMS 实现中,供应商提供了一个强大的安全模型,当数据库连接打开时,要访问数据库实例的部分或所有内容,必须向数据库发送凭证(用户名和密码)。

db4o 实现也是这样,至少在效果上是一样的。但是 db4o 数据库实例创建者设置授权安全策略的方法与常见 RDBMS 场景有很大区别,如清单 2 所示:


清单 2. 和其他人进行通信

                    
@Test public void letsTryMultipleNetworkClientConnections()
    {
        ObjectServer server = Db4o.openServer("persons.data", 2771);
        server.grantAccess("client1", "password");
        server.grantAccess("client2", "password");
            // Yes, "password" is a bad password. Don't do this in production
            // code. I get to do it only because I have a special Pedagogical
            // Code License from Sun Microsystems. And you don't. So don't do
            // this. Don't make me come over there. I'm serious. 
            // Fuggedaboutit. Never. Not ever. Capice?
        
        try
        {
            ObjectContainer client1 = 
                server.openClient("localhost", 2771, "client1", "password");
            
            ObjectContainer client2 = 
                server.openClient("localhost", 2771, "client1", "password");
                
            Employee ted1 = (Employee)
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next();
            System.out.println("client1 found ted: " + ted1);
                
            Employee ted2 = (Employee)
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next();
            System.out.println("client2 found ted: " + ted2);
            
            ted1.setTitle("Lord and Most High Guru");
            client1.set(ted1);
            System.out.println("set(ted1)");

            System.out.println("client1 found ted1: " +
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());

            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
                
            client1.commit();
            System.out.println("client1.commit()");

            System.out.println("client1 found ted1: " +
                client1.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());

            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
                
            client2.ext().refresh(ted2, 1);
            System.out.println("After client2.refresh()");
            System.out.println("client2 found ted2: " + 
                client2.get(
                    new Employee("Ted", "Neward", null, null, 0, null))
                .next());
            
            client1.close();
            client2.close();
        }
        finally
        {
            server.close();
        }
    }

 

要在 db4o 中定义访问控制,只需使用 grantAccess() 方法,它授权对整个数据库实例的访问。这种方法简化了安全设置场景,但是这样一来就无法使用最小特权原则了,因此该方法既有优点也有缺点。

最小特权原则

和许多安全思想一样,最小特权原则 的原理很简单,但是有时难于在实践中实现。该原则规定给定用户或代码体应该被授予能完成所分配任务的最少权限,不能拥有多余的特权(显然也不能缺少必要的特权)。例如,在一个 RDBMS 场景中,访问 RDBMS 的代码应该只对它访问的表拥有基本的 SELECT/INSERT/UPDATE/DELETE 权限 ,而不能超出这些权限。这样,即使代码中包含 SQL 注入攻击程序,它也不能执行注入攻击,因为它没有足够的数据库访问权限。

目前 db4o 还不支持更加粒度化级别的解决方式,其他 OODBMS 系统可能更加灵活。您应该确保只使用了最小数量的安全性凭证。如果不能限制登录需要使用的资源,那么至少可以限制系统访问原则的数量。

加密格式

分布式系统的安全性问题 — 尤其是 Fourth Fallacy of Enterprise Computing(参考 参考资料 中的 Effective Enterprise Java )— 说明,不能认为只有监听网络通信量的人或代码才是可信的。这意味着您必须确保在网络中传输的数据不是明文和二进制形式(如果使用的二进制形式的格式众所周知,那么它和明文形式是相同的)。

关心安全性的开发人员也会关心存储在 db4o 文件中的数据,因为文件 “仅仅是文件”,因此打开文件和阅读其中的内容会将文件暴露给攻击者(关系数据库也存在着这类问题)。

解 决方案是对文件进行加密,这在 db4o 中比较简单。对于大多数场景,db4o 的默认加密模式 eXtended Tiny Encryption Algorithm (XTEA) 对暴露给攻击者的数据进行模糊化。对于其他实例,db4o 提供了一个定制加密 “技巧”,支持使用第三方加密提供程序(不要定义自己的加密格式,除非您可以编写出论文挑战既有标准,并受到全世界密码学家和数学家的关注,而且可以在主 流安全会议上进行辩护。如果达到了这些要求,您可以 考虑使用它,毕竟,没有被发现并不意味着不存在)。

保护数据

保护有线形式(wire-format)的数据传输比较困难,因为 db4o 6.3 及以前的版本都没有提供工具跨安全的传输线(比如通过 SecureSocket 传输的 SSL)进行通信。这意味着任何敏感数据都必须以加密的方式传输,这暗示着对象本身应该包含某种形式的加密(如果 db4o 可以直接实现这一功能就太好了;在撰写本文的时候,db4o 6.4 版已经计划支持将 SocketFactory 传递到 openServer 调用,因此您可以使用 SecureSocket 连接取代完全开放的 Socket 连接)。

注意可以使用定制编组程序 通过 “横切(cross-cutting)” 方式进行传递以保护数据。顾名思义,这使您能够控制数据的打包(和解包)方式,以在线路上进行传输。这种方法与通过 Externalizable 接口定制串行化非常相似:编写一个实现 ObjectMarshaller 接口的类,实现 readFields()writeFields() 方法,然后告诉 db4o 系统对特定类的对象使用定制的编组程序,具体方法是在目标类的 ObjectClass 上调用 marshallWith() 。完整的代码如下:

Db4o.configure().objectClass(Item.class).marshallWith(customMarshaller);

 

这样做不会保护整个线路 — 攻击者仍然可以查看保存的对象类型 — 但是它会在网络上进行节点到节点传输时阻止数据被别人查看。

当 db4o 选项 “嵌入式和客户机/服务器” 不能满足需要时,比如将数据存储到特定的文件格式或非传统的数据存储资源中,db4o 库允许创建 IoAdaptor 的一个子类,该类是 db4o 在存储对象时写入数据的关键抽象。这让存储具有一定程度的灵活性,而大多数 RDBMS 系统都不具有这种灵活性。

 




 

结束语

关 于 OODBMs 和 db4o,还有大量内容值得讨论和研究,但是我已经完成了预定目标并决定暂时结束本系列。我相信我展示了一个关于 db4o 和面向对象数据管理的良好示例,并从 Java 开发人员的角度介绍了 RDBMS 不同于 OODBMS 的特性。我展示了如何使用 db4o 轻松地跟踪关联(track association);db4o 如何获得作为数据库优秀概念的继承性;db4o 如何使用定义对象的原生编程语言简化对象检索。

我也讨论了 OODBMS 的一些不足,以及 db4o 与 RDBMS 都存在的缺陷,比如在处理客户机-服务器网络中的 “往返传输” 时出现的性能问题。

只 要跟随本系列的示例并试用了其中的代码,就会获得使用任何 OODBMS 系统(不只是 db4o)的必备技巧。您可以尝试将所学知识应用到 Cache' 或 Versant 上。大多数 OODBMS 都遵循相同的基本编码约定和惯用表达式,而且实际上 db4o 对原生查询的支持已逐渐成为一种标准,使该特性成为所有 OODBMS 的一部分。

希望您能在本系列中找到所需的内容,并已准备好在您自己的项目中使用 OODBMS。这将是一种轻松的体验,不用担心关系模式和所有相关的内容。因此,请放松心情:试验、实现、在实践中体验乐趣,不要忘了来信告诉我您的经验(或者给我发送电子邮件)。

你可能感兴趣的:(java,sql,应用服务器,server,网络应用,嵌入式)