2

I have been storing an object which contains a GregorianCalendar object in a db4o database, which works just fine. However, on retrieving the object (after closing and re-opening the database), I cannot seem to access some of the information inside (namely get(GregorianCalendar.MONTH) ). I have included test code below, and am wondering how to fix this problem.

import static org.junit.Assert.assertEquals;

import java.util.GregorianCalendar;

import org.junit.Test;

import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.config.EmbeddedConfiguration;

public class DateTest {

    public class RecordDate {

        private GregorianCalendar calendar;

        public RecordDate() {
            calendar = new GregorianCalendar();
        }

        public int getMonth() {
            return calendar.get(GregorianCalendar.MONTH);
        }
    }

    @Test
    public void testGetMonth() {
        EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
        config.common().objectClass(RecordDate.class).cascadeOnActivate(true);
        config.common().objectClass(RecordDate.class).cascadeOnUpdate(true);
        config.common().activationDepth(25);
        config.common().updateDepth(25);

        ObjectContainer database = Db4oEmbedded.openFile(config,
                "db/datetest.db");

        GregorianCalendar currentdate = new GregorianCalendar();
        RecordDate testdate = new RecordDate();
        assertEquals(currentdate.get(GregorianCalendar.MONTH),
                testdate.getMonth()); // this passes

        database.store(testdate);
        database.close();

        EmbeddedConfiguration config2 = Db4oEmbedded.newConfiguration();
        config2.common().objectClass(RecordDate.class).cascadeOnActivate(true);
        config2.common().objectClass(RecordDate.class).cascadeOnUpdate(true);
        config2.common().activationDepth(25);
        config2.common().updateDepth(25);
        database = Db4oEmbedded.openFile(config2, "db/datetest.db");

        testdate = (RecordDate) database.queryByExample(RecordDate.class)
                .next();
        assertEquals(currentdate.get(GregorianCalendar.MONTH),
                testdate.getMonth()); // this should pass, but doesn't
        database.close();
    }
}
  • So, what month do you store in the database, and what month is on the object which you get out of it? – Paŭlo Ebermann Nov 04 '11 at 19:28
  • It's not that they are different, it's that it throws a NullPointerException – user1030309 Nov 04 '11 at 20:42
  • The `getMonth()` method (i.e. the `calendar` field is `null`), or `get(GregorianCalendar.MONTH)`? Or the `equals` method? Please help a bit solving your problem. – Paŭlo Ebermann Nov 04 '11 at 20:56
  • get(GregorianCalendar.MONTH) throws the exception. The calendar field is not null upon retrieval, I have checked this. – user1030309 Nov 05 '11 at 00:12
  • I checked the API, and it says that get() shouldn't ever throw a NullPointerException. The stack trace after get(GregorianCalendar.MONTH) is ' at java.util.Calendar.get(Unknown Source) ' – user1030309 Nov 05 '11 at 00:17

2 Answers2

4

Db4o does support java.util.Calendar objects, but not out-of-the-box. To add support for Calendar, add one of these two lines to your database configuration:

configuration.common().objectClass(Calendar.class).callConstructor(true);

or

configuration.common().objectClass(Calendar.class).storeTransientFields(true);

The reason this is necessary is that Calendar has transient fields, or fields that don't get stored or serialized by default in Java (JLS §8.3.1.3). You can find these fields in the Calendar source code if you want. These fields are usually dependent on some other field in the object and get calculated when the value of the other field changes. From the JLS section above:

Variables may be marked transient to indicate that they are not part of the persistent state of an object.

For example, let's say I have a class that represents a sale:

public class Sale {
    private double cost, taxRate;
    private transient double taxesPaid;

    // etc...
}

I've declared the taxesPaid variable here as transient because I can figure it out from cost and taxRate. If the cost is $2.00 and the tax rate is 7%, then the taxes paid would be $0.14. In databases, this kind of information is usually considered redundant, and there are entire books written on how to remove this kind of dependency from a database. In Java, you can specify that the field not be stored at all by specifying it as transient.

Db4o honors Java's definition of transient fields, and it won't store them either. In fact, most persistence frameworks and databases won't store transient fields. So when db4o recreates the Calendar object from the database, it only restores its non-transient fields, which means all the transient fields are null (hence your NullPointerException). To fix this, you tell db4o to either:

  • Call Calendar's constructor, and the Calendar constructor will initialize/calculate the transient fields automatically or
  • Store Calendar's transient fields, which will treat the fields as if they aren't transient.

For the record, I've seen people recommend using both lines in their code, and I've also seen GregorianCalendar.class in place of Calendar.class. One or the other should work fine though.

Sorry for weighing in late. I just thought it was important to make sure anyone reading this knows this is the proper way to store the Calendar (it allows indexing, querying, etc. on the Calendar fields without object instantiation slowing down the queries).

Brian
  • 17,079
  • 6
  • 43
  • 66
  • Thanks for this. In my case I'm no longer getting a null point exception but every time a retrieve an Object's Calendar attribute I only get the current date and not the one that I stored. Any Ideas of what went wrong. – jigzat Sep 07 '13 at 01:27
  • My advice is to store date as a String, because db4o doesn't support storing complex objects, like Calendar, Timestamp, etc. However, db4o will try to store any object, but there are no guarantees of success! – akelec Jan 16 '15 at 13:29
  • @AKelec Sure, you can do that, but it drastically reduces searchability. I'd sooner advocate for just storing it as a `long` or your own custom data type than as a `String`, then comparison operators are easier to wrap your head around and predicate queries and SODA queries end up being easier to write and later read. – Brian Jan 16 '15 at 22:04
1

db4o doesn't support storing Calendar instances. You should store Date instances.

As alternative you can use an object translator to store your calendar object. However is will not support any query on that object. You could try the serializing translator, which just uses Java serialization to store the object:

configuration.common().objectClass(Calendar.class).translate(new TSerializable());
configuration.common().objectClass(GregorianCalendar.class).translate(new TSerializable());

db4o basically supports storing your own 'data'-object which can consist of primitives, strings, arrays, basic collection and references to other data-objects. However db4o doesn't support storing complex framework objects, like Calendar, Swing-Objects and concurrent collections etc.

However db4o tries to store any object, no matter what. However with complex object with tons of transient state and references to static objects this can fail.

Gamlor
  • 12,978
  • 7
  • 43
  • 70