Java Immutable object Relational Mapper is a unique Java SQL ORM that allows you to CRUD immutable objects. It is a good match for highly concurrent message driven architectures.
How JIRM is different
- CRUD truly Immutable POJOs. That is all fields are final with a constructor that fills them.
- Uses JPA annotations to help map the SQL ResultSet to your POJOs.
- READs hierarchy of Immutable POJOs. That is
@ManyToOne
s are loaded eagerly, but for WRITE we only write the top POJO. - Once the POJO is loaded there is no magic. It is not "enhanced". It is safe to deserialize or cache especially because they are immutable.
- Manually do one-to-many (i.e. collections) which IMHO is the right way to do it (because there is nothing worse than accidentally pulling 1000 items).
- No hidden lazy loading.
- Threadsafe - Most of the library is threadsafe.
- Stateless (like Ajave EBean... i.e. no session factory).
- Fluent API.
- Or you can use SQL with IMHO the best SQL Placeholder templates.
- Sits nicely on top of other JDBC wrappers like Spring JDBC.
- Let your JDBC wrapper handle transactions - e.g. compile time Transaction Support through AspectJ (Through Spring JDBC).
JIRM does all of this and more!
There was also someone looking for one here: http://stackoverflow.com/questions/2698665/orm-supporting-immutable-classes
The current version of jirm in the maven central repository is: 0.0.8
If you would like full usage of the ORM, Spring is currently the only JDBC Wrapper supported.
<dependency>
<groupId>co.jirm</groupId>
<artifactId>jirm-spring</artifactId>
<version>${jirm.version}</version>
</dependency>
Alternatively if you want to use only the SQL Placeholder templates
you only need jirm-core
.
You need a JirmFactory to use Jirm. Right now Spring JDBC is the only implementation but it is
trivial to support other JDBC wrappers by implementing SqlExecutor
interface.
Why choose Spring JDBC? - Because it's an extremely mature JDBC wrapper that does most things correctly (exception handling and transactions).
Spring Config:
<bean class="co.jirm.spring.SpringJirmFactory" id="jirmFactory" />
Now in your Spring components you can simply do:
@Autowired //or however you do your wiring.
private JirmFactory jirmFactory;
Now you can create a threadsafe JirmDao
for your Immutable POJO like:
JirmDao<MyBean> dao = jirmFactory.daoFor(MyBean.class);
The JirmDao
allows you to CRUD immutable POJOs. Immutable POJOs have all of their member fields
final
and the fields themselves should be immutable objects. Immutable POJOs require a constructor to instantiate
their member fields.
Because immutable objects require constructor based loading of fields we need to make a constructor with all the fields from table (and/or many-to-one child tables... more on that later).
Unfortunately the JVM has some limitations on reflection of constructor based arguments so you will have to annotate
your constructor with either JDK's @ConstructorProperties
or Jackson's @JsonCreator
and @JsonProperty
public class TestBean {
@Id
private final String stringProp;
private final long longProp;
@Column(name="timets")
@NotNull
private final Calendar timeTS;
@JsonCreator
public TestBean(
@JsonProperty("stringProp") String stringProp,
@JsonProperty("longProp") long longProp,
@JsonProperty("timeTS") Calendar timeTS ) {
super();
this.stringProp = stringProp;
this.longProp = longProp;
this.timeTS = timeTS;
}
public String getStringProp() {
return stringProp;
}
public long getLongProp() {
return longProp;
}
public Calendar getTimeTS() {
return timeTS;
}
}
Our SQL Table for the bean might be something like (Postgres):
CREATE TABLE test_bean
(
string_prop text NOT NULL,
long_prop bigint,
timets timestamp without time zone,
CONSTRAINT string_prop_key PRIMARY KEY (string_prop)
);
Let's see some CRUD of our immutable TestBean
JirmDao<TestBean> dao = daoFactory.daoFor(TestBean.class);
// You can insert, delete, update, etc...
String id = randomId();
TestBean testBean = new TestBean(id, 1L, Calendar.getInstance());
//insert
dao.insert(testBean);
//Or batch insert 200 beans at a time
Iterator<TestBean> testBeanIterator = other.iterator();
dao.insert(testBeanIterator, 200);
//reload
TestBean reload = dao.findById(id);
//or
reload = dao.reload(testBean);
//Explictly update one field.
dao.update()
.set("longProp", 100L)
.where().property("stringProp").eq(id)
.execute();
//Of course you could update the entire object which will take advantage
//of opportunistic locking if you have @Version
TestBean updateBean = new TestBean(testBean.getId(), 2L, Calendar.getInstance());
dao.update(updateBean).execute();
//Or exclude a field from update
dao.update(updateBean).exclude("longProp").execute();
//delete
dao.deleteById(id);
//or
dao.delete()
.where().property("stringProp").eq(id)
.execute();
//Now let's select some TestBeans.
List<TestBean> list =
dao.select().where()
.property("longProp", 1L)
.property("stringProp").eq("blah")
.orderBy("longProp").desc()
.limit(100)
.offset(10)
.query()
.forList();
When the going gets tough JIRM says write SQL.
JIRM's select, update, and delete builders (fluent api) are generally for convenience of simply tasks.
That is because update, delete and insert are pretty simple one table operations.
Also most selecting only requires inner joining @ManyToOne
children.
As soon as it gets complicated we recommend you write SQL. Luckily JIRM provides awesome support for that.
Besides TestBean
mentioned earlier lets say we have another table/object called ParentBean
.
public class ParentBean {
@Id
private final String id;
@ManyToOne(targetEntity=TestBean.class, fetch=FetchType.EAGER)
private final TestBean test;
//... snip ...
}
Now we can select ParentBean
using plain SQL by making a SQL file in the class path we'll call it select-parent-bean.sql
.
SELECT parent_bean.id AS "id",
test.string_prop AS "test.stringProp",
test.long_prop AS "test.longProp",
test.timets AS "test.timeTS"
FROM parent_bean
INNER JOIN test_bean test ON test.string_prop = parent_bean.test
WHERE test.string_prop = 'test' -- {testName}
AND test.long_prop = 100 -- {testAmount}
Yes the above is real SQL without any placeholders that would break normal SQL parsing. We use comments on the end of the line to indicate a place holder. You can read more about it here.
Besides the comment placeholders the other thing to notice is the use of result column labels for property paths.
By using AS "dottedPropertyPath"
gives JIRM clues on how to map the flat ResultSet
back to a hierarchical object.
Now here is the Java:
JirmDao<ParentBean> dao = jirmFactory.daoFor(ParentBean.class);
List<ParentBean> results =
dao.getSelectBuilderFactory().sqlFromResource("select-parent-bean.sql")
.bind("testName", "test")
.bind("testAmount", 100)
.query()
.forList();
JIRM's SQL Placeholder Parser can also be used independently of JIRM's ORM functionality.