19

I have a database with two tables User and Country. I want to relationship where many user can belong to one county. I implement this using hibernate using the following model classes:

@Entity (name = "user")
public class User {

   @Id @GeneratedValue (strategy = GenerationType.IDENTITY)
    private int userId;
    private String username;
    private String password;

   @ManyToOne ()
   @JoinColumn (name = "countryId")
   private Country country;

    //Getter & Setter
 }


@Entity (name = "country")
public class Country {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int countryId;
    private String countryName;
   //Getter & Setter
}

When I try to save the user object I get the following exception:

  org.hibernate.HibernateException: Data was not saved: object references an unsaved transient instance - save the transient instance before flushing: com.kyrogaming.models.Country

How can I fix the problem?

Mario Dennis
  • 2,986
  • 13
  • 35
  • 50
  • persist the User object then set the Country, after that invoke the save or merge int the User – fmodos Jun 24 '13 at 19:26

7 Answers7

26

You can't save things to Hibernate until you've also told Hibernate about all the other objects referenced by this newly saved object. So in this case, you're telling Hibernate about a User, but haven't told it about the Country.

You can solve problems like this in two ways.

Manually

Call session.save(country) before you save the User.

CascadeType

You can specify to Hibernate that this relationship should propagate some operations using CascadeType. In this case CascadeType.PERSIST would do the job, as would CascadeType.ALL.

Referencing existing countries

Based on your response to @zerocool though, you have a second problem, which is that when you have two User objects with the same Country, you are not making sure it's the same Country. To do this, you have to get the appropriate Country from the database, set it on the new user, and then save the User. Then, both of your User objects will refer to the same Country, not just two Country instances that happen to have the same name. Review the Criteria API as one way of fetching existing instances.

sharakan
  • 6,821
  • 1
  • 34
  • 61
  • If I have to use the Criteria API to ensure everything is saved correctly then what is the point of the ManyToOne annotation? – Mario Dennis Jun 25 '13 at 14:15
  • 1
    You use the Criteria API to fetch instances of objects you want to refer to from the persistence context: in other words, to generate and run SQL. You use the ManyToOne annotation to tell Hibernate how a relationship between two tables is modeled in the database. They are related concepts, but they are NOT a replacement for each other. – sharakan Jun 25 '13 at 14:20
13

Looks like Users that are added in your Country object, are not already present in the DB. You need to use cascade to make sure that when Country is persisted, all User which are not there in data but are associated with Country also get persisted.

Below code should help:

@ManyToOne (cascade = CascadeType.ALL)
   @JoinColumn (name = "countryId")
   private Country country;
ajay.patel
  • 1,957
  • 12
  • 15
  • 1
    I added the CascadeType.ALL to @ManyToOne annotation which fixed the problem but now it saves a new country instance even when that country is all ready stored in the country. How can I solve that new problem? – Mario Dennis Jun 24 '13 at 19:37
  • There could be many reasons for the same. For now looks like you are not setting the primary key for Country. – ajay.patel Jun 24 '13 at 19:51
  • I use to generateValue annotation to auto generate a primary key for the country table will that cause the problem that I am currently having – Mario Dennis Jun 24 '13 at 19:56
  • Hello Please please, i also have the same problem http://stackoverflow.com/questions/18895585/hibernate-version-annotation-and-object-references-an-unsaved-transient-instanc any solution for this ? – Senthil Muthiah Sep 19 '13 at 13:49
  • 1
    It is usually not recommended to add a cascade in the child item. Else you will end up with multiple same parents. – Ganesh Krishnan Apr 15 '19 at 21:30
7

To add my 2 cents, I got this same issue when I m accidentally sending null as the ID. Below code depicts my scenario (and anyway OP didn't mention any specific scenario).

Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // -----> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);

Here I m setting the existing department id to a new employee instance without actually getting the department entity first, as I don't want to another select query to fire.

In some scenarios, deptId PKID is coming as null from calling method and I m getting the same error.

So, watch for null values for PK ID

Same answer given here

Community
  • 1
  • 1
manikanta
  • 8,100
  • 5
  • 59
  • 66
  • Yes, This solves the issue. emp.setDept((emp.getDept().getDeptId != null) ? new Dept(emp.getDept().getDeptId) : null); We getting from UI, we need to explicitly set the property to null to make hibernate happy.:-) – Jajikanth pydimarla Mar 15 '17 at 21:29
3

I had the same problem. In my case it arises, because the lookup-table "country" has an existing record with countryId==0 and a primitive primary key and I try to save a User with a countryID==0. Change the primary key of country to Integer. Now Hibernate can identify new records.

For the recommendation of using wrapper classes as primary key see this stackoverflow question

Community
  • 1
  • 1
Frank Rünagel
  • 307
  • 3
  • 5
0

It should be CascadeType.Merge, in that case it will update if the record already exists.

0

Well if you have given

@ManyToOne ()
   @JoinColumn (name = "countryId")
   private Country country;

then object of that class i mean Country need to be save first.

because it will only allow User to get saved into the database if there is key available for the Country of that user for the same. means it will allow user to be saved if and only if that country is exist into the Country table.

So for that you need to save that Country first into the table.

Kishan Bheemajiyani
  • 3,429
  • 5
  • 34
  • 68
0

It is because of CASCADE TYPE

if you put

@OneToOne(cascade=CascadeType.ALL)

You can just save your object like this

user.setCountry(country);
session.save(user)

but if you put

 @OneToOne(cascade={    
            CascadeType.PERSIST,
            CascadeType.REFRESH,
            ...
})

You need to save your object like this

user.setCountry(country);
session.save(country)
session.save(user)
Pen Lymeng
  • 271
  • 5
  • 14