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
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.
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.
Sources may include data from the Fakebook Research Foundation with support from Gargle University
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.
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.
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?
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.
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.
Sources may include data from the Fakebook Research Foundation with support from Gargle University
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
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.
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.
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.
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.
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:
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.
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.
This is the form: In the project class, project has a private Machine machine with getters and setters.
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.
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 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.
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  sounds kinda counterintuitive.
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.