Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: How to map a @OneToOne association which joins by the @Id
PostPosted: Thu Aug 31, 2017 9:49 am 
Newbie

Joined: Tue Aug 22, 2017 10:22 am
Posts: 3
I'm trying to map a OneToOne relationship as an entity identifier : in my schema a person can have 0 or 1 user account, thus user_accounts table's id is a foreign key referencing person's id.

Here's a sample of my class diagram :
Image

Schema :

persons (id, ..., PK(id))
customers (id, ..., PK(id), FK(id) RF persons(id))
user_accounts(id, ..., PK(ID), FK(id) RF persons(id))

Here's a sample of UserAccount class :

Code:
@Entity
    @Table(name = "user_accounts")
    public class UserAccount extends core.business.model.mapping.Entity implements Serializable {

        @Id
        @OneToOne(targetEntity = Person.class, cascade = CascadeType.ALL)
        @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
        private RegisteredUser user;

        @Column(name = "email_address")
        private String emailAddress;

        @Column
        private String hash;

        @OneToOne(mappedBy = "userAccount",
                  cascade  = CascadeType.ALL,
                  fetch    = FetchType.LAZY)
        private Token token;


        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            UserAccount that = (UserAccount) o;

            return user.equals(that.user);
        }

        @Override
        public int hashCode() {
            return user.hashCode();
        }

        UserAccount() {
        }
}


After a userAccount is returned from DAO, its user is null, which cannot be possible according to my model's constraints.

Here's the sql query generated by hibernate :
Quote:
select useraccoun0_.id as id4_25_, useraccoun0_.updated_at as updated_1_25_, useraccoun0_.email_address as email_ad2_25_, useraccoun0_.hash as hash3_25_, useraccoun0_1_.token_id as token_id0_24_ from user_accounts useraccoun0_ left outer join tokens__user_accounts useraccoun0_1_ on useraccoun0_.id=useraccoun0_1_.user_account_id where useraccoun0_.email_address='xxx@xxx.com' and useraccoun0_.hash='e13d212084f7d594e4c540474c0981d99dfe581a'


There is no join on persons table while it is expected to fetch it.

If I move RegistredUser into an @Embeddable class and embed it as an @EmbeddedId, the relationship is properly fetched, but it's quite "ugly" to proceed like so and Hibernate specs say at the end of 2.2.3.1 section :

Quote:
Finally, you can ask Hibernate to copy the identifier from another associated entity. In the Hibernate jargon, it is known as a foreign generator but the JPA mapping reads better and is encouraged


Top
 Profile  
 
 Post subject: Re: xToOne association annotated with @Id
PostPosted: Thu Aug 31, 2017 11:11 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1588
Location: Romania
As I explained in the best way to map a @OneToOne relationship with JPA and Hibernate article, your mapping should look like this:

Code:
@Entity
@Table(name = "user_accounts")
public class UserAccount
    extends core.business.model.mapping.Entity
    implements Serializable {

    @Id
    private Long id;

    @OneToOne
       
    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Person user;

    @Column(name = "email_address")
    private String emailAddress;

    @Column
    private String hash;

    @OneToOne(mappedBy = "userAccount",
              cascade  = CascadeType.ALL,
              fetch    = FetchType.LAZY)
    private Token token;


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserAccount that = (UserAccount) o;

        return user.equals(that.user);
    }

    @Override
    public int hashCode() {
        return user.hashCode();
    }

    UserAccount() {
    }
}


So, this is what I did:

1. I added a dedicated @Id column.
2. The @OneToOne uses @MapsId so that the one-to-one uses the PK as a FK to the parent entity.
3. You should use LAZY associations, not EAGER ones which are bad for performance.
4. I don't think it pays of to use the RegisteredUser for the @oneToOne along with the targetEntity attribute since it brings no extra value to your Domain Model since you are linking to Person entity type anyway.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
 Post subject: Re: How to map a @OneToOne association which joins by the @Id
PostPosted: Thu Aug 31, 2017 11:46 am 
Newbie

Joined: Tue Aug 22, 2017 10:22 am
Posts: 3
I will give it a try even though it's rather redundant imho (userAccount's id is the same as person's and thus will not be used in my application).

here is the spec's example I pointed out :
Code:
@Entity
class MedicalHistory implements Serializable {
  @Id @OneToOne
  @JoinColumn(name = "person_id")
  Person patient;
}

@Entity
public class Person implements Serializable {
  @Id @GeneratedValue Integer id;
}


I would really like to obtain such a result, but there are no example of this pattern on this internet.

Quote:
4. I don't think it pays of to use the RegisteredUser for the @oneToOne along with the targetEntity attribute since it brings no extra value to your Domain Model since you are linking to Person entity type anyway.


Well, RegisteredUser is an interface that contains getUserAccount() and setUserAccount() as not all persons have a user-account.


Top
 Profile  
 
 Post subject: Re: How to map a @OneToOne association which joins by the @Id
PostPosted: Thu Aug 31, 2017 1:05 pm 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1588
Location: Romania
Quote:
I will give it a try even though it's rather redundant imho (userAccount's id is the same as person's and thus will not be used in my application).


That syntax is only valid if you use @IdClass. Check out the Hibernate docs.

Quote:
I would really like to obtain such a result, but there are no example of this pattern on this internet.


You don't find this pattern because it's only supported by Hibernate for @ManyToOne. JPA defines @MapsId for this use case.

Quote:
Well, RegisteredUser is an interface that contains getUserAccount() and setUserAccount() as not all persons have a user-account.


Why not expose the interface on the getter? The field can still use the actual implementation, and it's already encapsulated.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
 Post subject: Re: How to map a @OneToOne association which joins by the @Id
PostPosted: Sat Sep 02, 2017 11:15 am 
Newbie

Joined: Tue Aug 22, 2017 10:22 am
Posts: 3
Quote:
You don't find this pattern because it's only supported by Hibernate for @ManyToOne. JPA defines @MapsId for this use case.


If so, this should be removed from the documentation 2.2.3.1's example because it is quite misleading :
Image


Quote:
Why not expose the interface on the getter? The field can still use the actual implementation, and it's already encapsulated.

I could but I prefer to store the person as an interface and expose the entity class in targetEntity attribute, it's a personal convention, there is no contraindication about this way of proceeding, is there ?


Top
 Profile  
 
 Post subject: Re: How to map a @OneToOne association which joins by the @Id
PostPosted: Sat Sep 02, 2017 11:45 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1588
Location: Romania
Quote:
If so, this should be removed from the documentation 2.2.3.1's example because it is quite misleading :


We already did that. You are reading the old User Guide. Check out the new one.

Quote:
I could but I prefer to store the person as an interface and expose the entity class in targetEntity attribute, it's a personal convention, there is no contraindication about this way of proceeding, is there ?


No, you can do that if you want.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.