I am currently working on 100+ Java Objects created by someone with no JPA/Hibernate experience into JPA Entities. Their Objects reference other objects based on a Foreign Key in the Class itself. All of the primary Keys are generated outside of the database.
For Example (Just to Illustrate)
Car
#Entity
#Table(name="CAR")
public class Car {
private Integer id;
private String name;
#Id
#Column(name="CAR_ID")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name="CAR_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Engine
#Entity
#Table(name="ENGINE")
public class Engine {
private Integer id;
private String name;
private Integer carId;
#Id
#Column(name="ENGINE_ID")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name="ENGINE_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="CAR_ID")
public Integer getCarId() {
return carId;
}
public void setCarId(Integer carId) {
this.carId = carId;
}
}
Here is what the schema looks like:
CREATE TABLE CAR(CAR_ID INTEGER NOT NULL PRIMARY KEY,CAR_NAME VARCHAR(255))
CREATE TABLE ENGINE(ENGINE_ID INTEGER NOT NULL PRIMARY KEY,CAR_ID INTEGER,ENGINE_NAME VARCHAR(255))
ALTER TABLE ENGINE ADD FOREIGN KEY (CAR_ID) REFERENCES CAR(CAR_ID) ON DELETE CASCADE ON UPDATE CASCADE
Testing Code
Car c = new Car();
c.setId(100);
c.setName("Dodge Intrepid");
em.persist(c);
Engine e = new Engine();
e.setId(999);
e.setName("V6");
e.setCarId(c.getId());
em.persist(e);
Object entity = em.find(c.getClass(),c.getId());
em.remove(entity);
So, the Car ID holds a reference to the Engine in the Database. Losing Lazy Loading is not a big deal because we are using an TRANSACTIONAL Entity Manager. I have tested this out and it seems to work fine.
Are there any obvious problems with this that I am missing? I know it does not exactly fit the JPA/Hibernate spec, but I think it works.
The main thing you're losing is easy navigation through the object graph without having to explicitly fetch associated objects by ID all the time via your data access layer. I understand you're reluctance to go through and convert all of the object IDs into object references, but in the long-run, you're probably better off making that investment. By continuing with your current design, you'll end up writing a lot of extra data access code to fetch related objects every time you need to navigate an association. Plus, it will make it more difficult to model object relationships when the objects are not persisted in the database (e.g. when constructing a new object graph or in unit tests). You're also giving up cascading (transitive) saves and updates.
In short, I think your system will be more difficult to maintain in the long run and navigating object graphs will be quite awkward unless you address this design problem.
Not having any mapped relationships is going to make it almost impossible to have any joins in your HQL Queries or Criteria. A simple Select will work but you could not do
from Engine as eng where eng.car.name = 'DODGE'
If you at least get the major relationships coded it will make thing a lot easier in the long run.
Related
I can't seem to find any reference online with regards to using a Many-To-One mapping in Spring JDBC. I just saw in the documentation that is not supported but I'm not sure if this is the case.
My example is that I want to map my AppUser to a particular Department.
For reference, AppUser joins to Department table using DEPARTMENT_ID
#Table(value="m_appuser")
public class AppUserProjectionTwo {
#Id
private Long id;
private String firstname;
private String middlename;
private String lastname;
#Column("DEPARTMENT_ID")
private DepartmentProjection departmenProjection;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
However, it seems that it won't map properly.
#Table("M_DEPARTMENT")
public class DepartmentProjection {
#Id
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
The created query looks like this. I was looking for something more of the opposite in which M_APPUSER.department_ID = Department.id
[SELECT "m_appuser"."ID" AS "ID", "m_appuser"."LASTNAME" AS "LASTNAME", "m_appuser"."FIRSTNAME" AS "FIRSTNAME", "m_appuser"."MIDDLENAME" AS "MIDDLENAME", "departmenProjection"."ID" AS "DEPARTMENPROJECTION_ID" FROM "m_appuser" LEFT OUTER JOIN "M_DEPARTMENT" AS "departmenProjection" ON "departmenProjection"."DEPARTMENT_ID" = "m_appuser"."ID" WHERE "m_appuser"."FIRSTNAME" = ?];
Thanks
I just saw in the documentation that is not supported but I'm not sure if this is the case.
I can confirm it is not supported.
Many-To-One relationships cross the boundaries of aggregates.
References across aggregates must be modelled as ids of the referenced aggregate.
If you don't do this Spring Data JDBC will consider the reference a One-To-One relationship and part of the same aggregate which will have effects you don't want for a Many-To-One relationship, like the referenced entity getting deleted when the referenced entity gets deleted. Which would be correct for a One-To-One relationship within the same aggregate.
This is explained in more detail in https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates
I imported some JPA model classes from a MySQL schema.
Here is one of the sample classes that are auto generated by JPA:
import java.io.Serializable;
import javax.persistence.*;
import java.util.Date;
/**
* The persistent class for the surveys database table.
*
*/
#Entity
#Table(name="surveys")
#NamedQuery(name="Survey.findAll", query="SELECT s FROM Survey s")
public class Survey implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(unique=true, nullable=false)
private int id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="done_at")
private Date doneAt;
//bi-directional many-to-one association to Form
#ManyToOne
#JoinColumn(name="form_id", nullable=false)
private Form form;
//bi-directional many-to-one association to User
#ManyToOne
#JoinColumn(name="user_id", nullable=false)
private User user;
public Survey() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public Date getDoneAt() {
return this.doneAt;
}
public void setDoneAt(Date doneAt) {
this.doneAt = doneAt;
}
public Form getForm() {
return this.form;
}
public void setForm(Form form) {
this.form = form;
}
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
}
The problem of this JPA model is, it just accepts entities for the foreign keys. I mean here I have form_id and user_id as the foreign keys! However I don't have any set functions like the following:
public void setForm(int formId) {
Form form = find from table forms with id equal to formId;
this.form = form;
}
or
public void setForm(int formId) {
this.formId = formId;
}
How can I force JPA to generate such kind of functions also when it tries to import the schema!?
If it is not possible what are the best practices for extending the JPA model class? I mean as it may happen in the future by changing of the database schema I need to import the JPA models again, maybe it is not a good idea to implement these functions by hand inside the JPA model and manipulate it by hand.
Maybe you ask why do I need such kinds of set functions that accept ids instead of entities?
My program reads some mockdata and tries to fill the tables in the MySQL. The problem is in the mock data I have foreign key of the User and Form entities. They are already exist in the DB, and I don't need to create them again.
You cannot use FK-s in JPA as is. You can use JPQL/SQL to update the given row's field with the FK or you can create a service class, where you load the Form by id and then set it to the given Survey.
You should avoid to extend an auto generated class (and its agains Open-Close principle too).
Edit:
Create a class, for example FormService. There you can create a method and use Spring Data JPA, JpaTemplate, Plain JPA or other methods to retrieve the Form.
For example, with hibernate:
Form form = session.load(Form.class, id);
Then you set this to the Survey.
survay.setForm(form);
And now just save it back (persist if it is a new survey or merge if not).
session.merge(survay);
If you use Spring, it may easier and better to use Data Jpa or JpaTemplate, but the previous one will work with Hibernate (and other JPA implementations) in any java environment.
I'm starting with OrientDB and I got the basics right. But I can't figured out how to map many-to-many relationships in a way that won't require me to make tons of integrity checking when removing a record.
I am using an object database (ODatabaseObjectTx) and I'm trying to map this simple relationship:
Person <-> Roles
A Person can have multiple Roles and a Role can be assigned to multiple Persons. N to M relationship.
My person class:
public class Person {
private String name;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My Role class:
public class Role {
private String name;
public Role() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now I can't figure out if I have to put a Set<Role> in the User class or a Set<User> in the Role class to make the links. Either way if I remove one record from one I have to check the links of the other to avoid null references. To me it is more convenient to place a Set<Role> in the User class.
In a RMDBS you can just create a foreign key and add some cascading rules, does OrientDB provide similar functionality to save me the bother of doing this manually?
If in the end I have to do this manually, what strategy should I take? Should I fully check the other cluster when removing a record? Should I just leave the nulls and handle them when I see them? Should I create double references (having both Set<Role> in User AND Set<User> in Role, I guess this would bring faster removal at the cost of more space and complexity)?
take a look at documentation: https://github.com/orientechnologies/orientdb/wiki/Object-Database#cascade-deleting
I have some trouble with Hibernate 4 and inheritance:
I use a ChildData class which inherit from BaseData by a JOIN inheritance strategy. My mapping is done by annotation in classes.
Everything is working fine except that when I delete a ChildData instance (with session.delete() or with a Hql query) the BaseData entry is also deleted.
I understand that in most case this is the awaited behavior, but for my particular case, I would like to preserve the BaseData entry no matter what for history purpose.
In other words I want all actions on the child class to be cascaded to base class except deletion.
I have already tried #OnCascade on the child class, with no success.
Is it a way to achieve this by code or do I have to use a SQL Trigger ON DELETE ?
EDIT :
Base Class
#Entity
#Table(name = "dbBenchHistory", uniqueConstraints = #UniqueConstraint(columnNames = "Name"))
#Inheritance(strategy = InheritanceType.JOINED )
public class DbBenchHistory implements java.io.Serializable {
private int id;
private String name;
private String computer;
private String eap;
private Date lastConnexion;
private Set<DbPlugin> dbPlugins = new HashSet<DbPlugin>(0);
private Set<DbSequenceResult> dbSequenceResults = new HashSet<DbSequenceResult>(
0);
public DbBenchHistory() {
}
public DbBenchHistory(int id, String name) {
this.id = id;
this.name = name;
}
public DbBenchHistory(int id, String name, String computer, String eap,
Date lastConnexion, Set<DbPlugin> dbPlugins,
Set<DbSequenceResult> dbSequenceResults) {
this.id = id;
this.name = name;
this.computer = computer;
this.eap = eap;
this.lastConnexion = lastConnexion;
this.dbPlugins = dbPlugins;
this.dbSequenceResults = dbSequenceResults;
}
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
//Getters/Setters
Child Class :
#Entity
#Table(name = "dbBench")
#OnDelete(action=OnDeleteAction.NO_ACTION)
public class DbBench extends DbBenchHistory {
private Set<DbProgram> dbPrograms = new HashSet<DbProgram>(0);
private Set<DbUser> dbUsers = new HashSet<DbUser>(0);
public DbBench() {
}
public DbBench(Set<DbProgram> dbPrograms,
Set<DbUser> dbUsers) {
this.dbPrograms = dbPrograms;
this.dbUsers = dbUsers;
}
//Getters/Setters
But I'm starting to think that I was wrong from the beginning and that inheritance was not the good way to handle this. If nothing shows up I will just go for BenchHistory - Bench being a simple one-to-one relationship
EDIT2 :
I edit while I can't answer my own question for insuficient reputation
I feel completly stupid now that I found the solution, that was so simple :
As I said, I was using hibernate managed methods : session.delete() or hql query. Hibernate was doing what he was supposed to do by deletintg the parent class, like it would have been in object inheritance.
So I just bypass hibernate by doing the deletion of the child class with one of the simplest SqlQuery on earth. And the base class entry remain untouched.
I understand that I somehow violate the object inheritance laws, but in my case it is really handy.
Thanks to everyone for your time, and believ me when I say I'm sorry.
I don't think Hibernate/JPA supports this. What you basically want is conversion from a subclass to a superclass, and not a cascading delete. When you have an object of the subclass, the members from the superclass are treated no different than the members of the subclass.
This can be solved through writing some logic for it though:
public void deleteKeepSuperclassObject(final ChildData childData) {
final BaseData baseDataToKeep = new BaseData();
//populate baseDataToKeep with data from the childData to remove
em.persist(baseDataToKeep);
em.remove(childData);
}
I'm coming from a C# entity framework background and looking at JPA in a Java project so I'm hoping that what I'm facing is just a conceptual problem.
I've got a legacy database that I can't alter the schema of and I need to write a DAL.
I've generated (simplified for the example) the following entities...
#Entity
#Table(name = "crag", catalog = "rad_dbo")
public class CragEntity {
private int id;
#Column(name = "id")
#Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
private int fkSubRegionId;
#Column(name = "fk_subRegionId")
#Basic
public int getFkSubRegionId() {
return fkSubRegionId;
}
public void setFkSubRegionId(int fkSubRegionId) {
this.fkSubRegionId = fkSubRegionId;
}
}
and
#Table(name = "subRegion", catalog = "rad_dbo")
#Entity
public class SubRegionEntity {
private int id;
#Column(name = "id")
#Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I've tried adding a relationship to CragEntity so that I can access its subRegion
#ManyToOne
#JoinColumn(name="fk_SubRegionId",nullable=false)
private SubRegionEntity subRegion;
but when I try to run
select c from CragEntity c where c.subRegion.region = :area
I get an exception
java.lang.RuntimeException: org.hibernate.QueryException: could
not resolve property: subRegion of: uk.co.bmc.rad.dal.CragEntity
Hopefully this is possible and I'm being slow...
Many thanks in advance for any help!
In your query you are searching for the property "subRegion" though in your entity definition you have the name "fkSubRegionId", so you must change the var name or the query. ;)
EDIT: Sorry i misreaded the relation.
Can you access the property (without making an HQL query) with the relationship inside the code?
Unless, you want to pick only certain fields in your query I would recommend a query like:
from CragEntity c where c.subRegion.region='theRegion'
It turns out there were several issues - one conceptual, one with how IntelliJ had generated a relationship I was copying and one between the chair and keyboard...
IntelliJ had picked the region to subregion relationship with the owner at the "wrong" end - probably a schema issue rather than IntelliJ's fault. Once I realised that and figured out the fix I could copy that to CragEntity and SubRegionEntity
In CragEntity I added:
private SubRegionEntity subRegion;
#ManyToOne
#JoinColumn(name="fk_SubRegionId",nullable=false)
public SubRegionEntity getSubRegion() {
return subRegion;
}
public void setSubRegion(SubRegionEntity subRegion) {
this.subRegion = subRegion;
}
and then in SubRegionEntity I added:
private List<CragEntity> crags;
#OneToMany(mappedBy = "subRegion")
List<CragEntity> getCrags() {
return crags;
}
public void setCrags(List<CragEntity> crags) {
this.crags = crags;
}
Also, it seem that any entity class that is going to be one end of a relationship has to implement serializable (I guess the entities get serialized into the owner. So that needed adding onto SubRegionEntity and RegionEntity
The silliness on my part was of course that the query should have been c.subRegion.region.name otherwise I was comparing an object of type RegionEntity with a string... doh - very stupid mistake on my part.
I'm new to TDD but as always as soon as I wrote tests for what I thought should be happening with the existing code I was walked through my errors (and given google keywords by the exceptions and errors :-))