My favorites | Sign in
Project Home Downloads Wiki Issues Source
Project Information
Members
Featured
Downloads
Links

Another ORM for SimpleDB?

This is a simple java-based object-relational mapping (ORM) framework, targeting people who used to develop against Hibernate ORM. Knowledge of Hibernate is not required, but will definitely help. What is wrong with simplejpa? Nothing wrong, it is a great project and please check it out. I am not a fan of JPA and my goal was to make this as light as possible with as less dependencies as possible. This framework is not perfect. Frankly, I would prefer to have SimpleDB jdbc driver with SimpleDB Hibernate Dialect, but too lazy to do that. Maybe someone will act on this idea.

Some Features:

  1. Compatible with existing hibernate mapping files (.hbm)
  2. Lightweight. I am not a fan of JPA. I also believe that Annotations defeat the purpose of POJO. Externalizing mappings into XML makes it easier to browse the mappings as well as allows using generated JAXB classes, for example, as domain objects.
  3. Removed lazy loading (CGLib dependency). Since I am used to initializing all the lazy beans through Hibernate initialization utility, I decided to put loading burden on the utility itself.
  4. Supports Amazon S3 for large storage.
  5. Supports Ehcache and Memcached as Second Level Cache.
  6. Many-to-Many relationship is used in conjunction with multi-valued attributes. This removes a need in a many-to-many tables. I really like this one.
  7. Supports logical table names, allowing one domain to store an entire schema. This might be useful for small projects where data load is small. This design allows creating INNER JOIN queries, assuming all column names are unique.
  8. Supports simple query building.
  9. Will support record distribution throughout domains to increase performance for large databases. I am still not sure if this is a good place for this functionality.

Getting Started:

Note: this projects depends on netflexity-amazonws-simpledb project, which provides client API to Amazon SimpleDB.

I suggest to start Amazon SimpleDB modeling with MySQL or any other relational database that you are the most familiar and productive with. Create the model. Make sure to use VARCHAR for primary and foreign keys. Make sure that you have no composite primary keys; this is the recommended approach for normalization anyway. Reverse-engineer your schema with Hibernate Tools. Make minor adjustments to support Domains and Logical names if necessary. Create DAO layer with SimpleDB ORM and have fun doing it!

Usage Example

// Find all party objects.
SimpleDBSession simpledbSession = (SimpleDBSession) context.getBean("simpledbSessionFactory");
Set<Party> parties = simpledbSession.findAll(Party.class, "companyName");
for (Party nfxParty : parties) {
	System.out.println(RecordUtil.toString(simpledbSession.getMetadata(Party.class), nfxParty));
}


// Add User with many-to-one account and 2 roles, setup as many-to-many. Note that
// many-to-many is mapped to Item Attributes with multiple values, which is very convenient :)
Role role = new Role();
role.setName("Administrator 3");

simpledbSession.save(role);

Role role2 = new Role();
role2.setName("Developer 3");

simpledbSession.save(role2);

Account account = new Account();
account.setId("3ca5b4b8-5d0b-4ed0-8908-bd6f046a3581");

User user = new User();
user.setUsername("maxim");
user.setPassword("password1");
user.setStatus("A");
user.setChangeBy("Somebody");
user.setChangeDate(System.currentTimeMillis());
user.setNfxAccount(account);
user.getNfxRoles().add(role);
user.getNfxRoles().add(role2);

simpledbSession.save(user);


// Test In clause.
Predicate<Role> predicate = Predicates.getInPredicate(Role.class, simpledbSession.getFactory().getMapping(), "name", new String[]{"Administrator 3", "Developer 3"});
List<Role> roles = simpledbSession.select(predicate, new String[]{});
for (Role role : roles) {
	System.out.println(RecordUtil.toString(simpledbSession.getMetadata(Role.class), role));
}


// Load all lazy dependencies of user objects.
User user = simpledbSession.find(User.class, "e78cb0c6-0de9-4df5-9f07-1391f112ab5c");
simpledbSession.populate(user);


// Test Count.
int parties = simpledbSession.count(new TypeFilter<Party>(Party.class, simpledbSession.getFactory().getMapping()));
System.out.println(parties + " parties found!");


// Test DaoSupport to make easier writing Daos.
RoleDaoImpl roleDao = (RoleDaoImpl)context.getBean("roleDao");
Role admin3 = roleDao.find("Administrator 3");

Spring Configuration

This is an example of SimpleDB ORM setup with Ehcache as a 2nd level cache. There is a reference to simpledbDataSource, which is a SimpleDB client for Amazon SimpleDB that you can find here. mappingResource property points to sample.xml, which is a hibernate-like configuration file, which is 99% like .hbm.xml files, but with a different root element.

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
	<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>

<bean id="ehcacheFactory" class="com.netflexitysolutions.amazonws.sdb.orm.cache.EhcacheLevel2CacheFactory">
	<property name="cacheManager" ref="cacheManager"/>
</bean>

<bean id="simpledbSessionFactory" class="com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactoryBean">
	<property name="dataSource" ref="simpledbDataSource"/>
	<property name="cacheFactory" ref="ehcacheFactory"/>
	<property name="mappingResource" value="classpath:sample.xml"/>
	<property name="mappingRulesResource" value="classpath:com/netflexitysolutions/amazonws/sdb/orm/metadata/simpledb-mapping-rules.xml"/>
</bean>


<!-- Dao -->
<bean id="roleDao" class="com.netflexitysolutions.commons.amazonws.sdb.dao.RoleDaoImpl">
	<constructor-arg ref="simpledbSessionFactory"/>
</bean>

Sample Ehcache Configuration

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="ehcache.xsd">

<diskStore path="java.io.tmpdir"/>

<cacheManagerEventListenerFactory class="" properties=""/>

<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=automatic,
                multicastGroupAddress=230.0.0.1,
                multicastGroupPort=4446, timeToLive=1"/><!-- propertySeparator="," -->

<cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>

<defaultCache
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    overflowToDisk="true"
    diskPersistent="false"
    diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU"
    /><!-- diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" -->
</ehcache>

Sample Hibernate-like configuration

Couple things to note:

  • table is used for domain name
  • discriminator-value is used as a logical name (to use a domain per schema)

<?xml version="1.0" encoding="utf-8"?>
<simpledb-mapping>
    <class name="com.netflexitysolutions.amazonws.sample.domain.Party" table="QflexCE" discriminator-value="Party">
        <id name="id" type="java.lang.String">
            <column name="id" />
            <generator class="identity" />
        </id>
        <property name="companyName" type="java.lang.String">
            <column name="company_name" length="128" not-null="true">
                <comment>Company name.</comment>
            </column>
        </property>
        <property name="addressLine1" type="java.lang.String">
            <column name="address_line1" length="128">
                <comment>Address line 1.</comment>
            </column>
        </property>
        <property name="addressLine2" type="java.lang.String">
            <column name="address_line2" length="128">
                <comment>Address line 2.</comment>
            </column>
        </property>
        <property name="city" type="java.lang.String">
            <column name="city" length="48">
                <comment>City name.</comment>
            </column>
        </property>
        <property name="stateProvince" type="java.lang.String">
            <column name="state_province" length="48">
                <comment>State or province.</comment>
            </column>
        </property>
        <property name="postalCode" type="java.lang.String">
            <column name="postal_code" length="16">
                <comment>Postal code.</comment>
            </column>
        </property>
        <property name="countryCode" type="java.lang.String">
            <column name="country_code" length="16">
                <comment>Country code.</comment>
            </column>
        </property>
        <property name="billingContactName" type="java.lang.String">
            <column name="billing_contact_name" length="48">
                <comment>Contact name.</comment>
            </column>
        </property>
        <property name="billingContactPhone" type="java.lang.String">
            <column name="billing_contact_phone" length="16">
                <comment>Contact phone.</comment>
            </column>
        </property>
        <property name="billingContactEmail" type="java.lang.String">
            <column name="billing_contact_email" length="64">
                <comment>Contact email.</comment>
            </column>
        </property>
        <property name="changeBy" type="java.lang.String">
            <column name="change_by" length="32" />
        </property>
        <property name="changeDate" type="java.lang.Long">
            <column name="change_date" />
        </property>
        <set name="nfxAccounts" inverse="true" lazy="false">
            <key>
                <column name="party_id" not-null="true">
                    <comment>Company.</comment>
                </column>
            </key>
            <one-to-many class="com.netflexitysolutions.amazonws.sample.domain.Account" lazy="false"/>
        </set>
    </class>
    <class name="com.netflexitysolutions.amazonws.sample.domain.Account" table="QflexCE" discriminator-value="Account">
        <id name="id" type="java.lang.String">
            <column name="id" />
            <generator class="identity" />
        </id>
        <many-to-one name="nfxParty" class="com.netflexitysolutions.amazonws.sample.domain.Party" fetch="select">
            <column name="party_id" not-null="true">
                <comment>Company.</comment>
            </column>
        </many-to-one>
        <property name="uuid" type="java.lang.String">
            <column name="uuid" length="16" not-null="true">
                <comment>Account auto-generated string id.</comment>
            </column>
        </property>
        <property name="licenseContent" type="java.lang.String">
            <column name="license_content" not-null="true">
                <comment>License content in XML.</comment>
            </column>
        </property>
        <property name="effectiveDate" type="java.lang.Long">
            <column name="effective_date" not-null="true">
                <comment>Date membership becomes valid.</comment>
            </column>
        </property>
        <property name="expirationDate" type="java.lang.Long">
            <column name="expiration_date" not-null="true">
                <comment>Date membership expires.</comment>
            </column>
        </property>
        <property name="creationDate" type="java.util.Date">
            <column name="creation_date" not-null="true">
                <comment>Date account established.</comment>
            </column>
        </property>
        <property name="changeBy" type="java.lang.String">
            <column name="change_by" length="32" />
        </property>
        <property name="changeDate" type="java.lang.Long">
            <column name="change_date" />
        </property>
        <property name="status" type="java.lang.String">
            <column name="status" length="1" not-null="true">
                <comment>A - ACTIVE, I - INACTIVE</comment>
            </column>
        </property>
        <set name="nfxUsers" inverse="true">
            <key>
                <column name="account_id" not-null="true" />
            </key>
            <one-to-many class="com.netflexitysolutions.amazonws.sample.domain.User" />
        </set>
    </class>
    
    <class name="com.netflexitysolutions.amazonws.sample.domain.User" table="QflexCE" discriminator-value="User">
        <id name="id" type="java.lang.String">
            <column name="id" />
            <generator class="identity" />
        </id>
        <many-to-one name="nfxAccount" class="com.netflexitysolutions.amazonws.sample.domain.Account" fetch="select">
            <column name="account_id" not-null="true" />
        </many-to-one>
        <property name="username" type="java.lang.String">
            <column name="username" length="32" not-null="true">
                <comment>Username</comment>
            </column>
        </property>
        <property name="password" type="java.lang.String">
            <column name="password" length="32" not-null="true">
                <comment>Password</comment>
            </column>
        </property>
        <property name="changeDate" type="java.lang.Long">
            <column name="change_date" />
        </property>
        <property name="changeBy" type="java.lang.String">
            <column name="change_by" length="32" />
        </property>
        <property name="status" type="java.lang.String">
            <column name="status" length="1" not-null="true" />
        </property>
        <!--set name="nfxUserRoles" inverse="true">
            <key>
                <column name="user_id" not-null="true" />
            </key>
            <one-to-many class="com.netflexitysolutions.amazonws.sample.domain.Role" />
        </set-->
        <bag name="nfxRoles" lazy="false">
	        <key>
                <column name="role_id" not-null="true" />
            </key>
	        <many-to-many column="name" class="com.netflexitysolutions.amazonws.sample.domain.Role"/>
   		</bag>
    </class>
    
    <class name="com.netflexitysolutions.amazonws.sample.domain.Role" table="Security" discriminator-value="Role">
    	<id name="name" type="java.lang.String">
            <column name="name" />
            <generator class="identity" />
        </id>
    </class>
</simpledb-mapping>
Powered by Google Project Hosting