Win a copy of Testing JavaScript Applications this week in the HTML Pages with CSS and JavaScript forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Bear Bibeault
  • Ron McLeod
  • Jeanne Boyarsky
  • Paul Clapham
Sheriffs:
  • Tim Cooke
  • Liutauras Vilda
  • Junilu Lacar
Saloon Keepers:
  • Tim Moores
  • Stephan van Hulst
  • Tim Holloway
  • fred rosenberger
  • salvin francis
Bartenders:
  • Piet Souris
  • Frits Walraven
  • Carey Brown

bi-directional OneToMany and ManyToOne foreign key always null Spring Boot hibernate

 
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
What I'm trying to do: create a project set it to a machine (project has machine's id as a foreign key in the database) and everything is working as intended EXCEPT machine_id column in project database is always null. I've tried every article and video on the internet to get this to work and have had zero luck so far.
Here is my Machine class



Machine Repository



Machine Service



Project class



Project Service



Project Repository



Machine Controller



Project Controller



and the new-projects.html with thymeleaf



In the project controller's /addMachine method (which returns the above new-projects.html), it shows the correct machine id, but once the form is submitted and project gets saved to the database, the machine_id in the database is still null. I don't understand why. Any help would be greatly appreciated. I've been stuck on this for a couple weeks now.
 
Saloon Keeper
Posts: 22289
151
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
One of the problems with posting a complete set of code examples is that there is simply too much to read - at least when we're doing it for free. When at all possible, it's best if you can compact the examples down to the parts that you think are giving you problems - for example, only the properties in an Entity that actually get involved in linking them and not all of the detailed data fields.

On the other hand, I couldn't find a definition of what Model is. There are actually 2 types of Models when GUIs meet ORMs. One is the GUI backing Model (for example, ManagedBeans in JSF), the other is the Object Model used in an ORM (Hibernate/JPA in this example). The lines sometimes blur since these days a good Model class is a POJO and therefore can often (but not always) be used in both contexts. But I'm suspecting that Model is only a GUI Model class here.

OK, so much for the incoherent rambling. Since I can't actually see what I'm looking for, I'll take it that the fault is the same one that most people make.

When you link 2 Entity Objects in an ORM system such as JPA, it is essential that you fully connect them. That is, it isn't enough to make a child point to its parent object, you also have to update the parent's collection of child objects. AND you must ensure that both sets of objects are properly persisted to the database. If you simply add a new child to a parent, for example, all of the existing children will get disconnected, so you have to have the full set of data in memory to work with it. In an ORM, you're working first and foremost with Model Objects, and should not assume that the database will know what you mean.

An additional caveat is when you work with disconnected objects. For a number of reasons, my own app designs disconnect the ORM objects before passing them to the higher levels (business and GUI layers) of my apps. But the cost of that bit of functionality is that I have to re- connect them before actually making changes to them, including changing their relationships to other objects. You do that with the merge() method, but there's a "gotcha".

There's a reason that merge() returns an object instead of void and it isn't to make chaining method calls possible. The returned object from merge() is not the same object that you passed to merge(). Instead, it's a new copy of the object with the necessary persistence context invisibly attached to its internals. Basically, once you call merge() on an object, you can discard the original object, because it's actually dangerous to do anything more with that object - you would get out of sync. You would then do your linkage updates on the object returned from merge(). Note that in JPA. equals() only compares on key values, so the return from y = entityManager.merge(x) returns "y.equals(x)" as true, but "y == x" returns false.

So, in brief, what I suspect is that you're updating the child's parent reference but not updating and persisting the parent and its list of children.
 
Bartender
Posts: 1925
13
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
My first approach is not to use any generator for the ID and see if it works. I mean, let the JPA to automatically generate an ID for you.
 
Himai Minh
Bartender
Posts: 1925
13
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,
Let me take my previous post back.
I understand your question now.
When  you create your project, add a machine to the project.
Then, add the project to the machine.
This way, you can build a bi-direction relationship between project and machine.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Tim, You are exactly right, hovering over Model, it is coming from spring's UI. I thought it was the object model that gets passed to the form and you act on the object itself. I wasn't aware there was two different things. All my research has pointed to what you guys are saying about linking the two entities. I think i'm expecting too much from spring, and it doesn't know what I want it to do.

I narrowed down to printing the list of projects from the machine entity and it's returning null, even though my code adds a project to that list once machine.setProjects() is called, which adds the project to the arraylist or create one if it's already null. The project is reading the correct machine ID, machine is just not setting the project.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sorry for the code dump. I'll target where I think my problem lies.
In my machine entity where I have my list and bi-directional method:



In my project entity



In the project controller



 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
My only other thought is hibernate isn't doing what I'm expecting.

I see the insert into project table in the log. it's expecting a machine_id (which is the foreign key) but in my code I am setting an object to an object. Maybe it's not smart enough to know I want the Id from the object?
 
Himai Minh
Bartender
Posts: 1925
13
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,
I guess the problem was one relationship was set and the other relationship is not set and not save. That may cause why the machine id column is always null.

You may want to try this:


 
Himai Minh
Bartender
Posts: 1925
13
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
One more thing you can try the following.
I believe you need to save the machine once your machine set the project.
 
Himai Minh
Bartender
Posts: 1925
13
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, I was taking a course about JPA from Udemy. I had an assignment about setting the one to many and many to one relationship between book and publisher.
I did something similar to your to set the relationship. But in the bootstrap class, I save the book and publisher. Then, I add the book to the publisher and vice versa.
I need to save the book again.
Here is my code:
https://github.com/aCodeRancher/spring5webapp/blob/my-bootstrap-v2/src/main/java/guru/springframework/spring5webapp/bootstrap/BootStrapData.java

Also, for your reference, here is my instructor John Thompson's sample code:
https://github.com/springframeworkguru/spring5webapp/blob/publisher-relationships/src/main/java/guru/springframework/spring5webapp/bootstrap/BootStrapData.java
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
ok so when I go to save a machine in the /addMachine mapping of the project controller



I get this error now:



it's inserting the correct machine id which is 37 in this case, the 179 is the autogenerated projectId. Why does saving the machine, try to save the project also? It's not going to have a name yet because I haven't made it to my form to input those values yet.
 
Tim Holloway
Saloon Keeper
Posts: 22289
151
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is pretty horrible:

Before I go further, however, I should mention that Spring is not involved here, and technically neither is Hibernate. This is all JPA. Spring Data provides a framework that assists JPA, but the actual linkages and data access is all done by you. Spring just manages the database transactions and error-catch/cleanup.


OK. Now back to my complaint. "setProjects" is not a proper POJO method. What it should actually be doing is this:

Nothing more, nothing less. Ideally your no-code construction should also have included the following code:

Meaning that projectList should never be null, just empty.

To add a project to the list, you would do this in your business logic class:

If I lost track of what object types I'm supposed to be working with, I apologise. It's the shape of the logic that matters, not the names.

Hmai is giving good advice on the details of persisting the related objects - don't trust that part of my example too much. I'm out of practice. The main thing I wanted to do was illustrate the linkage process and - just as important - that it should not be done in the Entity class. I have seen what happens when Entities get away from the strict POJO form and it's not pretty.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I agree it was horrible, but I was desperate and tried every solution I found that could solve my problem even if it was poorly done. So now I have done what you suggested, and hibernate is still giving me fits trying to save null into the project name,
when I haven't tried saving a project yet, just the machine has been saved at this point, trying to get to the form to input project information. The project gets saved after the form is submitted and passed to the /save mapping



Updated project controller



updated machine service



 
Himai Minh
Bartender
Posts: 1925
13
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Damon,

In your addMachine, you create a new project, but this project does not even exist in the database.

Before you call addMachine, make sure you have the related project in the database.
So, you should create the project first. When you add a machine, retrieve the project from the database, add a machine to this project.
And the machine add the project to itself.
Use your machine repository to save the newly added machine.

Rule of thumb for your add machine:
1. make sure you have the project saved in the database
2. make sure you save machine to the database.
3. build a bi-directional relationship between them after your retrieve them from the database.
4. save the machine in the database.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
That makes perfect sense! I was trying to combine 2 steps at once, which is why it would never work. I'll give that a shot. Thank you! I'll be back if it works.
 
Sheriff
Posts: 21974
106
Eclipse IDE Spring VI Editor Chrome Java Ubuntu Windows
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Himai Minh wrote:You may want to try this:



Please don't do that. What you created there is not a bi-directional relationship. It's instead a uni-directional one-to-many relationship from Machine to Project, and a separate uni-directional many-to-one relationship from Project to Machine. It may actually work, but it's not what you want. Once you switch to many-to-many, it will not even work correctly and you will get duplicate inserts.

The mappedBy attribute is the attribute that combines two separate uni-directional relationships into a single bi-directional one.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Alright I am getting much closer, now my issue is when saving a project to the database, hibernate is creating a new machine, and naming the machine with the id of the machine that I selected. The project is correctly holding the foreign key to the newly created machine. It's not what was intended but it's a small step in the right direction.
 
Himai Minh
Bartender
Posts: 1925
13
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Damon,
Do you want machine table have a foreign key of project ?
For bi-directional one-to-many and many-to-one relationship, the "many" entity should have a foreign key to the "one" entity.
But for the "one" entity, there should not be any foreign key to the "many" entity.
For example:
Project table has columns:
project_id  project_name       machine_id
1001        make_printer           1
1002        make_monitor          2
1003        make_tv_screen        2
1004        make_phone_screen  2

Machine table has columns:
machine_id    machine_name
1                  printer_maker
2                  screen_maker

What happens if machine table has project id?
machine_ id  machine_name     project_id
2                 screen_maker       1002
2                 screen_maker      1003
2                 screen_maker      1004

Machine table will have duplicated primary key , which is a database violation.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Answered my own question
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Alright this should be my last question. When creating a new project from the form, the user selects a machine (that has already been previously created and is stored in the database) once I click submit, a new machine is created in the database and hibernate calls my machine constructor with the name argument to create a new machine and names it the ID of the machine that was selected from the form. A new machine should not be created at this point, as it already exists in the database. I just want the new project to be assigned to the machine that was selected. The only thing that should be happening is the project being saved to the database and storing the machine id that was selected as it's foreign key.

Project Controller


This is the form: In the project class, project has a private Machine machine with getters and setters.


SelectFromProject.png
machineId foreign key is storing the newly created machine (which should not have been created)
machineId foreign key is storing the newly created machine (which should not have been created)
SelectFromMachine.png
newly created machine is named with the id of the machine that was selected
newly created machine is named with the id of the machine that was selected
 
Himai Minh
Bartender
Posts: 1925
13
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Damon,
Before you post/save a new project, have you create a new machine first?
For example, you can first create a new machine with id 142. Then, use a method like machineService.findById(Long id) to looking up the machine.
Inside the machineService method, there should be machineRepository.findById(id) to look up the database.
Once you find that machine with id =142, set the project to it and save it again.
I hope that helps.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The machine is already created and in the database. The machine selected in the form should just set the selected machine to the project.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Its possible I am thinking about this the wrong way but in my controller I'm searching for a list of all machines that have been previously created and stored in the database. Passing that list to the form view for project creation. A new project gets created and the machine that was selected should be part of the project, not creating a new machine
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
What I think is happening is a project is being created then saved to the database without the machine being set to the project first. Ill. Try that tonight.
 
Himai Minh
Bartender
Posts: 1925
13
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi Damon,

Damon Vessey wrote:The machine is already created and in the database. The machine selected in the form should just set the selected machine to the project.



I am aware that your machine is in the database as I read your code.
But when you create the project, first retrieve the machine from the database by machineService.findById(machine_id). Then, set the machine to that project.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
On my way to work this morning had a thought. Since I am sending a list to the controller for all machines from the database.
In my save route I should be able to request param a list of ids sent from the form even though the list only contains one element and find the Id of the machine from index position [0] sounds kinda counterintuitive.
 
Damon Vessey
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thank you everyone who helped. This issue has been solved and I will post a link to my github repository if anyone needs to see how I was able to solve this as this seems like a pretty edge case scenario, as I have not found a whole lot on what my program was setting out to accomplish. So hopefully this helps anyone so they don't go through what I did trying to figure it out, even though I feel a great sense of pride and accomplishment. I added functionality for date and time, although I have not worked on validation for checking if a new project has conflicting time with another, but that's for another day.

My GitHub Repository

 
This is my favorite tiny ad:
Thread Boost feature
https://coderanch.com/t/674455/Thread-Boost-feature
    Bookmark Topic Watch Topic
  • New Topic