How to add a JPA relationship against legacy database - java

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

Related

Spring Data JDBC - Many-to-One Relationship

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

How to automatically move annotations from methods to fields?

I have a large number of Java classes using JPA property access instead of field access, and converting them to use field access (moving the annotations from getters to fields) is time consuming. Re-generating them from database tables is also not an option.
So I was looking at using IDEA's structural search and replace to try move the annotations automatically. I'm wanting to change a class that looks like this:
#Entity
#Table
public class Make {
private Long id;
private String makeCode;
...other properties, with more involved JPA annotations
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(length = 10, unique = true)
public String getMakeCode() {
return makeCode;
}
public void setMakeCode(String makeCode) {
this.makeCode = makeCode;
}
...
}
to one that looks like this:
#Entity
#Table
public class Make {
#Id
#GeneratedValue
private Long id;
#Column(length = 10, unique = true)
private String makeCode;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMakeCode() {
return makeCode;
}
public void setMakeCode(String makeCode) {
this.makeCode = makeCode;
}
...
}
From there I'll be able to do additional refactoring, but I'm finding that moving the annotations manually is very time-consuming.
I've played around with structural search & replace, but its quite dense and lacking documentation, so I'd appreciate any tips on how to configure the different templates.
I imagine it would be something like:
find all methods starting with "get" that are annotated with at least
one javax.persistence.* annotation, along with a matching private
field, and replace with the same field and method, but move the
annotations from method to field.
I'm just struggling to put that into the structural search expressions.

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.

hibernate inheritance : How to protect base class entry on child class deletion

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);
}

Hibernate MappingException

I'm getting this Hibernate error:
org.hibernate.MappingException: Could not determine type for:
a.b.c.Results$BusinessDate, for columns: [org.hibernate.mapping.Column(businessDate)]
The class is below. Does anyone know why I'm getting this error??
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"businessDate"
})
#XmlRootElement(name = "Results")
#Entity(name = "Results")
#Table(name = "RESULT")
#Inheritance(strategy = InheritanceType.JOINED)
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Results implements Equals, HashCode
{
#XmlElement(name = "BusinessDate", required = true)
protected Results.BusinessDate businessDate;
public Results.BusinessDate getBusinessDate() {
return businessDate;
}
public void setBusinessDate(Results.BusinessDate value) {
this.businessDate = value;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"raw",
"display"
})
#Entity(name = "Results$BusinessDate")
#Table(name = "BUSINESSDATE")
#Inheritance(strategy = InheritanceType.JOINED)
public static class BusinessDate implements Equals, HashCode
{
....
Update: This code was generated by HyperJaxB. So I don't claim to understand it all, just trying to make some changes to it!
Update2: Here's the full (yah it's big) src file
Using a static nested class as a field type is fine and supported. But Hibernate won't know how to map such a complex type to a column type (which is what the error message says).
So you'll need either to create a user type to handle this or to annotate the Results.BusinessDate field with a #OneToOne annotation to persist it in another table (I would also remove the #Inheritance which is useless but this is not the problem here).
Update: Just to clarify, using a user type or mapping the complex type with #OneToOne does work. The following code works perfectly (tested):
#Entity
public class EntityWithStaticNestedClass implements Serializable {
#Id
#GeneratedValue
private Long id;
#OneToOne
private EntityWithStaticNestedClass.StaticNestedClass nested;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public EntityWithStaticNestedClass.StaticNestedClass getNested() {
return nested;
}
public void setNested(EntityWithStaticNestedClass.StaticNestedClass nested) {
this.nested = nested;
}
#Entity
public static class StaticNestedClass implements Serializable {
#Id
#GeneratedValue
private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
}
And both entities get well persisted in their respective tables. But you're not showing the entire code nor the exact error so I can't say why it didn't for you (maybe you're missing #Id etc).
That being said, if you don't want businessDate to be persisted at all, annotate it with #Transient (with JPA, fields are persistent by default):
Update: You can't mix field and property access. So you need to annotate getBusinessDate() with #Transienthere. Sorry, I couldn't guess that from the shown code and I thought it would be obvious.
Same comment as Kevin Crowell. You might also look at not using inner classes for entity types. I've actually never seen someone do that with Hibernate, so I'm not sure if it's even possible, or how you would map it.
The #Inheritance annotation on the BusinessDate inner class seems a little fishy too - the inner class is static, and does not inherit from another entity, unless Hibernate treats inner classes as "inherited."
Overall, not really sure what you're trying to accomplish, but you might be making your life harder than it should be. I would recommend not using inner classes, and just mapping all the entities in a more simple/straightforward fashion.

Categories

Resources