Путеводитель по db4o для Java-разработчика: За рамками простых объектов

Создание, редактирование и удаление структурированных объектов в db4o

До этого момента создание и манипулирование объектами в db4o выглядело довольно просто, может быть даже слишком просто. В этой статье Тед Ньюворд, горячий сторонник db4o, расскажет о работе со структурированными объектами, т.е. объектами, содержащими ссылки на другие объекты. В статье будут освещены такие вопросы, как опасность бесконечной рекурсии, каскадирование и ссылочная целостность.

Тед Ньюворд, директор, Neward & Associates

Тед Ньюворд (Ted Neward) является директором компании “Neward & Associates”. Он занимается консультированием, преподаванием и презентациями продуктов на основе Java, .NET, XML-сервисов и других платформ. В настоящее время он живет недалеко от Сиэттла, штат Вашингтон.



25.12.2008

В предыдущих статьях серии "Путеводитель по db4o для Java-разработчика" рассказывалось о сохранении объектов в db4o, при котором не требуется создание файлов отображения. Отсутствие необходимости явного описания объектно-реляционного отображения – это одно из преимуществ объектных баз данных (а возможно, и смысл их существования). Однако объектные модели, которые я использовал до настоящего момента для демонстрации возможностей ООСУБД, были значительно проще, чем встречающиеся на практике. В большинстве корпоративных приложений используются модели, состоящие из сложных (структурированных) объектов. Именно они станут главной темой данной статьи.

Отличительной чертой структурированных объектов объектов является то, что они содержат ссылки на другие объекты. Хотя в db4o поддерживается набор стандартных операций CRUD над структурированными объектами, не стоит думать, что работа с ними будет столь же легкой, как и с простыми объектами. В этой статье я расскажу о некоторых моментах, представляющих определенные трудности, например, об опасности бесконечной рекурсии, каскадировании и обеспечении ссылочной целостности, отложив рассмотрение более сложных вопросов управления структурированными объектами до следующей части. Кроме того, я познакомлю вас с принципами исследовательского тестирования (exploration testing) – малоизвестной методики, позволяющей одновременно тестировать и библиотеку классов, и программный интерфейс db4o.

От простых объектов к структурированным

Как вы помните, в примерах к предыдущим статьям использовался класс Person, показанный в листинге 1.

Листинг 1. Person
package com.tedneward.model;

public class Person
{
    public Person()
    { }
    public Person(String firstName, String lastName, int age, Mood mood)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.mood = mood;
    }
    
    public String getFirstName() { return firstName; }
    public void setFirstName(String value) { firstName = value; }
    
    public String getLastName() { return lastName; }
    public void setLastName(String value) { lastName = value; }
    
    public int getAge() { return age; }
    public void setAge(int value) { age = value; }
    
    public Mood getMood() { return mood; }
    public void setMood(Mood value) { mood = value; }

    public String toString()
    {
        return 
            "[Person: " +
            "firstName = " + firstName + " " +
            "lastName = " + lastName + " " +
            "age = " + age + " " + 
            "mood = " + mood +
            "]";
    }
    
    public boolean equals(Object rhs)
    {
        if (rhs == this)
            return true;
        
        if (!(rhs instanceof Person))
            return false;
        
        Person other = (Person)rhs;
        return (this.firstName.equals(other.firstName) &&
                this.lastName.equals(other.lastName) &&
                this.age == other.age);
    }
    
    private String firstName;
    private String lastName;
    private int age;
    private Mood mood;
}

Строки в ООСУБД

Как вы помните, все поля в классе Person, который использовался в предыдущих статьях, были строковыми. Так как в Java и .NET String является объектным типом (классом-наследником Object), может показаться, что Person не может считаться простым классом. Однако большинство ООСУБД, в том числе и db4o, работают со строковыми полями не так, как с полями, содержащими ссылки на другие объекты. В частности, они учитывают то, что строки по своей сути являются неизменяемыми объектами.

Этого класса было вполне достаточно для иллюстрации базовых функций db4o, таких как сохранение, поиск и выборка объектов, но он слишком прост по сравнению с объектами, встречающимися в реальных корпоративных приложениях. Например, вероятно, что объекты типа Person, хранящиеся в вашей базе данных, должны содержать информацию о домашнем адресе, а возможно, также о жене и детях.

Допустим, нам понадобилось хранить информацию о супругах объектов типа Person в базе данных. Для этого необходимо добавить новое поле в класс Person, которое будет содержать ссылку на объект Spouse. Ввиду того, что потребуется выполнение определенных бизнес-правил, придется также создать перечислимый тип Gender, переопределяющий метод equals(), и передавать его экземпляр в конструктор Person. В листинге 2 показан пример работы с полем spouse через get- и set-методы, включающие несколько бизнес-правил.

Листинг 2. Может ли данный человек вступить в брак?
package com.tedneward.model;
public class Person {
    // . . .

    public Person getSpouse() { return spouse; }
    public void setSpouse(Person value) { 
        // Несколько бизнес-правил
        if (spouse != null)
            throw new IllegalArgumentException("Already married!");
        
        if (value.getSpouse() != null && value.getSpouse() != this)
            throw new IllegalArgumentException("Already married!");
            
        spouse = value; 
        
        // Это правило сильно отдает сексизмом
        if (gender == Gender.FEMALE)
            this.setLastName(value.getLastName());

        // Гарантия рефлексивности отношения
        if (value.getSpouse() != this)
            value.setSpouse(this);
    }
    
    private Person spouse;    
}

В листинге 3 показан пример создания двух супругов. Весь код выглядит достаточно предсказуемо.

Листинг 3. Кто-то собирается жениться, пора в церковь...
import java.util.*;
import com.db4o.*;
import com.db4o.query.*;
import com.tedneward.model.*;

public class App
{
    public static void main(String[] args)
        throws Exception
    {
        ObjectContainer db = null;
        try
        {
            db = Db4o.openFile("persons.data");

            Person ben = new Person("Ben", "Galbraith", 
                Gender.MALE, 29, Mood.HAPPY);
            Person jess = new Person("Jessica", "Smith", 
                Gender.FEMALE, 29, Mood.HAPPY);
            
            ben.setSpouse(jess);
            
            System.out.println(ben);
            System.out.println(jess);
            
            db.set(ben);
            
            db.commit();
            
            List<Person> maleGalbraiths = 
                db.query(new Predicate<Person>() {
                    public boolean match(Person candidate) {
                        return candidate.getLastName().equals("Galbraith") &&
                                candidate.getGender().equals(Gender.MALE);
                    }
                });
            for (Person p : maleGalbraiths)
            {
                System.out.println("Found " + p);
            }
        }
        finally
        {
            if (db != null)
                db.close(); 
        }
    }
}

Потенциальные проблемы

Здесь необходимо обратить внимание на несколько моментов (кроме нескольких потенциально спорных бизнес-правил). Во-первых, очевидно, что при сохранении объекта ben в базе данных ООСУБД придется выполнить определенные дополнительные действия. Необходимо, чтобы объект, представляющий его супругу, был не только сохранен в базе данных, но и автоматически извлекался из нее при выборке самого объекта ben.

Об этой серии

В последние десять лет хранение и поиск информации прочно ассоциировались исключительно с реляционными СУБД. Однако недавно появилась тенденция к изменению текущего положения дел. В частности, Java-разработчики все больше жалуются на плохую совместимость объектной и реляционной моделей данных (так называемый “object-oriented mismatсh”) и с нетерпением ждут решения этой проблемы. Все это, а также появление применимых на практике альтернатив привело к возрождению интереса к объектно-ориентированному хранению и поиску данных. Серия "Путеводитель по db4o для Java-разработчика" представляет db4o – объектную базу данных с открытым кодом. db4o дополняет существующие объектно-ориентированные языки, системы и принципы проектирования. Скачайте db4o со страницы проекта - она вам понадобится для запуска примеров.

Размышления над этим вопросом могут стать причиной определенного беспокойства. Разумеется, можно предположить, как ООСУБД избегают бесконечной рекурсии в данном конкретном случае, но к гораздо более тревожным выводам можно прийти, если представить себе объект, содержащий десятки, а возможно, сотни или тысячи ссылок на другие объекты, которые в свою очередь могут содержать ссылки. Попробуйте представить себе объектную модель для представления информации о детях, родителях и т.д. Выборка единичного объекта из базы данных может вполне привести к извлечению всего генеалогического древа до седьмого колена, что будет означать серьезную нагрузку на сеть.

К счастью, db4o, как и все ООСУБД за исключением самых примитивных, предоставляет средства для решения этой проблемы.

Исследовательское тестирование db4o

Работа со структурированными объектами – это достаточно сложный аспект использования db4o, но в процессе его освещения у меня появилась возможность продемонстрировать исследовательское тестирование (exploration testing) – методику, о которой мне рассказал один из моих друзей (насколько я знаю, авторство термина принадлежит Стю Хэллоуэю, Stu Halloway). Вкратце, исследовательский тест – это набор юнит-тестов, которые не только тестируют некоторую библиотеку, но и исследуют ее программный интерфейс, чтобы убедиться, что библиотека ведет себя предполагаемым образом. У данного подхода есть один полезный побочный эффект: после перекомпилирования тот же самый исследовательский тест может быть использован для будущих версий библиотеки. Ошибки при компиляции или выполнении теста будут сигнализировать о том, что библиотека не обеспечивает обратную совместимость, о чем необходимо знать до ее использования в реальных приложениях.

Кроме того, при исследовательском тестировании API db4o можно определить инициализирующий метод, который будет создавать базу данных и наполнять ее объектами класса Person, а также метод для удаления базы данных после выполнения тестов, чтобы исключить вероятность появления ложных объектов. Без этого мне бы пришлось каждый раз вручную удалять файл persons.data. Честно говоря, я не очень-то доверяю своей памяти в те моменты, когда разбираюсь с новым API.

В моих примерах исследовательских тестов будет использоваться библиотека JUnit 4 в консольном режиме. Для начала обратите внимание на класс StructuredObjectTest, показанный в листинге 4.

Листинг 4. Тестирование программного интерфейса db4o
import java.io.*;
import java.util.*;
import com.db4o.*;
import com.db4o.query.*;
import com.tedneward.model.*;

import org.junit.Before;
import org.junit.After; 
import org.junit.Ignore; 
import org.junit.Test; 
import static org.junit.Assert.*;

public class StructuredObjectsTest
{
    ObjectContainer db;

    @Before public void prepareDatabase()
    {
        db = Db4o.openFile("persons.data");

        Person ben = new Person("Ben", "Galbraith", 
            Gender.MALE, 29, Mood.HAPPY);
        Person jess = new Person("Jessica", "Smith", 
            Gender.FEMALE, 29, Mood.HAPPY);
    
        ben.setSpouse(jess);
    
        db.set(ben);
    
        db.commit();
    }
    
    @After public void deleteDatabase()
    {
        db.close();
        new File("persons.data").delete();
    }


    @Test public void testSimpleRetrieval()
    {
        List<Person> maleGalbraiths = 
            db.query(new Predicate<Person>() {
                public boolean match(Person candidate) {
                    return candidate.getLastName().equals("Galbraith") &&
                            candidate.getGender().equals(Gender.MALE);
                }
            });
            
        // Возвращенное множество объектов должно содержать один элемент
        assertEquals(maleGalbraiths.size(), 1);

        // (Реальный юнит-тест не должен выводить информацию в консоль,
        // но для исследовательского теста это допустимо)
        for (Person p : maleGalbraiths)
        {
            System.out.println("Found " + p);
        }
    }
}

Как и следовало ожидать, данный тестовый класс выполняется успешно – в зависимости от выбранного метода запуска (консольного или графического) вы увидите либо “.” либо зеленую полоску. Имейте в виду, что как правило не рекомендуется выводить данные в консоль, потому что они должны проверяться автоматически, а не визуально. Однако в случае исследовательского теста бывает полезно сначала просмотреть возвращаемые данные, а потом создать необходимые условия для проверки. К тому же я всегда могу закомментировать вызовы System.out.println. (Не стесняйтесь добавлять собственные тесты, проверяющие другие функции программного интерфейса db4o.)

С этого момента все фрагменты кода будут представлять собой тестовые методы, которые будут выполняться в тестовом пакете, показанном в листинге 4 (на это будет также указывать аннотация @Test в сигнатурах методов).


Сохранение и выборка структурированных объектов

Сохранение структурированных объектов как правило ничем не отличается от того, что было показано ранее: достаточно вызвать метод db.set(), передав в него сохраняемый объект, а об остальном позаботится ООСУБД. При этом не имеет принципиального значения, какой это объект – простой или структурированный, потому что ООСУБД идентифицирует объекты с помощью OID (см. статью "Путеводитель по db4o для Java-разработчика: запросы, идентификация и редактирование данных"), а следовательно не может сохранить один объект дважды.

В отличие от сохранения, при мысли о выборке структурированных объектов у меня волосы встают дыбом. Представьте, что извлекаемый из базы данных объект (неважно, через QBE или при помощи родного запроса) содержит множество ссылок на другие объекты, которые в свою очередь содержат ссылки и так далее. Звучит похоже на печально известную схему Понци, не так ли?

Решение проблемы бесконечной рекурсии

Несмотря на то что первой реакцией большинства разработчиков будет что-то вроде "не может быть, что они на самом деле так делают!", db4o действительно выбирает структурированные объекты из базы данных рекурсивным образом (по крайней мере до определенного предела). Дело в том, что в действительности большая часть программистов ожидает именно такого поведения, справедливо предполагая, что ранее сохраненные объекты должны быть легко доступны при первой необходимости. В то же время, разумеется, нежелательно пересылать по сети всю массу связанных объектов, по крайней мере, не за один раз.

db4o предлагает компромиссный вариант, при котором можно ограничить количество выбираемых объектов, варьируя значение параметра под названием глубина активации (activation depth). Под глубиной активации понимается максимальный уровень, на который db4o может углубиться в граф объектов при их извлечении из базы данных. Другими словами, она представляет собой число переходов по ссылкам, начиная от корневого объекта, которое может сделать db4o в процессе обработки запроса. В предыдущем примере для извлечения Бена (Ben) и его супруги Джессики (Jessica) было достаточно глубины активации по умолчанию (5), так как Джессика находится на расстоянии всего одной ссылки от Бена. При этом ни один объект, находящийся на удалении более 5 ссылок от Бена, не был бы выбран из базы данных, а соответствующим ссылкам было бы присвоено значение null. В подобных случаях разработчик должен явно активировать такие объекты, вызывая метод activate() класса ObjectContainer.

db4o позволяет гибко управлять глубиной активации при помощи метода activationDepth() класса Configuration, ссылка на экземпляр которого может быть получена через вызов db.configure(). Кроме того, можно задавать глубину активации для каждого класса индивидуально. В листинге 5 показано, как можно использовать ObjectClass для изменения глубины активации для класса Person.

Листинг 5. Использование объекта ObjectClass для управления глубиной активации
// Обратитесь к документации по классу ObjectClass
// за более подробной информацией
Configuration config = Db4o.configure();
ObjectClass oc = config.objectClass("com.tedneward.model.Person");
oc.minimumActivationDepth(10);

Изменение структурированных объектов

При необходимости редактирования данных встает другой вопрос: что будет, если модифицировать объект в графе, но не сохранить его явным образом? При сохранении объектов через ObjectContainer db4o анализирует все ссылки и сохраняет в базе данных все подчиненные объекты точно так же как и при первоначальном вызове метода set(). Пример приведен в листинге 6.

Листинг 6. Изменение связанных объектов
@Test public void testDependentUpdate()
{
    List<Person> maleGalbraiths = 
        db.query(new Predicate<Person>() {
            public boolean match(Person candidate) {
                return candidate.getLastName().equals("Galbraith") &&
                        candidate.getGender().equals(Gender.MALE);
            }
        });
        
    Person ben = maleGalbraiths.get(0);
        
    // С днем рождения, Джессика!
    ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);

    // У нас есть только ссылка на Бена, сохраним ее и выполним commit
    db.set(ben);
    db.commit();

    // Убедимся, что Джессике действительно 30 лет
    Person jess = (Person)db.get(
            new Person("Jessica", "Galbraith", null, 0, null)).next();
    assertTrue(jess.getAge() == 30);
}

Несмотря на то что объект jess явно не сохраняется в базе данных, ссылка на него содержится в объекте ben. Поэтому при сохранении последнего все изменения в объекте jess будут автоматически перенесены в базу данных.

Вообще-то на самом деле все не совсем так. А точнее даже совсем не так.

Проверка на ложное отсутствие ошибок

На практике оказывается, что это один из тех случаев, когда исследовательские тесты выполняются неверно, ошибочно выдавая положительное заключение. Хотя это явно не следует из документации, ObjectContainer содержит кэш активированных объектов, поэтому объект jess в листинге 6 на самом деле извлекается не из базы данных на диске, а из оперативной памяти. Подобное поведение скрывает тот факт, что глубина изменения объектов равна единице, что означает, что только значения примитивных типов (в также строки) будут сохраняться в базе данных в момент вызова set(). Чтобы продемонстрировать данное явление, необходимо немного модифицировать тест, как показано в листинге 7.

Листинг 7. Проверка на ложное отсутствие ошибок
@Test(expected=AssertionError.class)
public void testDependentUpdate()
{
    List<Person> maleGalbraiths = 
        db.query(new Predicate<Person>() {
            public boolean match(Person candidate) {
                return candidate.getLastName().equals("Galbraith") &&
                        candidate.getGender().equals(Gender.MALE);
            }
        });
            
    Person ben = maleGalbraiths.get(0);
    assertTrue(ben.getSpouse().getAge() == 29);
    
    // С днем рождения, Джессика!
    ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);

    // У нас есть только ссылка на Бена, сохраним ее и вызовем commit
    db.set(ben);
    db.commit();
        
    // Закроем ObjectContainer а затем откроем его заново
    db.close();
    db = Db4o.openFile("persons.data");

    // Найдем Джессику чтобы убедиться, что ей 30 лет
    Person jess = (Person)db.get(
            new Person("Jessica", "Galbraith", null, 0, null)).next();
    assertTrue(jess.getAge() == 30);
}

В этом случае тест завершается с ошибкой AssertionFailure, которая означает, что все мои предыдущие утверждения о каскадном изменении объектов в графе были неверны. (Вы можете указать JUnit, что данная ошибка ожидается в процессе выполнения теста, задав в качестве атрибута expected аннотации @Test нужный тип исключения).

Управление каскадированием

Тот факт, что db4o возвращает объекты из кэша вместо неявного обновления в базе данных, вызывает определенные споры. Разработчики в основном делятся на два лагеря: тех, кто считает подобное поведение деструктивным и противоречащим здравому смыслу, и тех, кто убежден, что ООСУБД должны вести себя именно таким образом. Не вдаваясь в подробности данного спора, стоит заметить, что это всего лишь поведение по умолчанию, которое можно изменить. Как следует из листинга 8, это можно делать для каждого класса индивидуально при помощи метода ObjectClass.setCascadeOnUpdate() с параметром true (обратите внимание, что метод должен вызываться до открытия ObjectContainer). Обновленная версия теста с включенным каскадным обновлением показана в листинге 8.

Листинг 8. Включение каскадирования
@Test
public void testWorkingDependentUpdate()
{
    // Вызов cascadeOnUpdate() должен производиться до открытия ObjectContainer,
    // поэтому сначала вызовем close(), затем setCascadeOnUpdate, а потом снова open()
    db.close();
    Db4o.configure().objectClass(Person.class).cascadeOnUpdate(true);
    db = Db4o.openFile("persons.data");

    List<Person> maleGalbraiths = 
        db.query(new Predicate<Person>() {
            public boolean match(Person candidate) {
                return candidate.getLastName().equals("Galbraith") &&
                        candidate.getGender().equals(Gender.MALE);
            }
        });
           
    Person ben = maleGalbraiths.get(0);
    assertTrue(ben.getSpouse().getAge() == 29);
        
    // С днем рождения, Джессика!
    ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);

    // У нас есть только ссылка на Бена, поэтому сохраним ее и выполним commit
    db.set(ben);
    db.commit();
        
    // Закроем ObjectContainer, а затем заново откроем его
    db.close();
        
    db = Db4o.openFile("persons.data");

    // Найдем Джессика, чтобы убедиться, что ей 30 лет
    Person jess = (Person)db.get(
            new Person("Jessica", "Galbraith", null, 0, null)).next();
    assertTrue(jess.getAge() == 30);
}

Каскадирование поддерживается не только при редактировании, но также при выборке (что аналогично неограниченной глубине активации) и удалении объектов. Далее перейдем к удалению – последней операции над объектами Person, которую я рассмотрю в данной статье.


Удаление структурированных объектов

Удаление объектов из базы данных работает по тем же принципам, что выборка и изменение: по умолчанию удаление объекта не приводит к удалению ни одного из объектов, на которые он ссылается. Как правило именно такое поведение является предпочтительным (листинг 9).

Листинг 9. Удаление структурированного объекта
@Test
public void simpleDeletion()
{
  Person ben = (Person)db.get(new Person("Ben", "Galbraith", null, 0, null)).next();
  db.delete(ben);
        
  Person jess = (Person)db.get(new Person("Jessica", "Galbraith", null, 0, null)).next();
  assertNotNull(jess);
}

Бывают ситуации, при которых необходимо, чтобы все объекты, на которые ссылается удаляемый объект, также были удалены. Как и в случае с глубиной активации и изменения, этого можно добиться при помощи методов класса Configuration. Пример показан в листинге 10.

Листинг 10. Пример вызова Configuration.setCascadeOnDelete()
@Test
public void cascadingDeletion()
{
    // Вызов cascadeOnDelete() должен производиться до открытия ObjectContainer,
    // поэтому сначала вызовем close(), затем cascadeOnDelete, а потом снова open()
    db.close();
    Db4o.configure().objectClass(Person.class).cascadeOnDelete(true);
    db = Db4o.openFile("persons.data");

    Person ben = 
        (Person)db.get(new Person("Ben", "Galbraith", null, 0, null)).next();
    db.delete(ben);
        
    ObjectSet<Person> results = 
        db.get(new Person("Jessica", "Galbraith", null, 0, null));
    assertFalse(results.hasNext());
}

При этом необходимо соблюдать осторожность, не забывая о том, что в db4o не существует понятия ссылочной целостности, поэтому все объекты, ссылающиеся на каскадно-удаленные объекты, будут содержать null в своих ссылках. Ссылочная целостность – это одна из наиболее ожидаемых возможностей db4o, и, как мне стало известно, разработчики db4o думают над тем, как добавить ее в будущих версиях. При этом самое главное – не нарушать "принцип наименьшего удивления", принимая во внимание, что даже при использовании реляционных баз данных иногда желательно нарушать целостность.


Заключение

В данной статье мы перешагнули определенный рубеж: до этого во всех примерах фигурировали достаточно примитивные объекты. Благодаря их простоте можно было сосредоточиться на понимании принципов работы ООСУБД, а не самих объектов. Понимание того, как ООСУБД, в частности db4o, сохраняет связанные между собой объекты, является непростой задачей. К счастью, стоит только разложить поведение по полочкам, как все, что вам останется – это внести соответствующие ему изменения в ваше приложение.

В этой статье были продемонстрированы начальные примеры приведения сложных объектов в соответствие с объектной моделью db4o. Были рассмотрены базовые операции CRUD над структурированными объектами, а также выявлены некоторые неизбежные проблемы и пути их решения.

Тем не менее, все структурированные объекты в моих примерах по-прежнему остаются достаточно упрощенными, в частности, в них используются исключительно прямые ссылки. Как известно, стоит вам только выйти замуж, как неизбежно встает вопрос о детях. В будущих статьях я продолжу тему создания и управления структурированными объектами в db4o, добавив несколько детей к счастливому семейству объектов ben и jess.

Ресурсы

Научиться

Получить продукты и технологии

  • Скачайте db4o: объектную базу данных с открытым кодом для Java и .NET. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=361122
ArticleTitle=Путеводитель по db4o для Java-разработчика: За рамками простых объектов
publish-date=12252008