Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ORM supporting immutable classes

Which ORM supports a domain model of immutable types?

I would like to write classes like the following (or the Scala equivalent):

class A {   private final C c; //not mutable    A(B b) {      //init c   }    A doSomething(B b) {      // build a new A   } } 

The ORM has to initialized the object with the constructor. So it is possible to check invariants in the constructor. Default constructor and field/setter access to intialize is not sufficient and complicates the class' implementation.

Working with collections should be supported. If a collection is changed it should create a copy from the user perspective. (Rendering the old collection state stale. But user code can still work on (or at least read) it.) Much like the persistent data structures work.

Some words about the motivation. Suppose you have a FP-style domain object model. Now you want to persist this to a database. Who do you do that? You want to do as much as you can in a pure functional style until the evil sides effect come in. If your domain object model is not immutable you can for example not share the objects between threads. You have to copy, cache or use locks. So unless your ORM supports immutable types your constrainted in your choice of solution.

like image 739
Thomas Jung Avatar asked Apr 23 '10 12:04

Thomas Jung


1 Answers

UPDATE: I created a project focused on solving this problem called JIRM: https://github.com/agentgt/jirm

I just found this question after implementing my own using Spring JDBC and Jackson Object Mapper. Basically I just needed some bare minimum SQL <-> immutable object mapping.

In short I just use Springs RowMapper and Jackson's ObjectMapper to map Objects back and forth from the database. I use JPA annotations just for metadata (like column name etc...). If people are interested I will clean it up and put it on github (right now its only in my startup's private repo).

Here is a rough idea how it works here is an example bean (notice how all the fields are final):

//skip imports for brevity public class TestBean {      @Id     private final String stringProp;     private final long longProp;     @Column(name="timets")     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;     }  } 

Here what the RowMapper looks like (notice it mainly delegats to Springs ColumnMapRowMapper and then uses Jackson's objectmapper):

public class SqlObjectRowMapper<T> implements RowMapper<T> {      private final SqlObjectDefinition<T> definition;     private final ColumnMapRowMapper mapRowMapper;     private final ObjectMapper objectMapper;       public SqlObjectRowMapper(SqlObjectDefinition<T> definition, ObjectMapper objectMapper) {         super();         this.definition = definition;         this.mapRowMapper = new SqlObjectMapRowMapper(definition);         this.objectMapper = objectMapper;     }      public SqlObjectRowMapper(Class<T> k) {         this(SqlObjectDefinition.fromClass(k), new ObjectMapper());     }       @Override     public T mapRow(ResultSet rs, int rowNum) throws SQLException {         Map<String, Object> m = mapRowMapper.mapRow(rs, rowNum);         return objectMapper.convertValue(m, definition.getObjectType());     }  } 

Now I just took Spring JDBCTemplate and gave it a fluent wrapper. Here are some examples:

@Before public void setUp() throws Exception {     dao = new SqlObjectDao<TestBean>(new JdbcTemplate(ds), TestBean.class);  }  @Test public void testAll() throws Exception {     TestBean t = new TestBean(IdUtils.generateRandomUUIDString(), 2L, Calendar.getInstance());     dao.insert(t);     List<TestBean> list = dao.queryForListByFilter("stringProp", "hello");     List<TestBean> otherList = dao.select().where("stringProp", "hello").forList();     assertEquals(list, otherList);     long count = dao.select().forCount();     assertTrue(count > 0);      TestBean newT = new TestBean(t.getStringProp(), 50, Calendar.getInstance());     dao.update(newT);     TestBean reloaded = dao.reload(newT);     assertTrue(reloaded != newT);     assertTrue(reloaded.getStringProp().equals(newT.getStringProp()));     assertNotNull(list);  }  @Test public void testAdding() throws Exception {     //This will do a UPDATE test_bean SET longProp = longProp + 100     int i = dao.update().add("longProp", 100).update();     assertTrue(i > 0);  }  @Test public void testRowMapper() throws Exception {     List<Crap> craps = dao.query("select string_prop as name from test_bean limit ?", Crap.class, 2);     System.out.println(craps.get(0).getName());      craps = dao.query("select string_prop as name from test_bean limit ?")                 .with(2)                 .forList(Crap.class);      Crap c = dao.query("select string_prop as name from test_bean limit ?")                 .with(1)                 .forObject(Crap.class);      Optional<Crap> absent          = dao.query("select string_prop as name from test_bean where string_prop = ? limit ?")             .with("never")             .with(1)             .forOptional(Crap.class);      assertTrue(! absent.isPresent());  }  public static class Crap {      private final String name;      @JsonCreator     public Crap(@JsonProperty ("name") String name) {         super();         this.name = name;     }      public String getName() {         return name;     }  } 

Notice in the above how easy it is to map any query into immutable POJO's. That is you don't need it 1-to-1 of entity to table. Also notice the use of Guava's optionals (last query.. scroll down). I really hate how ORM's either throw exceptions or return null.

Let me know if you like it and I'll spend the time putting it on github (only teste with postgresql). Otherwise with the info above you can easily implement your own using Spring JDBC. I'm starting to really dig it because immutable objects are easier to understand and think about.

like image 66
Adam Gent Avatar answered Sep 28 '22 19:09

Adam Gent