2010-04-23 5 views
28

Qué ORM es compatible con un modelo de dominio de inmutables tipos?ORM apoyo a las clases inmutables

me gustaría escribir clases como la siguiente (o el equivalente Scala):

class A { 
    private final C c; //not mutable 

    A(B b) { 
    //init c 
    } 

    A doSomething(B b) { 
    // build a new A 
    } 
} 

El ORM tiene que inicializa el objeto con el constructor. Por lo tanto, es posible verificar invariantes en el constructor. El acceso predeterminado de constructor y campo/instalador para inicializar no es suficiente y complica la implementación de la clase.

Trabajar con colecciones debe ser apoyada. Si se cambia una colección, debe crear una copia desde la perspectiva del usuario. (Renderizar el antiguo estado de recopilación obsoleto. Pero el código de usuario aún puede funcionar en (o al menos leerlo). Muy parecido al trabajo persistent data structures.

Algunas palabras sobre la motivación. Supongamos que tiene un modelo de objeto de dominio estilo FP. Ahora quiere persistir esto en una base de datos. ¿A quién le haces eso? Desea hacer todo lo que pueda en un estilo funcional puro hasta que aparezca el efecto de lados malvados. Si su modelo de objetos de dominio no es inmutable, puede, por ejemplo, no compartir los objetos entre hilos. Tienes que copiar, guardar en caché o usar bloqueos. Por lo tanto, a menos que su ORM admita los tipos inmutables, estará limitado a su elección de solución.

Respuesta

4

Hibernate tiene la anotación @Immutable.

Y here is a guide.

+2

Por lo que sé, Hibernate no admite los campos finales y la "inyección de constructor" de valores. –

+1

¿necesitas 'final'? Puedes limitarlo con la falta de setters. – Bozho

+3

No sería inmutable de lo contrario. No me gustan los marcos para restringir mi elección de las características del lenguaje. –

0

yo sepa, no hay ORM para .NET que apoyan esta función exactamente como desee. Pero puede echar un vistazo a BLTookit y LINQ to SQL; ambos proporcionan semántica de actualización por comparación y siempre devuelven nuevos objetos en la materialización. Eso es casi lo que necesita, pero no estoy seguro acerca de las colecciones allí.

Por cierto, ¿Por qué necesita esta característica? Conozco los beneficios de idiomas puros funcionales & de objetos puramente imputables (por ejemplo, seguridad de hilo completo). Pero en el caso de ORM, todas las cosas que haces con esos objetos finalmente se transforman en una secuencia de comandos SQL de todos modos. Admito que los beneficios de usar tales objetos son vaporosos aquí.

+0

Ver edición. El problema no está en la parte de persistencia, sino en las capas que se crean en los objetos de dominio. –

3

Aunque no es real ORM, MyBatis puede hacer esto. No lo intenté sin embargo.

http://mybatis.org/java.html

+0

Sí, MyBatis admite clases inmutables bastante bien: incluso puede configurar sus campos para el final. Vea la sección de constructores para mapearlos: http://www.mybatis.org/core/sqlmap-xml.html – jomohke

0

Usted puede hacer esto con Ebean y OpenJPA (y creo que se puede hacer esto con Hibernate, pero no estoy seguro). El ORM (Ebean/OpenJPA) generará un constructor predeterminado (suponiendo que el bean no tenga uno) y realmente establecerá los valores de los campos 'finales'. Esto suena un poco extraño, pero los campos finales no siempre son estrictamente definitivos, por decir.

7

ACTUALIZACIÓN: he creado un proyecto centrado en la solución de este problema llamado JIRM: https://github.com/agentgt/jirm

Acabo de encontrar esta pregunta después de implementar el uso de mi propia Spring JDBC y Jackson asignador de objetos. Básicamente solo necesitaba un mínimo de SQL < -> asignación de objetos inmutables.

En resumen sólo tiene que utilizar Springs RowMapper y Jackson's ObjectMapper a objetos de mapa de ida y vuelta desde la base de datos. Uso las anotaciones JPA solo para metadatos (como el nombre de columna, etc.). Si las personas están interesadas, lo limpiaré y lo pondré en github (en este momento solo está en el repositorio privado de mi startup).

Aquí está una idea aproximada de cómo funciona aquí es un bean de ejemplo (aviso cómo todos los campos son finales):

//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; 
    } 

} 

Aquí lo que el RowMapper parece (lo nota principalmente delegats en Springs ColumnMapRowMapper y luego utiliza objectmapper de Jackson):

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()); 
    } 

} 

Ahora sólo tomó primavera JdbcTemplate y le dio un fluidez envoltura. Aquí hay algunos ejemplos:

@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; 
    } 

} 

Observe en lo anterior lo fácil que es asignar cualquier consulta a POJOs inmutables. Es decir, no lo necesita de 1 a 1 de entidad a tabla. También observe el uso de Guava's optionals (última consulta ... desplácese hacia abajo). Realmente odio cómo ORM arroja excepciones o devuelve null.

Avíseme si le gusta y pasaré el tiempo poniéndolo en github (solo teste con postgresql). De lo contrario, con la información anterior, puede implementar fácilmente su propio uso de Spring JDBC. Estoy comenzando a cavar realmente porque los objetos inmutables son más fáciles de entender y pensar.

+0

Me interesaría ver la fuente. Siéntase libre de ponerlo en GitHub! Curioso uso de 'Opcional'. No estoy seguro de por qué esto es mejor que usar 'null'? –

+0

@LukasEder Finalmente logré hacer eso: https://github.com/agentgt/jirm –

+0

Muy agradable. Tendré que tener una mirada más cercana CUANTO ANTES! –

0

SORM es un nuevo ORM de Scala que hace exactamente lo que usted desea. El siguiente código lo explicará mejor que cualquier palabra:

// Declare a model: 
case class Artist (name : String, genres : Set[Genre]) 
case class Genre (name : String) 

// Initialize SORM, automatically generating schema: 
import sorm._ 
object Db extends Instance (
    entities = Set() + Entity[Artist]() + Entity[Genre](), 
    url = "jdbc:h2:mem:test" 
) 

// Store values in the db: 
val metal = Db.save(Genre("Metal")) 
val rock = Db.save(Genre("Rock")) 
Db.save(Artist("Metallica", Set() + metal + rock)) 
Db.save(Artist("Dire Straits", Set() + rock)) 

// Retrieve values from the db: 
val metallica = Db.query[Artist].whereEqual("name", "Metallica").fetchOne() // Option[Artist] 
val rockArtists = Db.query[Artist].whereEqual("genres.name", "Rock").fetch() // Stream[Artist] 
+0

¿Cómo debería funcionar una actualización? Digamos que consulto un objeto inmutable "a" y hago algo como b = a.map (f: MyType => MyType) para obtener una "copia" actualizada de "a".Ahora "a" está desactualizado y "b" está actualizado y me gustaría almacenar "b" como la actualización de "a". Supongo que Db.save (b) dará como resultado un Insert y ahora se almacenan "a" y "b". Me gustaría decirle a SORM que reemplace "a" por "b". Idealmente sin tener que lidiar con la identificación, porque SORM quiere que la identificación sea una base de datos interna (No es la peor idea si me preguntas). – user573215

+0

@ user573215 Si 'val b = a.copy (..)', 'Db.save (b)' dará como resultado que las filas correspondientes se ** actualicen **, no se inserten. Tal comportamiento se logra con la ayuda de [Rasgo persistente] (http://sorm-framework.org/Documentation.html#persisted_trait_and_ids), que básicamente está allí para llevar de forma transparente la información de identificación con las entidades. –

+0

Mirando su ejemplo, veo un código de Scala inmutable respaldado por una base de datos mutable. Imagínese que se encontró un solo de Dire Straits jugando Metal. ¿Cuántas filas en la tabla Artist en la base de datos tomarían? ¿Sería capaz de consultar qué Dire Straits jugaba antes de que se encontrara el single de Metal, frente a lo que sabemos que juegan ahora? ¿Qué versión "inmutable" de Dire Straits ganaría, o se convertirían en dos bandas? – GlenPeterson

Cuestiones relacionadas