Java 支持不可变类的ORM

Java 支持不可变类的ORM,java,hibernate,orm,scala,types,Java,Hibernate,Orm,Scala,Types,哪个ORM支持不可变类型的域模型 我想编写如下类(或类似Scala的类): ORM必须用构造函数初始化对象。因此,可以检查构造函数中的不变量。对initialize的默认构造函数和字段/设置器访问不足,使类的实现复杂化 应支持使用集合。如果集合已更改,则应从用户角度创建副本。(使旧的集合状态过时。但用户代码仍然可以处理(或至少读取)它。)与此类似 一些关于动机的话。假设您有一个FP样式的域对象模型。现在您希望将其持久化到数据库。你是谁干的?你想以一种纯粹的功能性风格尽可能多地去做,直到邪恶的一面

哪个ORM支持不可变类型的域模型

我想编写如下类(或类似Scala的类):

ORM必须用构造函数初始化对象。因此,可以检查构造函数中的不变量。对initialize的默认构造函数和字段/设置器访问不足,使类的实现复杂化

应支持使用集合。如果集合已更改,则应从用户角度创建副本。(使旧的集合状态过时。但用户代码仍然可以处理(或至少读取)它。)与此类似

一些关于动机的话。假设您有一个FP样式的域对象模型。现在您希望将其持久化到数据库。你是谁干的?你想以一种纯粹的功能性风格尽可能多地去做,直到邪恶的一面出现。例如,如果域对象模型不是不可变的,则可以不在线程之间共享对象。您必须复制、缓存或使用锁。因此,除非您的ORM支持不可变类型,否则您在选择解决方案时会受到限制。

Hibernate有注释


而且。

好的,没有完全按照您的意愿支持此功能的.NET ORM。但是您可以看看BLTookit和LINQtoSQL——它们都提供了比较更新语义,并且总是在具体化时返回新对象。这几乎是你所需要的,但我不确定那里是否有收藏品

顺便说一句,你为什么需要这个功能?我了解纯函数式语言和纯可模拟对象的好处(例如,完全的线程安全性)。但在ORM的情况下,您对这些对象所做的所有事情最终都会转换为一系列SQL命令。因此,我承认在这里使用这些对象的好处是显而易见的。

虽然不是真正的ORM,但MyBatis可能能够做到这一点。不过我没有试过


您可以使用Ebean和OpenJPA实现这一点(我认为您可以使用Hibernate实现这一点,但不确定)。ORM(Ebean/OpenJPA)将生成一个默认构造函数(假设bean没有),并实际设置“final”字段的值。这听起来有点奇怪,但最终字段并不总是严格地说是最终字段。

更新:我创建了一个项目,专门解决这个问题,名为:

在实现了我自己的使用和对象映射器之后,我发现了这个问题。基本上,我只需要一些简单的最小SQL不可变对象映射

简而言之,我只是使用从数据库来回映射对象。我使用JPA注释只是为了元数据(比如列名等等)如果有人感兴趣,我会把它清理干净,放到github上(目前它只存在于我的创业公司的私人回购中)

下面是一个粗略的概念,它是如何工作的,这里是一个示例bean(注意所有字段是如何最终的):

下面是它的样子(请注意,它主要将数据委托给Springs ColumnMapRowMapper,然后使用Jackson的objectmapper):

公共类SqlObjectRowMapper实现了RowMapper{
私有最终SqlObjectDefinition;
私有最终列映射器映射器映射器映射器;
私有最终ObjectMapper ObjectMapper;
公共SqlObjectRowMapper(SqlObjectDefinition定义,ObjectMapper ObjectMapper){
超级();
这个定义=定义;
this.mapRowMapper=新的SqlObjectMapRowMapper(定义);
this.objectMapper=objectMapper;
}
公共SqlObjectRowMapper(k类){
这(SqlObjectDefinition.fromClass(k),新的ObjectMapper());
}
@凌驾
public T mapRow(ResultSet rs,int rowNum)抛出SQLException{
Map m=mapRowMapper.mapRow(rs,rowNum);
返回objectMapper.convertValue(m,definition.getObjectType());
}
}
现在,我只需要使用SpringJDBCTemplate并为其提供一个流畅的包装器。以下是一些例子:

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

}
@之前
public void setUp()引发异常{
dao=新的SqlObjectDao(新的JdbcTemplate(ds),TestBean.class);
}
@试验
public void testAll()引发异常{
TestBean t=newtestbean(IdUtils.generateRandomUUIDString(),2L,Calendar.getInstance());
dao.插入(t);
List=dao.queryForListByFilter(“stringProp”、“hello”);
List otherList=dao.select().where(“stringProp”,“hello”).forList();
资产质量(列表、其他列表);
long count=dao.select().forCount();
assertTrue(计数>0);
TestBean newT=newTestBean(t.getStringProp(),50,Calendar.getInstance());
dao.update(newT);
TestBean重载=dao.reload(newT);
assertTrue(重新加载!=牛顿);
assertTrue(重新加载的.getStringProp().equals(newT.getStringProp());
assertNotNull(列表);
}
@试验
public void testAdding()引发异常{
//这将执行更新测试\u bean SET longProp=longProp+100
inti=dao.update().add(“longProp”,100.update();
资产真实性(i>0);
}
@试验
public void testRowMapper()引发异常{
List craps=dao.query(“从test\u bean limit中选择string\u prop作为名称?”,Crap.class,2);
System.out.println(craps.get(0.getName());
craps=dao.query(“从test\u bean limit中选择string\u prop作为名称?”)
.连同(2)
.forList(垃圾级);
Crap c=dao.query(“从test\u bean limit中选择string\u prop作为名称?”)
.连同(1)
.forObject(垃圾类);
任选缺席
=dao.query(“从test\u bean中选择string\u prop作为名称,其中string\u prop=?limit?”)
.用(“从不”)
.连同(1)
.可选(垃圾级);
assertTrue(!缺席.isPresent());
}
公共静态类垃圾{
私有最终字符串名;
@JsonCreator
公共垃圾(@JsonProperty(“name”)字符串名){
超级();
this.name=名称;
}
公共字符串getName(){
返回名称;
}
}
公告
//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;
    }

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

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

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