Spring Data JDBC - Many-to-One Relationship - java

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

Related

Objectify/AppEngine/Java: Many-many relationship

I use Objectify in AppEngine with JAva. I would like to model a many-many relationship in which the resolution entity has additional fields, as below
#Entity
public class Account {
public #Id Long id;
public String name;
}
#Entity
public class Baby {
#Id public Long id;
public String name;
}
#Entity
public class AccountBaby {
public #Id Long id;
#Index
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Ref<Account> account;
#Index
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Ref<Baby> baby;
public int permission;
}
If I follow this model, I can not query list of account for given baby's id (or list of baby for given account's id) as the query below
List<AccountBaby> babies = OfyService.ofy().load().type(AccountBaby.class).filter("account=",
Key.create(Account.class, accountId)).list();
Is they any other way to query or model this relationship?
You need a space in "account =". Or you can just leave off the "=", which is implied.
What you're actually searching for is a property which is literally named "account=" (which can be saved in the datastore with the low level api). This is mentioned in the javadocs for the filter() method.

Hibernate One to many Annotation Mapping

Hi I have a two tables like below .
1) Task - id,name
2) Resource - id,name,defaultTask(foreign key to Task.id)
The mapping is one to Many - one task can have many resource.
The code for Task is like below.
#Entity
public class Task implements Serializable {
private long m_id;
private String m_name;
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
public long getId() {
return this.m_id;
}
public void setId(long id) {
this.m_id = id;
}
public String getName() {
return this.m_name;
}
public void setName(String name) {
this.m_name = name;
}
#OneToMany
#JoinColumn(
name = "defaultTask"
)
private List<Resource> m_relatedResources;
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
public void setrelatedResources(List<Resource> relatedResources) {
m_relatedResources = relatedResources;
}
And the code for Resource class is like below.
#Entity
public class Resource implements Serializable {
private Long m_id;
private String m_name;
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
public Long getId() {
return this.m_id;
}
public void setId(Long id) {
this.m_id = id;
}
public String getName() {
return this.m_name;
}
public void setName(String name) {
this.m_name = name;
}
Task m_task;
#ManyToOne
#JoinColumn(
name = "defaultTask"
)
public Task getTask() {
return this.m_task;
}
public void setTask(Task task) {
this.m_task = task;
}
}
When i execute it I am getting an error like
Initial SessionFactory creation failed.org.hibernate.MappingException: Could not determine type for: java.util.List, for columns: [org.hibernate.mapping.Column(relatedResources)]
What have i done wrong ?How can i fix the problem ?
You can't apply annotations to methods or fields randomly. Normally, you should apply your annotations the same way as #Id..
In Task class OneToMany should be like
#OneToMany
#JoinColumn(
name = "defaultTask"
)
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
Field access strategy (determined by #Id annotation). Put any JPA related annotation right above each method instead of field / property as for your id it is above method and it will get you away form exception.
Also there appears to be an issue with your bidrectional mapping metntioned by #PredragMaric so you need to use MappedBy which signals hibernate that the key for the relationship is on the other side. Click for a really good question on Mapped by.
Many mistakes here:
you're annotating fields sometimes, and getters sometimes. Half of the annotation will be ignored: you must be consistent. It's one or the other.
You're not respecting the Java Bean naming conventions. The getter must be getRelatedResources(), not getrelatedResources().
A bidirectional association must have an owner side and an inverse side. In a OneToMany, the One is always the inverse side. The mapping should thus be:
.
#ManyToOne
#JoinColumn(name = "defaultTask")
public Task getTask() {
return this.m_task;
}
and
#OneToMany(mappedBy = "task")
public List<Resource> getRelatedResources() {
return m_relatedResources;
}
I also strongly advise you to respect the Java naming conventions. Variables should be named id and name, not m_id and m_name. This is especially important if you choose to annotate fields.
You're mixing annotating fields and getters in the same entity, you should move your #OneToMany to a getter
#OneToMany
#JoinColumn(mappedBy = "task")
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
and yes, as the others mentioned, it should be mappedBy = "task". I'll upvote this teamwork :)
#JoinColumn is only used on owner's side of the relation, ToOne side, which is Resource#task in your case. On the other side you should use mappedBy attribute to specify bidirectional relation. Change your Task#relatedResources mapping to this
#OneToMany(mappedBy = "task")
private List<Resource> m_relatedResources;
Also, as #Viraj Nalawade noticed (and others, obviously), mapping annotations should be on fields or properties, whatever is used for #Id takes precedence. Either move #Id to field, or move #OneToMany to getter.

How to add a JPA relationship against legacy database

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 :-))

Hibernate failing by prepending fully qualified class name to property name on ManyToMany association

I'm trying to map two objects to each other using a ManyToMany association, but for some reason when I use the mappedBy property, hibernate seems to be getting confused about exactly what I am mapping. The only odd thing about my mapping here is that the association is not done on a primary key field in one of the entries (the field is unique though).
The tables are:
Sequence (
id NUMBER,
reference VARCHAR,
)
Project (
id NUMBER
)
Sequence_Project (
proj_id number references Project(id),
reference varchar references Sequence(reference)
)
The objects look like (annotations are on the getter, put them on fields to condense a bit):
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany(mappedBy="sequences")
private List<Project> projects;
}
And the owning side:
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(name="sequence_project",
joinColumns=#JoinColumn(name="id"),
inverseJoinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Sequence> sequences;
}
This fails with a MappingException:
property-ref [_test_local_entities_Project_sequences] not found on entity [test.local.entities.Project]
It seems to weirdly prepend the fully qualified class name, divided by underscores. How can I avoid this from happening?
EDIT:
I played around with this a bit more. Changing the name of the mappedBy property throws a different exception, namely:
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: test.local.entities.Project.sequences
So the annotation is processing correctly, but somehow the property reference isn't correctly added to Hibernate's internal configuration.
I have done the same scenario proposed by your question. And, as expected, i get the same exception. Just as complementary task, i have done the same scenario but with one-to-many many-to-one by using a non-primary key as joined column such as reference. I get now
SecondaryTable JoinColumn cannot reference a non primary key
Well, can it be a bug ??? Well, yes (and your workaround works fine (+1)). If you want to use a non-primary key as primary key, you must make sure it is unique. Maybe it explains why Hibernate does not allow to use non-primary key as primary key (Unaware users can get unexpected behaviors).
If you want to use the same mapping, You can split your #ManyToMany relationship into #OneToMany-ManyToOne By using encapsulation, you do not need to worry about your joined class
Project
#Entity
public class Project implements Serializable {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="project")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Sequence> sequenceList = null;
// getters and setters
public void addSequence(Sequence sequence) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(id, sequence.getReference())));
}
public List<Sequence> getSequenceList() {
if(sequenceList != null)
return sequenceList;
sequenceList = new ArrayList<Sequence>();
for (ProjectSequence projectSequence : projectSequenceList)
sequenceList.add(projectSequence.getSequence());
return sequenceList;
}
}
Sequence
#Entity
public class Sequence implements Serializable {
#Id
private Integer id;
private String reference;
#OneToMany(mappedBy="sequence")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Project> projectList = null;
// getters and setters
public void addProject(Project project) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(project.getId(), reference)));
}
public List<Project> getProjectList() {
if(projectList != null)
return projectList;
projectList = new ArrayList<Project>();
for (ProjectSequence projectSequence : projectSequenceList)
projectList.add(projectSequence.getProject());
return projectList;
}
}
ProjectSequence
#Entity
public class ProjectSequence {
#EmbeddedId
private ProjectSequenceId projectSequenceId;
#ManyToOne
#JoinColumn(name="ID", insertable=false, updatable=false)
private Project project;
#ManyToOne
#JoinColumn(name="REFERENCE", referencedColumnName="REFERENCE", insertable=false, updatable=false)
private Sequence sequence;
public ProjectSequence() {}
public ProjectSequence(ProjectSequenceId projectSequenceId) {
this.projectSequenceId = projectSequenceId;
}
// getters and setters
#Embeddable
public static class ProjectSequenceId implements Serializable {
#Column(name="ID", updatable=false)
private Integer projectId;
#Column(name="REFERENCE", updatable=false)
private String reference;
public ProjectSequenceId() {}
public ProjectSequenceId(Integer projectId, String reference) {
this.projectId = projectId;
this.reference = reference;
}
#Override
public boolean equals(Object o) {
if (!(o instanceof ProjectSequenceId))
return false;
final ProjectSequenceId other = (ProjectSequenceId) o;
return new EqualsBuilder().append(getProjectId(), other.getProjectId())
.append(getReference(), other.getReference())
.isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getProjectId())
.append(getReference())
.hashCode();
}
}
}
I finally figured it out, more or less. I think this is basically a hibernate bug.
edit: I tried to fix it by changing the owning side of the association:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(name="sequence_project",
inverseJoinColumns=#JoinColumn(name="id"),
joinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany(mappedBy="projects")
private List<Sequence> sequences;
}
This worked but caused problems elsewhere (see comment). So I gave up and modeled the association as an entity with many-to-one associations in Sequence and Project. I think this is at the very least a documentation/fault handling bug (the exception isn't very pertinent, and the failure mode is just wrong) and will try to report it to the Hibernate devs.
IMHO what you are trying to achieve is not possible with JPA/Hibernate annotations. Unfortunately, the APIDoc of JoinTable is a bit unclear here, but all the examples I found use primary keys when mapping join tables.
We had the same issue like you in a project where we also could not change the legacy database schema. The only viable option there was to dump Hibernate and use MyBatis (http://www.mybatis.org) where you have the full flexibility of native SQL to express more complex join conditions.
I run into this problem a dozen times now and the only workaround i found is doing the configuration of the #JoinTable twice with swapped columns on the other side of the relation:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="reference", referencedColumnName="reference"),
inverseJoinColumns = #JoinColumn(name="id")
)
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="id"),
inverseJoinColumns = #JoinColumn(name="reference", referencedColumnName="reference")
)
private List<Sequence> sequences;
}
I did not yet tried it with a column different from the primary key.

Hibernate/JPA - Foreign Key Index in Object itself

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.

Categories

Resources