#ManyToMany using #JoinTable - java

i have the following two entities and one join table:
entity:
#Entity
#Table(name = "PERSON")
public class parent {
#OneToOne(mappedBy="person", cascade= CascadeType.ALL)
private Reader reader;
//more fields and getters and setters and of course #Id.....
}
and another entity:
#Entity
#Table(name="READER")
public class Reader{
#Id
#Column(name= "READER_ID")
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="Person"))
private long readerId;
#OneToOne
#PrimaryKeyJoinColumn
private Person person;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "READER_BOOKS", joinColumns={#JoinColumn(name="READER_ID")}, inverseJoinColumns={#JoinColumn(name="BOOK_ID")})
private List<Books> books;
//more fields and getters and setters
}
And i have a join table in the data base which is named "READER_BOOKS" which is consisted with two columns - readerId and bookId - both are a primary key as a pair, and each of the columns is a foreign key to the corresponding table.
A few details:
I have defined a "ManyToMany" relationship in Reader.java, meaning
that a reader can have many books ,and books can be reused in other
readers ( for example another reader can have the same books as other
readers). Also as you can see the reader id and the person id is the
same - so i defined a "OneToOne" relationship - which works fine.
In the books entity class i didn't add a reference or stated any
relationship because i understood that i don't need to.
The problem:
When i add a Person and set the reader field with several books, it works and the database is filled with the right values.(a person,reader and join tables are inserted with new rows)
The problem is when i delete the Person instance OR the Reader instance AND the books instance is not null(books are assigned to reader). hibernate is just stuck and not throwing any execption.
but when the books instance is null (didnt assign any books to the reader) the deletion is successfull.
I think that something might be wrong with the "ManyToMany" , because when the join table is empty the deletion is successful.
what is wrong in my implementation?
Edited: i found out sometimes hibernate gets stuck even if that list is empty, so i think something is also wrong with the "OneToOne" anottation, maybe i missed out something

there was no problem in my implementation.
i use PL/SQL developer and i didn't notice but it acquired a lock on one of the tables :|.
so when i tried to delete a record, hibernate got stuck and from some reason didn't throw any error.
Any way, the implementation is correct.

Related

Mapping hierarchical data in Hibernate

In a Hibernate application I'm working on a table with hierarchical data (Oracle Database).
For the sake of simplicity I'll put an example of a person with a father (assume the hierarchy is not fixed and can extend for more than 3 levels).
Every Person may own a couple of Cars.
#Entity
class Person
{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "FATHER_ID")
private Person father;
}
#Entity
class Car
{
#Id
private String id;
#ManyToOne
private Person owner;
}
So far whenever we needed to fetch all cars for a person's descendants, we issued a native query fetching all descendants' ids and then a second hql query to fetch the cars themselves (using IN clause with the previously fetched ids as bind parameters).
I don't like it because it stains our code and causing us to issue more queries than we actually need.
Moreover, we are kind of abusing the IN clause, using a different number of bind parameters for many of our queries.
So I decided to try and encapsulate this hierarchical data using Hibernate.
I've tried using #JoinFormula to get the person's descendants as a data member like this:
#Entity
class Person
{
...
#JoinFormula(value = "SELECT p.id FROM Person p START WITH p.id = id CONNECT BY PRIOR p.id = father_id", referencedColumnName = "id")
#OneToMany
List<Person> descendants;
}
Unfortunately it doesn't work, hibernate throws an exception:
ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
I have tried changing the relation to #ManyToMany, it fixes the ClassCastException, but then hibernate just ignores the formula and creates a new table.
Now I'm thinking of using an sql function to retrieve descendants ids and using it inside my hql.
However I'm not quite sure on how to do this...
Any help would be much appreciated!

Hibernate saving to db "Duplicate entry '1' for key 'UK_2n5xttsxwbyc6x67l0x8phfwn'"

I have the Recipe.java object with #Entity:
...
#OneToMany(cascade = CascadeType.ALL)
private List<Category> category;
...
Then the Category.java object with #Entity:
...
#OneToOne(cascade = CascadeType.ALL)
private Name name;
...
Let's say that the db looks like this (recipe_category table):
Then the following code is executed (I just want to add a category to the recipe):
...
Recipe recipe = recipeRepository.findOne(recipeId);
Category ctg = categoryRepository.findOne(categoryId); // id=1
List<Category> categories = recipe.getCategory();
categories.add(ctg);
recipe.setCategory(categories);
recipeRepository.save(recipe);
...
On the recipeRepository.save(recipe) I am getting the following error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'UK_2n5xttsxwbyc6x67l0x8phfwn'
So what would be the solution for this problem?
UPDATE:
the recipe table structure looks like this:
The category table structure looks like this:
So the problem seems to happen because when recipe.setCategory(categories); is triggered, it tries to save the ctg to the db, but it already exists. What I want is not to save it to db (because is's already there in the 'category' table) but to add a new row in the recipe_category table.
Maybe it has to do something with cascade?
Your relationship is not one-to-many. You want each recipe to have multiple categories. I guess you also want each category to be associated to multiple recipes. This is a many-to-many relationship. You need to use #ManyToMany annotation to configure your entities.
Also note that Hibernate relationships are always unidirectional. When you put #ManyToMany annotation in Recipe class you can access categories associated with a given recipe. In order to access the reverse relationship, to get recipes of a given category you also need to add an appropriate property with #ManyToMany annotation to Category class too.
I guess you used hbm2ddl or a similar method to auto create your tables and since tables are created for a one-to-many relationship you get an error. More specifically in a unidirectional one-to-many relationship the inverse foreign key column in join table (category_id in your recipe_category table) has a unique constraint defined on it. Thus with that table schema you cannot associate a category with more then one recipe.
Something like this should work:
// Recipe
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="category_map", )
private List<Category> categories = new ArrayList<> ();
and
// Category
#ManyToMany(cascade=CascadeType.ALL, mappedBy="categories")
private Set<Recipe> recipes;
Recipe recipe = recipeRepository.findOne(recipeId);
Category ctg = categoryRepository.findOne(categoryId); // id=1
List<Category> categories = recipe.getCategory();
categories.add(ctg);
recipe.setCategory(categories);
recipeRepository.save(recipe);
//categories.clear();

JPA many-to-one relation - need to save only Id

I have 2 classes: Driver and Car. Cars table updated in separate process. What I need is to have property in Driver that allows me to read full car description and write only Id pointing to existing Car. Here is example:
#Entity(name = "DRIVER")
public class Driver {
... ID and other properties for Driver goes here .....
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CAR_ID")
private Car car;
#JsonView({Views.Full.class})
public Car getCar() {
return car;
}
#JsonView({Views.Short.class})
public long getCarId() {
return car.getId();
}
public void setCarId(long carId) {
this.car = new Car (carId);
}
}
Car object is just typical JPA object with no back reference to the Driver.
So what I was trying to achieve by this is:
I can read full Car description using detailed JSON View
or I can read only Id of the Car in Short JsonView
and most important, when creating new Driver I just want to pass in JSON ID of the car.
This way I dont need to do unnesessery reads for the Car during persist but just update Id.
Im getting following error:
object references an unsaved transient instance - save the transient instance before flushing : com.Driver.car -> com.Car
I dont want to update instance of the Car in DB but rather just reference to it from Driver. Any idea how to achieve what I want?
Thank you.
UPDATE:
Forgot to mention that the ID of the Car that I pass during creation of the Driver is valid Id of the existing Car in DB.
You can do this via getReference call in EntityManager:
EntityManager em = ...;
Car car = em.getReference(Car.class, carId);
Driver driver = ...;
driver.setCar(car);
em.persist(driver);
This will not execute SELECT statement from the database.
As an answer to okutane, please see snippet:
#JoinColumn(name = "car_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = Car.class, fetch = FetchType.EAGER)
private Car car;
#Column(name = "car_id")
private Long carId;
So what happens here is that when you want to do an insert/update, you only populate the carId field and perform the insert/update. Since the car field is non-insertable and non-updatable Hibernate will not complain about this and since in your database model you would only populate your car_id as a foreign key anyway this is enough at this point (and your foreign key relationship on the database will ensure your data integrity). Now when you fetch your entity the car field will be populated by Hibernate giving you the flexibility where only your parent gets fetched when it needs to.
You can work only with the car ID like this:
#JoinColumn(name = "car")
#ManyToOne(targetEntity = Car.class, fetch = FetchType.LAZY)
#NotNull(message = "Car not set")
#JsonIgnore
private Car car;
#Column(name = "car", insertable = false, updatable = false)
private Long carId;
That error message means that you have have a transient instance in your object graph that is not explicitly persisted. Short recap of the statuses an object can have in JPA:
Transient: A new object that has not yet been stored in the database (and is thus unknown to the entitymanager.) Does not have an id set.
Managed: An object that the entitymanager keeps track of. Managed objects are what you work with within the scope of a transaction, and all changes done to a managed object will automatically be stored once the transaction is commited.
Detached: A previously managed object that is still reachable after the transction commits. (A managed object outside a transaction.) Has an id set.
What the error message is telling you is that the (managed/detached) Driver-object you are working with holds a reference to a Car-object that is unknown to Hibernate (it is transient). In order to make Hibernate understand that any unsaved instances of Car being referenced from a Driver about be saved should also be saved you can call the persist-method of the EntityManager.
Alternatively, you can add a cascade on persist (I think, just from the top of my head, haven't tested it), which will execute a persist on the Car prior to persisting the Driver.
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinColumn(name = "CAR_ID")
private Car car;
If you use the merge-method of the entitymanager to store the Driver, you should add CascadeType.MERGE instead, or both:
#ManyToOne(fetch=FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "CAR_ID")
private Car car;
public void setCarId(long carId) {
this.car = new Car (carId);
}
It is actually not saved version of a car. So it is a transient object because it hasn't id. JPA demands that you should take care about relations. If entity is new (doesn't managed by context) it should be saved before it can relate with other managed/detached objects (actually the MASTER entity can maintain it's children by using cascades).
Two ways: cascades or save&retrieval from db.
Also you should avoid set entity ID by hand. If you do not want to update/persist car by it's MASTER entity, you should get the CAR from database and maintain your driver with it's instance. So, if you do that, Car will be detached from persistence context, BUT still it will have and ID and can be related with any Entity without affects.
Add optional field equal false like following
#ManyToOne(optional = false) // Telling hibernate trust me (As a trusted developer in this project) when building the query that the id provided to this entity is exists in database thus build the insert/update query right away without pre-checks
private Car car;
That way you can set just car's id as
driver.setCar(new Car(1));
and then persist driver normal
driverRepo.save(driver);
You will see that car with id 1 is assigned perfectly to driver in database
Description:
So what make this tiny optional=false makes may be this would help more https://stackoverflow.com/a/17987718
Here's the missing article that Adi Sutanto linked.
Item 11: Populating a Child-Side Parent Association Via Proxy
Executing more SQL statements than needed is always a performance penalty. It is important to strive to reduce their number as much as possible, and relying on references is one of the easy to use optimization.
Description: A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent ( #ManyToOne or #OneToOne lazy association). In such cases, fetching the parent entity from the database (execute the SELECT statement) is a performance penalty and a pointless action. Hibernate can set the underlying foreign key value for an uninitialized proxy.
Key points:
Rely on EntityManager#getReference() In Spring
use JpaRepository#getOne() Used in this example,
in Hibernate, use load()
Assume two entities, Author and Book, involved in a unidirectional #ManyToOne association (Author is the parent-side) We fetch the author via a proxy (this will not trigger a SELECT), we create a new book
we set the proxy as the author for this book and we save the book (this will trigger an INSERT in the book table)
Output sample:
The console output will reveal that only an INSERT is triggered, and no SELECT
Source code can be found here.
If you want to see the whole article put https://dzone.com/articles/50-best-performance-practices-for-hibernate-5-amp into the wayback machine. I'm not finding a live version of the article.
PS. I'm currently on a way to handle this well when using Jackson object mapper to deserialize Entities from the frontend. If you're interested in how that plays into all this leave a comment.
Use cascade in manytoone annotation
#manytoone(cascade=CascadeType.Remove)

Data was not saved: object references an unsaved transient instance - save the transient instance before flushing [duplicate]

This question already has answers here:
How to fix the Hibernate "object references an unsaved transient instance - save the transient instance before flushing" error
(33 answers)
Closed 2 years ago.
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?
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.
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;
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
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
It should be CascadeType.Merge, in that case it will update if the record already exists.
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.
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)

How can I access the underlying column after defining a #ManyToOne relationship on it in Spring?

I'm using Spring 3.2 with Roo 1.2.3 to build a database-backed Java application via Hibernate. I have several bidirectional OneToMany/ManyToOne relationships among the tables in my database. When I set up the ManyToOne side of the relationship using #JoinColumn (via "field reference" in Roo), a new field whose type is the related entity (the "one" in ManyToOne) is created. However, once this is done, there seems to be no way to access the underlying column value on which the ManyToOne relationship is based. This is a problem when the underlying join column contains data needed by the application (i.e. when the join column contains product stock numbers).
Is there any way to set up my entity class so that the column on which its ManyToOne relationship is based remains accessible without traversing the new join property? How can I define an accessor method for the value of this column?
I've been looking online for an answer to this question for several days, but to no avail. Thanks in advance for your help.
just map the column a second time with insertable=false and updateable=false
To make it more concrete. It's possible to do a HQL-SELCT and restrict a ManyToOne relationship, without any join in the resulting SQL:
Instead of using a join in
session.createQuery("FROM Person person WHERE person.adress.id = 42")
we use can use the adress_idcolumn
session.createQuery("FROM Person person WHERE person.adressId = 42")
This works, if you specify an additional adressId field, which is only used as mapping info for Hibernate:
#Entity
#Access(AccessType.FIELD)
public class Person{
#Id
String id;
#JoinColumn(name = "adress_id")
#ManyToOne(fetch = FetchType.LAZY)
#Nullable
public Adress adress;
#Column(name = "adress_id", insertable = false, updatable = false)
private String adressId;
}
#Entity
#Access(FIELD)
public class Adress{
#Id
String id;
}
The AccessType.FIELD is not needed (But we can leave getters/setters in example). The FetchType.LAZY and #Nullable are also optional, but make it clear when it makes sense to use it. We are able to load Person entities which have a specific Address (we know the address id). But we don't need a join because it's not needed for the WHERE-clause and not for the initial fetch (the address can be fetched lazy).

Categories