Controlling Hibernate Output with Log4J

Our Simple Hibernate Application is a bit verbose. That’s probably okay for a web application because stdout actually ends up in a server log. But it’s a bit annoying in console application. Fortunately, Hibernate uses the Apache commons-logging abstraction layer and Hibernate logging is easily configured.

First, a quick aside on logging. Logging packages are a mechanism that allow a developer to add diagnostic messages to the code and specify the message level and destination at runtime. The design allows the developer to define a hierarchical logging structure in which each level inherits the properties of the level above it unless otherwise specified. Typically, the logging structure mirrors the package structure as that is both simple and effective. The JCL Users Guide is a good place to start for more information.

Apache commons-logging supports the log4j, JDK 1.4, and JCL SimpleLog Logging packages. We’ll use log4j because I’m already familiar with it and the Hibernate distribution includes a sample log4j.properties file.

Here are the required changes to our build.xml file (shown in bold). First, there is a new property that locates the log4j jar file. And second, the log4j jar file is added to the execution classpath.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<project default="all">
<property name="HBM-HOME" location="/Projects/Java/hibernate" />
<property name="JDBC" location="/Projects/Server/hsqldb/lib/hsqldb.jar" />
<property name="LOG4J" location="/Projects/Java/Lib/log4j.jar" />

<property name="src-dir" location="src" />
<property name="cfg-dir" location="cfg" />
<property name="obj-dir" location="obj" />
<property name="TALK" value="false" />

<path id="classpath.base">
<pathelement location="${obj-dir}" />
<pathelement location="${HBM-HOME}/hibernate2.jar" />
<fileset dir="${HBM-HOME}/lib" includes="**/*.jar" />
<pathelement location="${JDBC}" />
</path>
<path id="classpath.run">
<pathelement location="${cfg-dir}" />
<path refid="classpath.base" />
<pathelement location="${LOG4J}" />
</path>

<target name="init">
<mkdir dir="${obj-dir}" />
</target>

...

</project>

And here is the log4j.properties file from my hibernate distribution.

  • The first block of parameters define an output channel identified as stdout. This channel directs messages to the standard output.
  • The second block of parameters define an output channel identified as file. This channel directs messages to the hibernate.log file.
  • The rootLogger assignment defines the default behavior as directing messages at or more severe than the warn level to the stdout channel.
  • And the remaining assignments change the output levels to info for the respective packages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ##

log4j.rootLogger=warn, stdout

log4j.logger.net.sf.hibernate=info

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.net.sf.hibernate.connection.DriverManagerConnectionProvider=trace

### log JDBC bind parameters ###
log4j.logger.net.sf.hibernate.type=info

### log prepared statement cache activity ###
log4j.logger.net.sf.hibernate.ps.PreparedStatementCache=info

The easiest solution is to simply uncomment the lines defining the file channel and redefine the rootLogger to use the file channel. This will direct the log output to the hibernate.log file.

1
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file hibernate.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=hibernate.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ##

log4j.rootLogger=warn, file

log4j.logger.net.sf.hibernate=info

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.net.sf.hibernate.connection.DriverManagerConnectionProvider=trace

### log JDBC bind parameters ###
log4j.logger.net.sf.hibernate.type=info

### log prepared statement cache activity ###
log4j.logger.net.sf.hibernate.ps.PreparedStatementCache=info

But here’s another option. We’ll create two properties files, one that directs the output to hibernate.log and another that directs the output to both System.out and hibernate.log. Then we’ll use the condition task and the java sysproperty task to specify the desired configuration file based upon the value of the TALK property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<project default="all">
< ... >

<target name="setup-run" depends="clean-run">
<condition property="LOG4J_OUT" value="log4j-to-stdout.properties">
<istrue value="${TALK}"/>
</condition>
<condition property="LOG4J_OUT" value="log4j-to-file.properties">
<isfalse value="${TALK}"/>
</condition>
</target>
<target name="run" depends="compile,setup-run">
<java classname="Main"
fork="true">

<classpath refid="classpath.run" />
<sysproperty key="log4j.configuration" value="${LOG4J_OUT}" />
</java>
</target>
<target name="clean-run">
<delete verbose="${TALK}" file="hibernate.log" />
</target>

< ... >
</project>

Now we can specify via the command line whether to view the logging messages on System.out or not.

1
$ ant run
Buildfile: build.xml

init:

compile:

clean-run:

setup-run:

run:

BUILD SUCCESSFUL
Total time: 12 seconds
$ wc -l hibernate.log 
      15 hibernate.log
$ ant -DTALK=true run
Buildfile: build.xml

init:

compile:

clean-run:
   [delete] Deleting: /Projects/Learn/Hibernate/Simple/hibernate.log

setup-run:

run:
     [java] 15:38:16,872  INFO Environment:401
                          - Hibernate 2.0.1
     [java] 15:38:16,898  INFO Environment:435
                          - loaded properties ... 
     [java] 15:38:16,942  INFO Environment:450
                          - using CGLIB reflection optimizer
     [java] 15:38:17,029  INFO Environment:460
                          - JVM proxy support: true
     [java] 15:38:17,080  INFO Configuration:283 
                         - Mapping resource: hb/Keyword.hbm.xml
     [java] 15:38:20,273  INFO Binder:176
                          - Mapping class: hb.Keyword -> keywords
     [java] 15:38:21,977  INFO SessionFactoryImpl:140
                               - building session factory
     [java] 15:38:22,114  INFO Dialect:37
                               - Using dialect: net.sf.hibernate.dialect.HSQLDialect
     [java] 15:38:22,167  INFO DriverManagerConnectionProvider:41
                               - Hibernate connection pool size: 2
     [java] 15:38:22,249  INFO DriverManagerConnectionProvider:70
                               - using driver: org.hsqldb.jdbcDriver
                                 at URL: jdbc:hsqldb:hsql:\//localhost
     [java] 15:38:22,259  INFO DriverManagerConnectionProvider:71
                               - connection properties: {user=user, password=user}
     [java] 15:38:22,260  INFO SessionFactoryImpl:170
                               - Use outer join fetching: false
     [java] 15:38:22,533  INFO SessionFactoryImpl:193
                               - Use scrollable result sets: true
     [java] 15:38:23,623  INFO SessionFactoryObjectFactory:82
                               - no JDNI name configured
     [java] 15:38:23,627  INFO SessionFactoryImpl:287
                               - Query language substitutions: {}

BUILD SUCCESSFUL
Total time: 13 seconds
$ wc -l hibernate.log 
      15 hibernate.log
$ exit

Our application still persists keyword objects without regard for existing keyword objects, but at least it does so quietly. If you’re following along, then here is the source for our Simple Hibernate application with log4j. We’ll fix the persistence problem next.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.

Bad Timing

With the Macworld CreativePro conference in town, this probably isn’t the best time for a Mac gaff by the New York Times. But the picture accompanying a Market Place story on Intel (page C4) has the caption:

A customer at a McDonald’s in Times Square takes advantage of the Wi-Fi capabilities there with a laptop that uses Intel’s Centrino chip.

The only problem is that I think it’s an iBook. It has the distinctive middle hinge. It has the retracting latch for the lid. And it sure looks like a Mac toolbar at the top of the screen.

Whoops.

Hoist with our own Petard

John Palfrey thinks that fair use [is] not a strong doctrine at the moment [via Scripting News]. And suggests that we get involved with people like the EFF, Fred von Lohmann, and Wendy Seltzer if we’d like to do something about it. Unfortunately, this cuts a bit too close to home for many of us. While I think that most of us would like to see fair use better defined, I also think that many of us are not interested in seeing fair use liberalized. And that may be too narrow a path to navigate.

Right now, the primary tools to protect intellectual property are patents, trade secrets, and copyright (disclaimer: everything I know about the law I learned from TV, mysteries and the web). Patents are primarily a big company tool. And trade secrets don’t help with openly distributed software. That leaves copyright as the primary IP protection for the small software developer. So any steps towards liberalizing fair use are pretty scary steps for us to take.

Which is not to say “Don’t do it.” I just think that you’re going to need to carefully spell out direction and intent before we rally around the flag for you.

Every bug has a Story

Scoble says that every bug has a story in explaining why nagging bugs don’t get fixed. I’ll just add that sometimes the so-called “easy to fix” bug turns out to be incredibly hard. Run up against that a couple of times and you’ll learn to stick to the bug prioritization too.

Of course, I don’t have to like it just because I understand it. I once identified a bug, identified a fix, and convinced QA that my fix was correct. But the bug was still there two releases later - it just never became a priority.

Entry Drafts in Radio Userland

There are many great things about Radio Userland, but entry management probably isn’t one of them. Out of the box, Radio supports the concepts of post and publish. But while publish means just what you think, post is much more nebulous. In particular, post without publish ≠ draft.

If you really want drafts on Radio, then you need to finesse the system. Create a draft category within Radio; select render this category in HTML and clear notify weblogs.com on updates. To insure that nothing is ever streamed up to the server, create an #upstream.txt file in the work directory ([radio home]/www/categories/draft) containing:

1
<upstream type="none" version="1.0"></upstream>

To create a draft entry, clear the home page category and select the draft category. When you’re ready to publish, select the appropriate categories for the entry and publish. Of course, you could also use an client such as NetNewsWire to create drafts. What I like about the category approach is that you can visit the local draft rendition to see what it will really look like (modulo the Radio toolbar at the top).

This little trick also comes in handy when you’re creating a long entry like So you want to learn hibernate. I use emacs (my editor of my choice) to create and edit a file named work.txt in the draft directory. And I view the local rendition to see how it will look in the browser.

Setting Java System Properties with Ant

In this installment, we’ll learn how to set a Java System Property with ant. Here is our Java class. It simply accesses the System Properties object and prints the value of the test.property key.

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Properties;

public class prop {
public static void main( String[] args )
{

String testProp = "test.property";
Properties sysProps = System.getProperties();

System.out.println( "Value of " + testProp + " is " +
sysProps.getProperty(testProp) );
}
}

And here is the simple Ant build.xml that compiles and executes that class.

1
2
3
4
5
6
7
8
9
10
11
12
13
<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<target name="run" depends="compile">
<java classname="prop"
fork="true">

</java>
</target>

</project>

With the following result:

1
$ ant run
Buildfile: build.xml

compile:
    [javac] Compiling 1 source file

run:
     [java] Value of test.property is null

BUILD SUCCESSFUL
Total time: 6 seconds

To specify a System Property we use the sysproperty attribute of the java task. We’ll change the run target to assign the value blue to the key test.property.

1
2
3
4
5
6
7
8
<target name="run" depends="compile">
<java classname="prop"
fork="true">

<sysproperty key="test.property"
value="blue"
/>

</java>
</target>

And we get the expected output on execution.

1
$ ant
Buildfile: build.xml

compile:

run:
     [java] Value of test.property is blue

BUILD SUCCESSFUL
Total time: 3 seconds

So far, so good. Of course, if test.property is going to have a fixed value, then we might as well set it from within our application. If test.property will typically have a wide range of simple values, then it is probably best managed as a parameter with a default value that can be overridden from the command line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<property name="COLOR" value="red" />
<target name="run" depends="compile">
<java classname="prop"
fork="true">

<sysproperty key="test.property"
value="${COLOR}"
/>

</java>
</target>

</project>

And if the property has a small number of complex values, then it can be set based upon another parameter that can be set from the command line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<property name="COLOR" value="red" />
<target name="setup-run">
<condition property="COMPLEX-COLOR"
value="complex.property.when.red.">

<contains string="red" substring="${COLOR}" casesensitive="false" />
</condition>
<condition property="COMPLEX-COLOR"
value="complex.property.when.blue.">

<contains string="blue" substring="${COLOR}" casesensitive="false" />
</condition>
<condition property="COMPLEX-COLOR"
value="complex.property.when.unknown.">

<not>
<isset property="COMPLEX-COLOR" />
</not>
</condition>
</target>

<target name="run" depends="compile,setup-run">
<java classname="prop"
fork="true">

<sysproperty key="test.property"
value="${COMPLEX-COLOR}"
/>

</java>
</target>

</project>

Which lets us control the property from the command line:

1
$ ant
Buildfile: build.xml

compile:

setup-run:

run:
     [java] Value of test.property is complex.property.when.red.

BUILD SUCCESSFUL
Total time: 3 seconds
$ ant -DCOLOR=BLue
Buildfile: build.xml

compile:

setup-run:

run:
     [java] Value of test.property is complex.property.when.blue.

BUILD SUCCESSFUL
Total time: 3 seconds
$ ant -DCOLOR=purple
Buildfile: build.xml

compile:

setup-run:

run:
     [java] Value of test.property is complex.property.when.unknown.

BUILD SUCCESSFUL
Total time: 3 seconds

Disclaimer: I don’t claim to be an expert on ant. Please send comments and corrections.

So you want to learn Hibernate

Me too. Unfortunately, I’m finding the learning curve just a bit steep – or maybe it’s just that my brain is not wired the right way. I like to start with the simplest example that does something and then start playing from there. The examples in the Hibernate documentation weren’t quite what I was looking for. For one thing, they all seemed to run inside an App Server. So here is my version of a simple Hibernate application.

Let’s start with some book keeping. I’m using Java 1.4.1_01 on Mac OS X, Ant 1.5.1, Hibernate 2.0.1, and HSqlDB 1.7.1. I chose HSqlDB because I would like to run an in-process database at some point. Although I haven’t tried any other databases, I would expect good results with any other database known to Hibernate. And I would also expect good results with closely related versions of Java and Ant. If you are in a hurry to play, then you can grab the example distribution and go to town (but see this warning first).

Now, on to my Ant build file.

  • It starts with two properties that specify the location of the hibernate distribution and the jdbc driver – these should be the only properties that you need to change for your system.
  • The directory hierarchy consists of 3 directories: src-dir for Java source, obj-dir for compiled classes, and cfg-dir for all configuration files.
  • There are two defined classpaths. The base classpath includes our class files, all the hibernate jar files (I’m not positive that we need them all, but they must be in there for a reason), and the JDBC jar file. And the execution classpath adds our configuration directory in addition to that.
  • Finally, be sure to set fork=”true” in your java task; this prevents xml parser conflicts by isolating the hibernate application from the ant application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<project default="all">
<property name="HBM-HOME" location="/Projects/Java/hibernate" />
<property name="JDBC" location="/Projects/Server/hsqldb/lib/hsqldb.jar" />

<property name="src-dir" location="src" />
<property name="cfg-dir" location="cfg" />
<property name="obj-dir" location="obj" />
<property name="TALK" value="false" />

<path id="classpath.base">
<pathelement location="${obj-dir}" />
<pathelement location="${HBM-HOME}/hibernate2.jar" />
<fileset dir="${HBM-HOME}/lib" includes="**/*.jar" />
<pathelement location="${JDBC}" />
</path>
<path id="classpath.run">
<pathelement location="${cfg-dir}" />
<path refid="classpath.base" />
</path>

<target name="init">
<mkdir dir="${obj-dir}" />
</target>

<target name="compile" depends="init">
<javac srcdir="${src-dir}"
destdir="${obj-dir}"
>

<classpath refid="classpath.base" />
</javac>
</target>
<target name="clean-compile">
<delete verbose="${TALK}" dir="${obj-dir}" />
</target>

<target name="run" depends="compile">
<java classname="Main"
fork="true">

<classpath refid="classpath.run" />
</java>
</target>

<target name="dist">
<jar destfile="distrib.jar"
basedir="."
includes="build.xml,src/**,cfg/**"
excludes="src/prj.el"
/>

</target>
<target name="clean-dist">
<delete VERBOSE="${TALK}" file="distrib.jar"/>
</target>

<target name="all" depends="run" />
<target name="clean" depends="clean-compile,clean-dist" />

</project>

<!-- $Id: build.xml,v 1.6 2003/07/07 19:29:01 dwight Exp $ -->

And here is where all the action is. We start with a Configuration object that specifies how Hibernate will interact with the underlying Database and add a mapping for the hb.Keyword.class (hibernate will load the file hb/Keyword.hbm.xml from the CLASSPATH). Then we create the Session that acts as a service for persistent objects (via a SessionFactory). Finally, we create a Transaction and save a Keyword object via the Session object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
// $Id: Main.java,v 1.3 2003/07/07 19:09:33 dwight Exp $
//
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.HibernateException;

import hb.*;


public class Main {

public static void main ( String[] args )
throws MappingException,
HibernateException
{

Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );

SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();

Transaction tx = null;
Keyword kw = new Keyword( 1,"red" );
try {
tx = session.beginTransaction();
session.save( kw );
tx.commit();
}
catch ( HibernateException he ) {
if ( tx != null ) tx.rollback();
throw he;
}
finally {
session.close();
}
}
}

Now let’s take a look at our configuration files. First, the hibernate.properties file. This specifies that we’ll be using HSqlDB and how the application should connect to HSqlDB server instance.

1
2
3
4
5
6
7
8
9
10
11
12
#
#
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:hsql:\//localhost
hibernate.connection.username=user
hibernate.connection.password=user
hibernate.connection.pool_size=2
#
hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect
#
# $Id: hibernate.properties,v 1.1 2003/07/05 21:39:57 dwight Exp $
#

And finally, the mapping from the hb/Keyword class to the KEYWORDS database table (I’m skipping the Keyword.java file because it’s mostly boilerplate: 2 instance variables, 2 constructors, 2 pairs of get/set methods. Hibernate requires accessors and mutators for persistent fields and a default constructor). The use of the identity generator requires that the KEYWORDS.id column be defined as type identity – you’ll need to look at Hibernate Identity Columns and Sequences to use a database other than HSqlDB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">


<hibernate-mapping>
<!-- table created by: CREATE TABLE KEYWORDS ( ID IDENTITY, NAME VARCHAR(25) ); -->

<class name="hb.Keyword"
table="keywords">


<id name="id"
type="integer"
column="id">

<generator class="identity"/>
</id>

<property name="name"
column="NAME"
not-null="true"
unique="true"
/>

</class>

</hibernate-mapping>

<!-- $Id: Keyword.hbm.xml,v 1.3 2003/07/08 19:18:30 dwight Exp $ -->

Warning: this application persists keyword objects without regard for any existing persistent keyword objects. Consequently, another row will be added to the database with each execution. We’ll take a look at fixing that later.

With that warning out of the way, you’re ready to get the example distribution and go to town. Here is the output from a sample run on my system (somewhat modified for brevity and format):

1
$ ant
Buildfile: build.xml

init:

compile:

run:
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>

     [java] INFO: Hibernate 2.0.1
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: loaded properties from resource hibernate.properties:
                                  { ... }
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: using CGLIB reflection optimizer
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: JVM proxy support: true
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Configuration addClass
     [java] INFO: Mapping resource: hb/Keyword.hbm.xml
     [java] Jul 9, 2003 7:42:41 AM net.sf. ... .Binder bindRootClass
     [java] INFO: Mapping class: hb.Keyword -> keywords
     [java] Jul 9, 2003 7:42:42 AM net.sf. ... .SessionFactoryImpl <init>

     [java] INFO: building session factory
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .Dialect <init>
     [java] INFO: Using dialect: net.sf.hibernate.dialect.HSQLDialect
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: Hibernate connection pool size: 2
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: using driver: org.hsqldb.jdbcDriver at URL:
                                  jdbc:hsqldb:hsql:\//localhost
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: connection properties: {user=user, password=user}
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Use outer join fetching: false
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Use scrollable result sets: true
     [java] Jul 9, 2003 7:42:46 AM net.sf. ...
                                  .SessionFactoryObjectFactory addInstance
     [java] INFO: no JDNI name configured
     [java] Jul 9, 2003 7:42:46 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Query language substitutions: {}

all:

BUILD SUCCESSFUL
Total time: 18 seconds

Now that we’re here, I have to say that getting started with Hibernate wasn’t so bad. It just wasn’t obvious that it would turn out to be this simple.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.

I'll pay for a better URL

The first thing that I noticed about the TypePad beta is that I got to choose the url: http://firststep.blogs.com. Over time, my fingers have developed some pretty good muscle memory for 0118153. But just try telling someone that your blog is at: http://radio.weblogs.com/0118153 - it took me weeks to be able to remember it.

And while it’s not like I’d definitely dump Radio for TypePad to get a better url, I have to say that it is a factor. So just in case UserLand is listening, I’m willing to pay an extra $10/year for a better url. Heck, I’m willing to pay an extra $20-$30/year + registration fees for my own domain name.

You gotta do what you gotta do

Tim Oren points out that sometimes you need VC money. And I think that TypePad is a perfect example - because you need a hosted product to compete in today’s weblog market. I didn’t even consider Moveable Type when I was looking at weblog products because I was looking for a hosted service. And I have the skills to select a hosting plan, obtain a domain name, and install Moveable Type. If you can’t reach me, then who can you reach?

And hosting takes money. Nothing will kill a new service faster than poor responsiveness and lightning quick word of mouth. So you gotta have solid servers and you gotta have a big network pipe. And you gotta have the money.