Подтвердить что ты не робот

ORM, поддерживающий неизменяемые классы

Какой ORM поддерживает модель домена неизменяемых типов?

Я хотел бы написать классы, подобные следующим (или эквивалент Scala):

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

  A(B b) {
     //init c
  }

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

ORM должен инициализировать объект конструктором. Таким образом, можно проверить инварианты в конструкторе. Конструктор по умолчанию и доступ к полям/сеттерам для инициализации недостаточно и усложняют реализацию класса.

Работа с коллекциями должна поддерживаться. Если коллекция изменена, она должна создать копию с точки зрения пользователя. (Предоставление старого состояния коллекции устарело, но код пользователя все еще может работать (или, по крайней мере, читать).) Как и в постоянных структурах данных.

Несколько слов о мотивации. Предположим, у вас есть объектная модель домена типа FP. Теперь вы хотите сохранить это в базе данных. Кто вы это делаете? Вы хотите делать столько, сколько можете, в чисто функциональном стиле, пока не появится эффект злых сторон. Если объектная модель домена не является неизменной, вы можете, например, не делиться объектами между потоками. Вы должны копировать, кэшировать или использовать блокировки. Поэтому, если ваш ORM не поддерживает неизменные типы, которые вы ограничены в своем выборе решения.

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ: Я создал проект, ориентированный на решение этой проблемы JIRM: https://github.com/agentgt/jirm

Я просто нашел этот вопрос после реализации моего собственного использования Spring JDBC и Jackson Объект Mapper. В основном мне просто нужно было немного минимального отображения SQL ↔ неизменяемых объектов.

Короче говоря, я просто использую Springs RowMapper и Jackson ObjectMapper, чтобы отображать объекты взад и вперед из базы данных. Я использую аннотации JPA только для метаданных (например, имя столбца и т.д.). Если люди заинтересованы, я очищу их и поставлю на github (сейчас это только в моем приватном репозитории автозагрузки).

Вот приблизительная идея, как это работает здесь, это пример bean (обратите внимание, как все поля являются окончательными):

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

}

Здесь выглядит RowMapper (обратите внимание, что это главным образом делегаты для SpringsMapRowMapper, а затем использует объект-объект 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());
    }

}

Теперь я просто взял Spring JDBCTemplate и дал ему плавную оболочку. Вот несколько примеров:

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

}

Обратите внимание на то, как легко отобразить любой запрос в неизменяемые POJO. То есть вам не нужно это от 1 до 1 сущности к таблице. Также обратите внимание на использование опций Guava (последний запрос.. прокрутка вниз). Я действительно ненавижу, как ORM либо бросает исключения, либо возвращает null.

Сообщите мне, нравится ли вам это, и я потрачу время на github (только teste с postgresql). В противном случае с информацией, приведенной выше, вы можете легко реализовать свой собственный, используя Spring JDBC. Я начинаю действительно рыть, потому что неизменные объекты легче понять и подумать.

Ответ 3

Хотя это не настоящий ORM, MyBatis может это сделать. Я не пробовал, хотя.

http://mybatis.org/java.html

Ответ 4

AFAIK, ORM для .NET не поддерживает эту функцию, как вам угодно. Но вы можете взглянуть на BLTookit и LINQ на SQL - оба предоставляют семантику обновления по сравнению и всегда возвращают новые объекты при материализации. Это почти то, что вам нужно, но я не уверен, что там есть коллекции.

Кстати, зачем вам эта функция? Я знаю о чистых функциональных языках и преимуществах чисто imutable объектов (например, полная безопасность потоков). Но в случае с ORM все вещи, которые вы делаете с такими объектами, в любом случае преобразуются в последовательность команд SQL. Поэтому я признаю, что преимущества использования таких объектов здесь парообразны.

Ответ 5

Вы можете сделать это с помощью Ebean и OpenJPA (и я думаю, вы можете сделать это с Hibernate, но не уверены). ORM (Ebean/OpenJPA) будет генерировать конструктор по умолчанию (при условии, что bean не имеет одного) и фактически устанавливает значения "окончательных" полей. Это звучит немного странно, но окончательные поля не всегда строго окончательны для каждого.

Ответ 6

SORM - это новый Scala ORM, который делает именно то, что вы хотите. Код ниже объяснит это лучше, чем любые слова:

// 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]