I’m now fully populating the database with information from my collection. It took longer than I expected; I’ll keep that in mind the next time I plan to work in a unfamiliar area. (It wouldn’t have been so bad, of course, if I were working in chunks larger than an hour or two at a time.)
The process was reasonably pleasant, and I ended up with a nice abstraction layer hiding the details of the SQL code. Some sort of layer was necessary just for testing purposes, though perhaps I could have found a pre-written testing layer. But writing my own testing layer wasn’t very hard – right now, I’m using a very small subset of JDBC, and doing just a handful of SQL queries – and it actually ended up making the code much more readable. For one thing, it helped me make a clean separation of SQL code from non-SQL code – I only used java.sql classes in three short source files, and the only job of those files is to translate into java.sql classes. (For the curious, they are implementations of abstract interfaces called SqlRow, which is an abstraction of a single row in a table; SqlRows, which is an abstraction of a collection of rows in a table; and SqlSource, which is an abstraction of a connection to a database.) And, for another thing, the abstraction layer gave me a convenient place to translate from my custom types into standard types – otherwise, I’d either have had to come up with a layer whose only job was to do that translation or done the translation by hand in many places.
Maybe the single most annoying thing was handling dates. I was aware that Java has a class java.util.Date
which is now frowned upon, in favor of a superior class java.util.Calendar
. And I’m even somewhat sympathetic to that idea – it’s good for people to have to deal with internationalization issues, and time zones are certainly part of that. But I don’t care about time zones for this project – all I care about are dates, and I can’t say that I’m too interested in the argument “not everybody uses the Gregorian calendar”. So the upshot was that I had written my own Date class (which was useful for other reasons, e.g. to handle books that I’m in the progress of reading right now).
But when writing out to SQL, I wanted to do things the appropriate JDBC way, which meant using its class java.sql.Date
when writing out SQL dates. I was hoping that I could easily convert from my own Date class to the JDBC Date class – after all, an SQL date is just a date, it doesn’t have a timestamp, so there’s no need for, say, abstractions to handle time zones. And the JDBC Date class does have a constructor taking a year, month, and day, which I could have used, but it’s marked as deprecated. Instead, for better or for worse, you’re supposed to use the constructor whose argument is the milliseconds that the date started after January 1, 1970. (The argument can be negative, for dates before 1970, though the docs weren’t crystal clear on that matter; they also mutter something about normalization, which I can’t say I understand, either.)
After a while, I finally came up with the correct code: the right thing to do turns out to be
new java.sql.Date(new java.util.GregorianCalendar(date.year(), date.month() - 1, date.day()).getTimeInMillis())
Which is wrong on several levels. For one thing, when converting from one abstraction of a date to another abstraction of a date, I shouldn’t have to talk about milliseconds. For another thing, “getTimeInMillis” is a lousy method name. And for a third thing, why are months numbered from 0 to 11 while days in a month are numbered from 1 to 31? Sigh. I am glad that I’m starting to understand how to use Java dates “correctly”, but I’m not impressed with this aspect of JDBC’s design.
Anyways, I’m pretty sure I have the database populated correctly now. (And doing so turned up a couple of mistakes in my database schema, too.) The next thing is to write code to read from the database. I’ll have a bit of free time in the middle of the week, so I’ll be able to get it started soon; at first I was optimistic that I’d actually be able to finish it in the middle of the week, but now I’m thinking it will be a bit trickier than that. If it were just a matter of writing JDBC code to read from a database, then I don’t think that would take too long – it would take a little while, because I’ve never done that before, but I think I understand all the ideas, and have a pretty good idea of what most of the new necessary abstractions are.
(To be sure, I’ll probably spend a fair amount of time playing God of War instead of programming. Good game, though not quite as superb as some people make it out to be; not something to play while Miranda is around, though, while I can program when Miranda’s around, so I’ll probably spend a fair amount of time on that. And I have some blog reading and writing to do; in particular, I want to talk about the stunning Killer 7.)
But, setting aside other matters competing for my time, the next step will also force me to approach certain notions from a quite different angle than I’m doing now. For example, right now, when I create a book, I add that book to an internal table attached to that author, so that, when I ask the author to generate its HTML page, it can list all the books by that author. But, in the new world, the right way to generate that list is to look it up in the database; so, instead of keeping a collection of Books in each Author, I’ll either want each Author to contain a pointer to some sort of database abstraction, or I’ll want to pass a database abstraction to the method on Author that generates the HTML. Right now, I’m leaning towards the latter, but it’s not entirely obvious to me what the pros and cons are; I can easily imagine proceeding a fair way down one path, realizing that it’s a mistake, and backtracking and heading down a different path. (Hey, I just thought of a way to get the former to work well – each entity (Author, Book, whatever) already has a ‘Key’ associated to it; I could stick the database backpointer in that Key. That would only require minimal alterations of my current code, and it’s not immediately obvious to me that doing so would be inelegant.)
I am looking forward to the day when I can get back to adding new features. I really want to add a list of links to each entity, so I can have pointers from dbcdb to the blog, not just pointers in the other direction. And I just finished a book with three authors, which I currently can’t represent. And I’m thinking that it’s time to add more media types – CDs, certainly, and eventually DVDs. And at some point I want to switch to Ruby: I already suspect that working in a typed language will make my next step harder than it could be.
Post Revisions:
There are no revisions for this post.
The above code is necessary because of two Sun deficiencies: fixing either would make it much cleaner.
A java.util.Date object represents a specific instant of Universal (that is, Greenwich) Time. A java.sql.Date is a trivial wrapper over a java.util.Date. Unfortunately, there is no way to construct the latter given the former; you have to extract the internal representation of the java.util.date as a long that specifies milliseconds since the Unix Epoch and pass that to the constructor for java.sql.Date.
A java.util.GregorianCalendar object represents a specific instant of time as such-and-such a year, month, day, hour, minute, second, millisecond, and time zone. It has a method to return the corresponding java.util.Date but not the corresponding java.sql.Date.
As for months being numbered from 0 to 11, that is so they can be used directly to index a zero-based array of month names. This dubious feature is inherited from the corresponding C library routine, localtime().
3/6/2006 @ 3:08 pm
How can it be hard to associate a book with a list of links? Isn’t that just a one-to-many relation between the book ID and links?
If the table’s represented as a tree that applies common suffix omission to adjacent keys, you should even get a reasonably dense representation of a list that way.
3/6/2006 @ 9:07 pm
To John’s comment: I agree, you should be able to go directly from a util.Date to an sql.Date, as well as in the other direction. Of course, I don’t think sql.Date should be a trivial wrapper over a util.Date – for one thing, they seem to me that they should be different abstractions, as I complained about in my post, and for another thing, using inheritance in situations like that seems kind of gauche. I hadn’t realized that localtime() also used zero-based months; sigh, but at least it makes the Java decision a bit less wacky.
To answer Jim: it certainly should be easy, and would have been easy before the SQL conversion as well – I’d just promised myself I wouldn’t add any new features until I finished the SQL conversion. (I broke the promise once, but not again.)
3/7/2006 @ 8:49 am