I'm trying to persist a User that has a mapping #ManyToOne with UserStatus
but when I do the code below, the hibernate throws PropertyValueException
user.setStatus(new UserStatus(1));
em.persist(user); // ou session.saveAndUpdate(user);
to work I have to do this way:
user.setStatus(em.getReference(UserStatus.class, 1));
em.persist(user); // ou session.saveAndUpdate(user);
I know the first way is possible, but what I don't know is whether I need to configure or call another method (I've already tried saveAndUpdate from Session and still the same)
Does anyone have any idea?
The error message is:
not-null property references a null or transient value
the mapping
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
public UserStatus getStatus() {
return status;
}
This error means "you are referencing a null (not persisted) object" and you have to choice: remove nullable or set #Cascade so UserStatus will per persisted when you do em.persist(user)
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
#Cascade(cascade=CascadeType.ALL)
public UserStatus getStatus() {
return status;
}
EDIT: After various test, using getReference() is the right way to proceed because new UserStatus(1) go for error and should be substituted as getReference(UserStatus.class,ID) to return a proxied instance of UserStatus. Proxied object doesn't hit on database, so SELECT is avoided and the only field setted on UserStatus proxy is the ID, necessary to resolve #ManyToOne relation!
Some useful answer: When to use EntityManager.find() vs EntityManager.getReference()What is the difference between EntityManager.find() and EntityManger.getReference()?
Related
I am confused with the Hibernate session get method. My understanding is that the get method always returns real object and not the proxy object (ref).
But in my program I get the proxy object even when I use the get method.
My scenario:
I have two tables product and company.
Product JPA:
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#Column(name = "prd_id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "price")
private int price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cmp_id")
private Company company;
}
Company JPA:
#Entity
#Table(name = "COMPANY")
public class Company {
#Id
#Column(name = "cmp_id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "revenue")
private int revenue;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private List<Product> products;
}
In my main method I have the following code:
Product product = (Product) session.get(Product.class, 1); // this product has cmp_id 1
System.out.println("Got product");
Company company = (Company) session.get(Company.class, 1);
System.out.println(company instanceof HibernateProxy); // this returns true
I know because of the lazy loading Hibernate has company 1 as proxy object from Product product = (Product) session.get(Product.class, 1);.
But I was expecting (Company) session.get(Company.class, 1); to return normal object.
From the log, I see hibernate does hit the database and get complete data of Company object. This makes it more confusing, if hibernate has all the data why is still returning the proxy object ?
Is my understanding incorrect ? How can ensure session get returns normal object and not a proxy object ?
Hibernate does not hit the database for every call to get.
See JavaDocs for get:If the instance is already associated with the session, return that instance. This method never returns an uninitialized instance.
So, since with session.get(Product.class) there's already an instance of Company in the session (although a proxy) so Hibernate just initializes that proxy (thus the sql you saw).
First of all session.get never returns a proxy object it returns a fully initialized object from the persistent space if it is present there otherwise it hits the database to get the object from there and give it back else returns null.
As the java doc says :
Return the persistent instance of the given entity class with the given identifier, or null if there is no such persistent instance. (If the instance is already associated with the session, return that instance. This method never returns an uninitialized instance.)
For more information you can follow the link "http://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/Session.html#get(java.lang.Class, java.io.Serializable)" .
I'm having a hard time understanding this JPA behavior which to me doesn't seem to follow the specification.
I have 2 basic entities:
public class User {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#OrderBy("sequence ASC")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.REMOVE })
private final Set<UserProfile> userprofiles = new HashSet<UserProfile>(0);
//Ommiting rest of fields since they aren't relevant
}
public class UserProfile {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "userID", nullable = false, foreignKey = #ForeignKey(name = "FK_UserProfile_User"))
private User user;
//Ommiting rest of fields since they aren't relevant
}
As you can see I only have cascading set to REMOVE, the behavior will be the same if I don't have cascade set at all.
Now if I call:
User user = new User();
user.setId(UUIDGenerator.generateId());
UserProfile userProfile = new UserProfile();
userProfile.setId(UUIDGenerator.generateId());
userProfile.setUser(user);
user.getUserProfiles().add(userProfile);
em.merge(user);
merge will throw an exception.
I see Hibernate is executing a SQL query against the UserProfile table:
select userprofil0_.userProfileID as userProf1_4_0_, userprofil0_.profileID as profileI3_4_0_, userprofil0_.sequence as sequence2_4_0_, userprofil0_.userID as userID4_4_0_ from UserProfile userprofil0_ where userprofil0_.userProfileID=?
And then it will throw an exception
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.mytest.domain.UserProfile with id 6aaab891-872d-41e6-8362-314601324847;
Why is this query even called?
Since I don't have cascade type set to MERGE in userprofiles my expectation would be that JPA/Hibernate would simply ignore the entities inside userprofiles set and only insert/update the user record, doesn't this go against the JPA specs?
If I change cascadetype to MERGE things will work as expected and both User and UserProfile will be added to the database, so no problem there. What puzzles me is why is Hibernate querying the database and erroring out about an entity that's not supposed to be merged at all since I don't have it set to cascade.
This is more of an academic scenario that I ran into, of course I could simply clear the userprofiles set and things would work, but I'm trying to understand why the above behavior happens since I'm probably missing some crucial piece of information about how merge works. It seems it will always try to attach all entities to the session regardless cascade type being set or not.
Why is this query even called?
It's because you are trying to merge the entity, in JPA merge() is used to make the entity managed/attached. To "merge" User, JPA needs to still maintian the references it holds(UserProfile). In your case its not trying to persist UserProfile its trying to get a reference to it to merge User. Read here
If you use persist rather than merge this should not happen.
I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.
I have a question regarding Hibernate.
I have two objects with a Many-to-One relationship:
Eg:
Object 1:
public class Person {
#Basic
#Column(length = 50)
protected String name;
#NotFound(action=NotFoundAction.IGNORE)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "groupCode", referencedColumnName = "code", updatable=false)
protected Group group;
...all the getters and setters...
}
Object 2:
public class Group {
#Id
#Basic
#Column(length = 3, nullable = false)
protected String code;
#Basic
#Column(length = 30, nullable = false)
protected String groupName;
#Basic
#Column(precision = 15, scale = 0)
protected long exampleFieldId;
...rest of code....
}
I have tried to make this example as simple as possible. My issue is that the associated object (Group) on Person can be null. Currently, Hibernate loads up an instance of Group when I load up a particular Person and throws an exception because it cannot set exampleFieldId to be null (as it is a primitive type).
I can stop this error by changing long to be Long however, I would have thought that the Group object on Person should be null and thus no Group object loaded in the first place?
Does anyone know if Hibernate loads the associated object regardless of it being allowed to be null, or have I missed some important annotation?
Thanks
As mentioned by Firo:
have you disabled lazy loading and set fetchmnode to join because
NHibernate has to fetch them to decide if it should nullify it or not
and it can not decide that only with an id
This seems to be the same issue you are experiencing even though it is in NHibernate. Worth checking though!
Why does Hibernate attempt to load "not-found=ignore" associations?
Edit: It could be that you're missing #Fetch(FetchMode.JOIN).
I have finally worked out what was going on here, and the issue was not with Hibernate.
Deep in the code I had a UserType that converted a null String into a empty String, meaning that the groupCode would never actually be null. Hence Hibernate assumes that there is a child object to load.
Adding the annotation #Type(type="org.hibernate.type.StringType") above the groupCode avoided this issue.
Interestingly I had misunderstood the use of
#NotFound(action=NotFoundAction.IGNORE).
I had thought it was used to solve the issue I described above, but it actually defines what to do if groupCode is set but there’s no corresponding Group; not what to do when groupCode is null.
Others may fall for that too.
Inside a service class, I have a method that is called from a #Transactional method. I have verified that I have a transaction active at the point this code is called. I realize that I don't have a DA layer when I should, but I am working with a legacy application that makes doing things the 'right' way more of a hassle than it's worth at this point.
The mappings look like this:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
#JoinColumn(name = "FOO_ID")
#OneToOne(fetch = FetchType.LAZY, optional = false)
public Bar getBar() { return bar; }
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
#GeneratedValue(generator = "someSeq")
#GenericGenerator(name = "someSeq",
strategy = "foreign",
parameters = {
#Parameter(name = "property", value = "foo")
})
public String getId() { return id; }
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "FOO_ID")
public Foo getFoo() { return foo; }
// SETTERS INCLUDED
}
The method looks something like this:
public String createFoo(Foo foo) {
Session ses = getSessionFactory().getCurrentSession();
Bar bar = new Bar();
bar.setFoo(foo);
foo.setBar(bar);
ses.save(foo);
ses.save(bar);
System.out.println(foo.getId()); // yields the ID value set by generator
System.out.println(bar.getId()); // yields same ID value as above
ses.flush();
ses.refresh(foo);
}
Now, with org.hibernate.SQL logging set to DEBUG, I can see that the insert statements for both Foo and Bar are created, but the refresh after the flush is called throws a org.hibernate.UnresolvableObjectException: No row with the given identifier exists exception.
What could cause this? The database used is Oracle 11gR2.
UPDATE
I have narrowed my issue down to sessions. It seems that calling the currentSession.flush() is not writing the data to the database as expected for the refresh. If I comment out the rest of the method, it will commit at the end and everything will be in the database.
Doing the flush/refresh will not return the hydrated object, however, so I cannot use the database-populated values (set by column defaults) later on in my transaction. I also cannot split the transaction into multiple ones because I need to be able to rollback at any point in the method.
Any ideas as to why the flush is not giving me accessible data in the database?
ANOTHER UPDATE
I have moved a lot of code around just to try and isolate the issue, and I'm still having problems. I also got rid of the relationship between the two entities to try and handle everything manually, just to see if that would fix the problem. Considering all the comments from Steve, here's what I have now:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
public String getId() { return id; }
// SETTERS INCLUDED
}
#Service('fooService')
#Transactional(readOnly = true)
class FooService {
#Autowired
SessionFactory sessionFactory // populated using Spring config:
// org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
#Transactional(readOnly = false)
public void doSomeStuff(Foo fooToSave) {
sessionFactory.getCurrentSession().saveOrUpdate(fooToSave);
Bar bar = new Bar(fooToSave); // this populates the Bar.Id field
sessionFactory.getCurrentSession().saveOrUpdate(bar);
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().refresh(fooToSave); // exception thrown here
}
}
YET ANOTHER UPDATE
After quite a bit of playing around in Oracle-land to make sure that the SQL was running on the same session and the like, I've found the issue. Even though Hibernate is logging that the SQL bind variables are being set, they actually are not. Using Oracle 11gR2's V$SQL_BIND_CAPTURE, I was able to see using the SQL ID that was executed last (verified to be the insert statement) had 24 bind variables and not one of them ever had a value bound to it. Still not sure what's causing the values to be blank, but I am quite a bit closer to finding my answer. It has to be a problem with my mappings, which I cannot put here in entirety.
Being bind variables, I'm guessing that Oracle doesn't throw a fit about not being able to insert. JDBC typically just returns the number of rows inserted for an INSERT statement for verification, but I'm not sure exactly how the Hibernate abstraction handles this stuff. I am currently using Hibernate 3.6.10 -- upgraded from 3.6.5 to see if it might fix the issue. It didn't. :P
I'VE BEEN MISLEAD
Ignore that "YET ANOTHER UPDATE" section, above. The bind variables seem like they don't show up in the V$SQL_BIND_CAPTURE view until the transaction has been committed. Back to the drawing board.
ANOTHER REVISION - I SWEAR I'M GONNA GET BANNED
I decided to go back to basics. What have I changed since it was in a working state? Mostly mappings. A few service layer items were also changed, but it was mostly moving our Hibernate mappings from XML to annotations. So I took the same service method I've been playing with, commented out all the other stuff, and tried doing the very same thing as what I'm trying to do with Foo using another persistent object type. Guess what? That works. The only link that could be causing my heartache at this point is the mapping I have for Foo. I doubt my employer would like me to just throw full source up on SO, so I'll probably have to just figure this one out on my own. I will post the answer in some capacity when I finally figure it out.
SUCCESS! BUT I'M NOT SURE WHY...
Here's the code that was giving me trouble. Keep in mind that BAZ is a linking table that has a composite ID made up with an #Embeddable (just called "key" for this example), consisting of FOO_ID referencing a row in the FOO table and a STATE_ID referencing another table.
public class Foo {
// OTHER FIELDS INCLUDING IDs AND SUCH
private Baz bazOfDoom;
private Baz bazOfLight;
private Set<Baz> allTheBaz;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "DOOM_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfDoom() { return bazOfDoom; }
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "LIGHT_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfLight() { return bazOfLight; }
#OneToMany(mappedBy = "key.foo", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
public Set<Baz> getAllTheBaz() { return allTheBaz; }
}
I removed the cascades and it worked. I don't know why. Whoever can explain that will get the "correct answer" mark from me. :)
It seems that your object doesn't own an identifer for your object after saving it to database, leading thus to your exception when calling refresh().
Indeed, assume your database tables own primary key defined as auto-increment.So, when you save your first Foo object, primary key column is valued as: 1.
However, Hibernate has to be aware of this newly generated identifier after calling save() method !
The best way to do this is to expect Hibernate to reaffect the good identifier as soon as the object is saved into database.
Thus, you might miss this line within your entity classes in order to provide identifier automatically when object is saved in database:
#GeneratedValue(strategy = GenerationType.AUTO)
Of course, you don't have to autogenerate them and rather can manually precise it.