2010-04-23 26 views
28

ORM nào hỗ trợ mô hình miền bất biến loại?ORM hỗ trợ các lớp không thể thay đổi

Tôi muốn viết các lớp học như sau (hoặc tương đương Scala):

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

    A(B b) { 
    //init c 
    } 

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

Các ORM phải khởi tạo các đối tượng với các nhà xây dựng. Vì vậy, nó có thể kiểm tra bất biến trong constructor. Mặc định constructor và field/setter access để intialize không đủ và làm phức tạp việc triển khai của lớp.

Làm việc với bộ sưu tập phải được hỗ trợ. Nếu một bộ sưu tập được thay đổi, nó sẽ tạo một bản sao từ phối cảnh của người dùng. (Hiển thị trạng thái cũ của bộ sưu tập cũ. Nhưng mã người dùng vẫn có thể hoạt động (hoặc ít nhất là đọc).) Giống như công việc persistent data structures.

Một số từ về động lực. Giả sử bạn có một mô hình đối tượng miền kiểu FP. Bây giờ bạn muốn duy trì điều này với cơ sở dữ liệu. Bạn làm nghề đó Bạn muốn làm nhiều như bạn có thể trong một phong cách chức năng thuần túy cho đến khi các hiệu ứng bên xấu đi vào. Nếu mô hình đối tượng miền của bạn không phải là bất biến, bạn có thể không chia sẻ các đối tượng giữa các chủ đề. Bạn phải sao chép, bộ nhớ cache hoặc sử dụng khóa. Vì vậy, trừ khi ORM của bạn hỗ trợ các loại bất biến của bạn bị ràng buộc trong lựa chọn giải pháp của bạn.

Trả lời

4

Hibernate có chú thích @Immutable.

here is a guide.

+2

Theo tôi biết Hibernate không hỗ trợ các trường cuối cùng và "hàm tạo đầu phun" của các giá trị. –

+1

bạn có cần 'final' không? Bạn chỉ có thể giới hạn nó với việc thiếu người định cư. – Bozho

+3

Sẽ không thay đổi nếu không. Tôi không thích các khung công tác để hạn chế lựa chọn các tính năng ngôn ngữ của mình. –

0

AFAIK, không có ORM cho .NET hỗ trợ tính năng này chính xác như bạn muốn. Nhưng bạn có thể xem BLTookit và LINQ to SQL - cả hai đều cung cấp các ngữ nghĩa cập nhật theo từng so sánh và luôn trả về các đối tượng mới về hiện thực hóa. Đó là gần như những gì bạn cần, nhưng tôi không chắc chắn về các bộ sưu tập ở đó.

Btw, tại sao bạn cần tính năng này? Tôi biết về các ngôn ngữ chức năng thuần túy & lợi ích của các đối tượng hoàn toàn imutable (ví dụ: hoàn toàn an toàn luồng). Nhưng trong trường hợp với ORM tất cả những thứ bạn làm với các đối tượng như vậy cuối cùng cũng được chuyển đổi thành một chuỗi các lệnh SQL. Vì vậy, tôi thừa nhận những lợi ích của việc sử dụng các đối tượng như vậy là hơi ở đây.

+0

Xem chỉnh sửa. Vấn đề không nằm trong phần kiên trì nhưng trong các lớp xây dựng trên các đối tượng miền. –

3

Mặc dù không phải là thực ORM, MyBatis có thể thực hiện việc này. Tôi đã không thử nó mặc dù.

http://mybatis.org/java.html

+0

Vâng, MyBatis hỗ trợ các lớp học không thay đổi khá tốt: bạn thậm chí có thể đặt trường của bạn để cuối cùng. Xem phần xây dựng để lập bản đồ chúng: http://www.mybatis.org/core/sqlmap-xml.html – jomohke

0

Bạn có thể làm điều này với Ebean và OpenJPA (và tôi nghĩ bạn có thể làm điều này với Hibernate nhưng không chắc chắn). ORM (Ebean/OpenJPA) sẽ tạo ra một hàm tạo mặc định (giả định rằng bean không có một) và thực sự thiết lập các giá trị của các trường 'cuối cùng'. Điều này nghe có vẻ hơi kỳ quặc nhưng các trường cuối cùng không phải lúc nào cũng nghiêm túc cuối cùng.

7

UPDATE: Tôi tạo ra một dự án tập trung vào việc giải quyết vấn đề này được gọi là JIRM: https://github.com/agentgt/jirm

Tôi chỉ tìm thấy câu hỏi này sau khi thực hiện của riêng tôi sử dụng Spring JDBCJackson Object Mapper. Về cơ bản tôi chỉ cần một số tối thiểu SQL < -> ánh xạ đối tượng bất biến.

Nói tóm lại tôi chỉ sử dụng Springs RowMapperJackson's ObjectMapper để ánh xạ các đối tượng qua lại từ các cơ sở dữ liệu. Tôi sử dụng chú thích JPA chỉ dành cho siêu dữ liệu (như tên cột, v.v ...). Nếu mọi người quan tâm tôi sẽ làm sạch nó và đặt nó trên github (ngay bây giờ nó chỉ trong repo riêng của khởi động của tôi).

Dưới đây là một ý tưởng thô thế nào nó hoạt động ở đây là một đậu dụ (thông báo như thế nào tất cả các lĩnh vực là cuối cùng):

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

} 

đây những gì RowMapper trông giống như (chú ý nó chủ yếu delegats để Springs ColumnMapRowMapper và sau đó sử dụng objectmapper 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()); 
    } 

} 

Bây giờ tôi chỉ mất mùa xuân JdbcTemplate và cho nó một thạo wrapper. Dưới đây là một số ví dụ:

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

} 

Lưu ý ở trên mức độ dễ dàng để ánh xạ bất kỳ truy vấn nào vào POJO bất biến. Đó là bạn không cần nó 1-to-1 của thực thể để bàn. Cũng lưu ý việc sử dụng Guava's optionals (truy vấn cuối cùng .. cuộn xuống). Tôi thực sự ghét cách ORM ném ngoại lệ hoặc trả lại null.

Hãy cho tôi biết nếu bạn thích nó và tôi sẽ dành thời gian cho nó trên github (chỉ teste với postgresql). Nếu không với thông tin ở trên, bạn có thể dễ dàng thực hiện của riêng bạn bằng cách sử dụng Spring JDBC. Tôi bắt đầu thực sự đào nó bởi vì những vật thể bất biến dễ hiểu và dễ hiểu hơn.

+0

Tôi muốn được nhìn thấy nguồn. Hãy đặt nó trên GitHub! Tò mò sử dụng 'Tùy chọn'. Tôi không chắc tại sao điều này tốt hơn là sử dụng 'null'? –

+0

@LukasEder Cuối cùng tôi cũng đã làm việc đó: https://github.com/agentgt/jirm –

+0

Rất hay. Tôi sẽ phải có một cái nhìn gần hơn càng sớm càng tốt! –

0

SORM là một ORM Scala mới thực hiện chính xác những gì bạn muốn. Mã bên dưới sẽ giải thích rõ hơn bất kỳ từ nào:

// 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ập nhật hoạt động như thế nào? Cho phép nói rằng tôi truy vấn một đối tượng bất biến "a" và làm một cái gì đó như b = a.map (f: MyType => MyType) để nhận "bản sao" cập nhật "a".Bây giờ "a" đã lỗi thời và "b" được cập nhật và tôi muốn lưu trữ "b" làm cập nhật "a". Tôi đoán Db.save (b) sẽ dẫn đến một Chèn và bây giờ "a" và "b" đều được lưu trữ. Tôi muốn yêu cầu SORM thay thế "a" bằng "b". Lý tưởng nhất là không giao dịch với id, vì SORM muốn id là cơ sở dữ liệu nội bộ (Không phải là ý tưởng tồi tệ nhất nếu bạn hỏi tôi). – user573215

+0

@ user573215 Nếu 'val b = a.copy (..)' thì 'Db.save (b)' sẽ dẫn đến các hàng thích hợp nhận ** cập nhật **, không được chèn vào. Một hành vi như vậy được thực hiện với sự trợ giúp của [Tính trạng bền bỉ] (http://sorm-framework.org/Documentation.html#persisted_trait_and_ids), về cơ bản là có thể mang thông tin nhận dạng một cách rõ ràng với các thực thể. –

+0

Nhìn vào ví dụ của bạn, tôi thấy mã Scala bất biến được hỗ trợ bởi một cơ sở dữ liệu có thể thay đổi. Hãy tưởng tượng rằng một đĩa đơn được tìm thấy ở Dire Straits chơi Metal. Có bao nhiêu hàng trong bảng Nghệ sĩ trong cơ sở dữ liệu sau đó họ sẽ mất? Bạn có thể truy vấn những gì Dire Straits chơi trước khi single Metal được tìm thấy, so với những gì chúng ta biết họ chơi bây giờ? Phiên bản "bất biến" nào của Dire Straits sẽ giành chiến thắng, hoặc họ sẽ trở thành hai ban nhạc? – GlenPeterson

Các vấn đề liên quan