22

We are using JPA to load some stuff from a database. Some entities may have optional relationships between them, e.g.

@Entity
public class First {
    ....
    @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
    @JoinColumns(value = {
        JoinColumn(name = "A_ID", referencedColumnName = "A_ID", insertable = false, updatable = false), 
        JoinColumn(name = "B_ID", referencedColumnName = "B_ID", insertable = false, updatable = false)})
    private Second second;

When this association is present in the database, everything is working fine. When it's not, I'm getting a javax.persistence.EntityNotFoundException
What I want is instead of the exception to have this field as NULL if the association is not present.

I have tried several different things, e.g. using the optional=true in the relationship annotation (which btw is the default option), putting it as Nullable, etc. Nothing seems to do the trick, it seems like all these options are being ignored.

I found a lot of links mentioning this very same problem (and some questions here in stackoverflow) but in all of them the suggestion is to use the @NotFound annotation from Hibernate. But we do NOT want to have any dependencies to Hibernate (we want to keep everything pure JPA).

Does any of you guys know any other way to solve this?

Many thanks for all your help!

4
  • 2
    Yes, become part of the expert group that writes the JPA spec and hit them in the head until it's added... There's no 'JPA' way to do what you want. JPA has only very basic features, if you want anything more complicated, you'll need to use Hibernate or any other ORM specific annotations/configuration. Commented Sep 4, 2012 at 10:52
  • if "second" is never null (as you say to one answer) then the FK to that related record is set (not null), and so it should always find the related object ... or at least does with the JPA impl I use. If "second" is null then it should just return "first.second" as null ... in the JPA impl I use it does. How "there is no JPA way" I don't get Commented Sep 4, 2012 at 18:27
  • When I say "is never null", I mean that it either has a value (when there is smth in the db) or there is an exception (EntityNotFound). By default, the OneToOne annotation assumes optional=true, which works in most implementations of JPA, but not with the one we are using (Hibernate 3.6.4). The optimal thing would be to either upgrade to the latest version of Hibernate (in which I think they fixed this problem) or use a different implementation, but unfortunately none of these options is possible for our project! Commented Sep 5, 2012 at 11:30
  • Ok, thx for the clarification. So the issue is Hibernate-specific and not JPA (general) unlike what the previous commenter implied. Good luck Commented Sep 5, 2012 at 12:12

7 Answers 7

7

Below is an alternative solution for this problem. I had to build on top of an old database where the relations sometimes were corrupt. This is how I solved it by only using JPA.

@PostLoad
public void postLoad(){
    try {
        if(getObject() != null && getObject().getId() == 0){
            setObject(null);
        }
    }
    catch (EntityNotFoundException e){
        setObject(null);
    }
} 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for this suggestion. This doesn't break lazy loading like Hibernates @NotFound(action=NotFoundAction.IGNORE) does. Works with Join fetches too (only makes 1 extra query for the missing entity with id 0, instead of 1 extra query for every related object).
2

I've met the same problem. It's not always reproducable, so I cannot test it, but here are some thoughts:

  1. The instance of your Second class is deleted, while the instance of the First class does not know anything about that.
  2. You need a way to let an instance of First know, when its instance of Second is deleted.
  3. cascade option for removing does not help here.
  4. You may try to use bidirectional relationship, when instance of First exists inside instance of Second. It let you update instance of First via instance of Second before removing second
  5. Bidirectional relationship - is evil. I would suppose in your case, that First - is owner of Second. Don't allow any service delete your Second instance directly. Let service which works with instances of First remove instance of Second. In this case you may make the "second" field nullable first, than remove instance of Second via EntityManager.
  6. You may get exception when execute queries and the 2nd level cache is enabled and query has a hint, which allows to cache its result. I would offer you get result of queries via the following method:
private List<?> getQueryResult(final Query query)
{
    try
    {
        return query.getResultList();
    }
    catch (EntityNotFoundException e)
    {
        return query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.REFRESH).getResultList();
    }
}
  1. If you work with entities via EntityManger, but not via queries, and you get exception because entity is cached, you may invalidate all entities of First in cache when you delete Second.

I'd like to discuss this solution, as I cannot test it and cannot make sute it works. If somebody tries, please let me know.

PS: if somebody has a unit test for hibernate, that reproduces this issue, could you please let me know. I wish to investigate it further.

1 Comment

I tested this solution. It works, so, my problem now is following: I have two Entities, A and B. A has one B while B has many A, so @ManyToOne(fetch = FetchType.LAZY) private B b; @OneToMany private List<A> list; My DB has not foreign-key and in case A has a value not present in B table I want however take this value and print it on a view. How I can do?
2

It happens when you delete the associated entity id. In my case I had Product table depending upon Brand table. I deleted a row or an entity of brand id to which one of the product instance was depending upon.

Comments

2

Try to add the parameter optional = true to your @OneToOne annotation.

1 Comment

Please read the description of the question more carefully...I have already tried that but it's not working! It's a bug with Hibernate (hibernate.onjira.com/browse/ANN-725), it ignores the optional=true.
0

What about adding a test in the correspondent entity class:

public boolean getHasSecond() {
    if (this.Second != null) {
        return true;
    } else {
        return false;
    }
}

Like this, you can check if the relation exists...

1 Comment

This is not possible. second is never null, that's the problem. JPA tries to load it from the db, and if it's not found, then it throws the exception...so this method will never execute! Besides, how can you check if something exists if you don't actually try to load it from the db?
0

Even though late to the party I would also like to point you to another possible cause of this failure. It took me many hours to figure this out when I encountered the very same exception javax.persistence.EntityNotFoundException. That's why I am happy to share the result of my investigation. Basically and somewhat simplified, the database user had permissions to access the primary table (First in the original post), but not the child table (Second in the original post). Using a database browser logged in as the same database user my JPA application has been using to access the database, SELECT * FROM SECOND; always yielded the empty result set, even though I have know that it has actually contained several data. After having granted the necessary permissions the the database user the EntityNotFoundException vanished.

My actual case was more involved, as I have two relations: One explicit @OneToMany relation from a primary table to a cross table and another @ManyToOne one from the entiy which represents the cross table to a third table. Only the permissions to the third table were missing, so JPA found a foreign key to third table in the cross table, but was not able to select the respective row there (probably not explicitly, but in terms of a table join).

Comments

-3

Try to use cascade=CascadeType.ALL in @OneToMay(******).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.