Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate - one table multiple entities

Tags:

sql

hibernate

Firstly, I have read Hibernate - One table with multiple entities?.

However, I would like to map two entities to the same table, but I would like both of them to be entities, which I can select from. What I mean:

  • One table: Person (id, name, dateOfBirth, city, street, zipcode).
  • Two Entities: Person (id, name, dateOfBirth), Address (id, city, street, zipcode).

So it's a 1:1 relationship between Entities, but still 1 table in DB.

If I do it using the proposed solution (component keyword) in the above link, I can't query Address directly (I can access it via Person entity). And I want to be able to do

session.createCriteria(Adres.class)

How do I do that?

UPDATE: I tried the one-to-one association between entities, in Address mapping:

<one-to-one name="Person " class="model_mapowanie_xml.Person "/>

and in Person mapping:

<one-to-one name="Address" class="model_mapowanie_xml.Address "/>

Both classes have fields referring to the other one. Selecting records works fine for that. However, how can I add in one transaction a record using both entities? (Id is db-generated)

Address ad = new Address();
ad.setProperty("Sydney");
Person p = new Person();
p.setProperty("John");
p.setAddress(ad);
session.save(p);

and only Person part is saved, the address property remains empty.

like image 850
Dayton Tex Avatar asked Aug 31 '13 12:08

Dayton Tex


2 Answers

This is very simple to achieve with JPA and Hibernate.

Let's assume you are using the following book database table:

enter image description here

Mapping entities

Now, you can map two entities: Book and BookSummary to this table.

First, we will create a BaseBook abstract class which will be extended by all entities:

@MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NaturalId
    @Column(length = 15)
    private String isbn;
 
    @Column(length = 50)
    private String title;
 
    @Column(length = 50)
    private String author;
 
    public Long getId() {
        return id;
    }
 
    public T setId(Long id) {
        this.id = id;
        return (T) this;
    }
 
    public String getIsbn() {
        return isbn;
    }
 
    public T setIsbn(String isbn) {
        this.isbn = isbn;
        return (T) this;
    }
 
    public String getTitle() {
        return title;
    }
 
    public T setTitle(String title) {
        this.title = title;
        return (T) this;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public T setAuthor(String author) {
        this.author = author;
        return (T) this;
    }
}

Now, the BookSummary entity simply extends the BaseBook superclass and adds no additional entity attribute.

@Entity(name = "BookSummary")
@Table(name = "book")
public class BookSummary extends BaseBook<BookSummary> {
 
}

On the other hand, the Book entity extends the BaseBook superclass and maps the properties attribute.

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    name = "jsonb",
    typeClass = JsonBinaryType.class
)
@DynamicUpdate
public class Book extends BaseBook<Book> {
 
    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private String properties;
 
    public String getProperties() {
        return properties;
    }
 
    public Book setProperties(String properties) {
        this.properties = properties;
        return this;
    }
 
    public ObjectNode getJsonProperties() {
        return (ObjectNode) JacksonUtil
            .toJsonNode(properties);
    }
}

Persisting entities

This way, you can persist either a Book entity:

entityManager.persist(
    new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setAuthor("Vlad Mihalcea")
        .setProperties(
            "{" +
                "   \"publisher\": \"Amazon\"," +
                "   \"price\": 44.99," +
                "   \"publication_date\": \"2016-20-12\"," +
                "   \"dimensions\": \"8.5 x 1.1 x 11 inches\"," +
                "   \"weight\": \"2.5 pounds\"," +
                "   \"average_review\": \"4.7 out of 5 stars\"," +
                "   \"url\": \"https://amzn.com/973022823X\"" +
            "}"
        )
);

or a BookSummary:

entityManager.persist(
    new BookSummary()
        .setIsbn("978-1934356555")
        .setTitle("SQL Antipatterns")
        .setAuthor("Bill Karwin")
);

Fetching entities

You can fetch the BookSummary entity:

BookSummary bookSummary = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(BookSummary.class)
    .load("978-9730228236");
 
assertEquals(
    "High-Performance Java Persistence",
    bookSummary.getTitle()
);

or the Book entity if you want:

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");
 
assertEquals(
    "High-Performance Java Persistence, 2nd edition",
    book.getTitle()
);

Conclusion

So mapping multiple entities to the same database table, not only that it allows us to fetch data more efficiently, but it also speeds up the dirty checking process as Hibernate has to inspect fewer entity properties.

The only drawback of using this approach is that you have to make sure you don’t fetch more than one entity type for the same database table record, as otherwise, this can cause inconsistencies when flushing the Persistence Context.

like image 138
Vlad Mihalcea Avatar answered Oct 22 '22 23:10

Vlad Mihalcea


You should be able to do it using @Table annotation. These entites will be treated as different entites but will be mapped onto same table.

@Entity
@Table(name="PERSON_TABLE")
class Person {}


@Entity
@Table(name"PERSON_TABLE")
class Address {}

Edit:

If you want to save both entities in one transaction you either have to explicitly save them using Session or set cascade property to cascade operations on relationship. I guess you want to cascade operations on Address when you do something on Person. See CascadeType if you use annotations.

In your hbm it would look like <one-to-one name="Person" class="model_mapowanie_xml.Person" cascade="all"/>

like image 33
Marek Avatar answered Oct 22 '22 22:10

Marek