JPA i18n issue with mapping columns - java

I'm working on a project, where some of the data are stored in multiple languages. For persistence I use Spring Data JPA with Hibernate. My model is the following:
I have an embeddable class for storing a string in a given locale:
#Embeddable
public class MultilingualData {
#Column(name = "locale", nullable = false)
#Convert(converter = LocaleConverter.class)
private Locale locale;
#Column(name = "value")
private String value;
}
And I also have a class, where I want to use that MultilingualData as a value of a Map, and the Locale in the MultilingualData as key for a better processing.
#Entity
#Table(name = "multilingual_string")
public class MultilingualString extends BaseEntity {
private static final long serialVersionUID = -2434353448112062292L;
#ElementCollection(fetch = FetchType.EAGER)
#MapKeyJoinColumn(name = "locale", insertable = false, updatable = false)
#CollectionTable(name = "multilingual_string_map",
joinColumns = #JoinColumn(name = "string_id"))
private Map<Locale, MultilingualData> localizedStrings;
A class where I use the MultilingualString:
#Entity
#Table(name = "location")
public class Location extends BaseEntity {
private static final long serialVersionUID = 1L;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "name")
private MultilingualString name;
So when JPA generates the tables, in the 'multilingual_string_map' two columns are generated for the locale itself. One with the name 'locale', as given in the model, and it creates another one with 'localized_strings_key' as part of the primary key. So when I store a multilingual data, the locale is saved twice.
I want to get rid of that extra column, because it is redundant. I know I did not configured the mapping right, so I hope someone can fix my mistake here.
Thanks in advance!

Why would you define the join column as insertable = false? Is the map never going to be populated from within your code?
In any case, #MapKeyJoinColumn is only used when the map key is an entity. #MapKeyColumn is what you want.
(the fact that #MapKeyColumn is missing from your model means Hibernate was forced to use a default name for the key column i.e. localized_strings_key. The fact that the locale column also got created despite the map key not being an entity is, well, undocumented behaviour)

Related

JPA/Hibernate Spring boot-primary key one entity referred as an instance to other entity not working

I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.

How to use derived columns in Spring Data JPA associations

I have an entity TeamActivity:
#Entity
#Table(name = "teams_to_activities")
public class TeamActivity {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "team_id", nullable = false)
private String teamId;
#Column(name = "activity_set_id", nullable = false)
private String activitySetId;
#Id
#Column(name = "scoped_team_activity_id", nullable = false)
private String scopedTeamActivityId;
}
And another entity ActivitySet:
#Entity
#Table(name = "activity_sets")
public class ActivitySet {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Id
#Column(name = "scoped_activity_set_id", nullable = false)
private String scopedActivitySetId;
}
There's no index on any other column besides the PK in both tables.
There's no FK constraint creating a relationship between these tables whatsoever. I have no idea why as this is a legacy system.
Technically, if I fetch a TeamActivity record, I can pick the scope_id and activity_set_id from it and combine them to form a scoped_activity_set_id which would be a valid PK to fetch the corresponding ActivitySet.
I know that TeamActivity -> ActivitySet is a N -> 1 association
I would like to leverage Spring Data JPA features to create an association from TeamActivity to ActivitySet such that when I fetch a TeamActivity from TeamActivityRepository, the corresponding ActivitySet is also returned.
I have created an association like this before using a combination of #JoinColumn and #MapsId but there was actually a single FK to use which is different here where source table has 2 columns I can combine to get the target's key.
If you are fully in control of the database, I may propose you create a Materialized View with the contents you desire from both tables and handle it as any other table with JPA, i.e, create #Entity model and CrudRepository<MVTeamActivitySet, String>.
If you are not fully in control of the database, one easy way to achieve it is to simply create a method that internally executes two lookup queries and retrieves the expected model you want. You will still be using using JPA correctly.
Querying two tables and joining desired fields in the code layer is quite common with denormalized DBs, sometimes you want to avoid the overhead of a Materialized View.
#Override
public TeamActivitySetDto findById(String scopedTeamActivityId) throws DemoCustomException {
Optional<TeamActivity> teamActivityEntity = teamActivityDao.getById(scopedTeamActivityId);
if(teamActivityEntity.isEmpty()) {
throw new DemoCustomException("teamActivity record not found");
}
String scopedActivitySetId =
teamActivityEntity.get().getScopeId() + ":" + teamActivityEntity.get().getActivitySetId();
Optional<ActivitySet> activitySetEntity = activitySetDao.getById(scopedActivitySetId);
if(activitySetEntity.isEmpty()) {
throw new DemoCustomException("activitySet record not found");
}
return TeamActivitySetDto.builder()
.description(activitySetEntity.get().getDescription())
.name(activitySetEntity.get().getName())
.scopedActivitySetId(activitySetEntity.get().getScopedActivitySetId())
.activitySetId(teamActivityEntity.get().getActivitySetId())
.scopedTeamActivityId(teamActivityEntity.get().getScopedTeamActivityId())
.scopeId(teamActivityEntity.get().getScopeId())
.teamId(teamActivityEntity.get().getTeamId())
.build();
}

Hibernate ForeignKey mapping annotations

I want to have hibernate generate some tables with foreign keys and so on. Ill give you an example of the query i want hibernate to generate:
create table RealtimeCost(id INTEGER not null primary key Autoincrement,
mnemonic varchar(50)not null references Exchange(mnemonic),
sid int not null references License(sid),
price numeric(10,2) not null)
so this query should be generated by hibernate via Annotations. The corresponding class to this is:
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#MapsId("mnemonic")
#JoinColumn(referencedColumnName="sid")
private String mnemonic;
#MapsId("sid")
#JoinColumn(referencedColumnName="sid")
private Integer sid;
#Column
private Double price;
Example for what the mnemonic in RealtimeCost should be mapped to (each mnemonic in RealtimeCost has exactly 1 value in Exchange):
#Entity
#Table
public class Exchange {
#Id
#Column(name="mnemonic")
private String exchange;
#Column
private String description;
As you can see I've tried a bit with the help of the docs, but I was not able to have the foreign keys be generated by hibernate. It would be really kind, if anyone could tell me the needed annotations and values for this class, so i can do it myself for the other classes as well. Also please tell me if i need to change anything in the Exchange class for the mapping to work. Thanks in advance
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "accommodation_type", unique = true, nullable = false)
private AccommodationType accommodationType;
#ManyToOne()creates a relationship according to #JoinColumn()
name in #JoinColumn() is the table name that you want to make a connection.
Then when you create a class that is going to be connected to main class, you first need to give it a table name below #Entity e.g #Table(name="accommodation_types")
Then you create your variable.
//bi-directional many-to-one association to Accommodation
#OneToMany(mappedBy="accommodationType", fetch=FetchType.EAGER)
private List<Accommodation> accommodations;
value of mappedByis the variable name in main class.
I'm not an expert but we let hibernate do all the work with the javax.persistence annotations for joining entities.
#javax.persistence.ManyToOne( fetch = javax.persistence.FetchType.EAGER, optional = true )
#javax.persistence.JoinColumn( name = "VIEWTYPE_ID", nullable = true, unique = false, insertable = true, updatable = true )
private com.company.other.subproject.ViewType viewType;
Maybe this is what you need. Since this let's hibernate care about the tables that have to be created or not and the foreignKeys get created automatically with the dialect of the database you communicate with.
You should set up the association in one entity and use the mappedBy in the other. You don't need #MapsId because you are not using embedded entities (read the docs). Take a look at the #OneToMany and #ManyToOne relationships:
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany
#JoinColumn(name="mnemonic")
private Exchange exchange;
...
}
#Entity
#Table
public class Exchange {
#Id
#Column(name="mnemonic")
private String mnemonic;
#Column
private String description;
#ManyToOne(mappedBy="exchange")
private RealtimeCost realtimeCost;
...
}
Every answer posted here got an upvote from me, because everyone was kinda right, but it was not 100% what i was searching for, yet it helped me solving my problem by myself. For the example i posted, the solution i was seeking is as follows (i also added not nullable):
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "mnemonic",nullable=false)
private Exchange exchange;
#ManyToOne
#JoinColumn(name = "sid",nullable=false)
private License license;
#Column(nullable=false)
private Double price;
these are the annotations i was seeking for RealtimeCost class. I did not need any special annotations in Exchange class. #Nico answer was closest to what i need, therefore his answer will be accepted

JPA 2 Hibernate mapping with composite key in primary key using #IdClass with 3 tier structure

This question is very similar to: JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, #EmbeddedId composite PK-FK)?
Actually my only (from meaningful that I spotted) difference is that I use #IdClass and that I most probably won't be able to switch to a different provider than hibernate.
but anyway here is the code (removed parts that where unimportant):
PermissionContextType.java:
#Entity
#IdClass(PermissionContextTypePk.class)
public class PermissionContextType{
#Id
private String id;
#Id
#JoinColumn (name = "PROJECT", referencedColumnName = "ID")
#ManyToOne ()
private Project project;
public static class PermissionContextTypePk implements Serializable{
public String project;
public String id;
// ... eq and hashCode here ...
}
}
PermissionContext.java:
#Entity
#IdClass(PermissionContextPk.class)
public class PermissionContext{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContextType permissionContextType;
public static class PermissionContextPk implements Serializable{
public String id;
public PermissionContextTypePk permissionContextType;
// ... eq and hashCode here ...
}
}
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE"),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
public String id;
public PermissionContextPk permissionContext;
// ... eq and hashCode here ...
}
}
and what I get is:
org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
Caused by: org.hibernate.AssertionFailure: org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
does anybody know if this is a hibernate bug and I should post it on their issue tracking system (and pray that I would be able to update to given hibernate version) or is there something fundamentally wrong with my way of binding the entities?
I've checked it with the hibernate implementation on EAP 6.1 (4.2.0) as well as on wildfly (don't really know which one.)
Ok, so this is what I found so far :
Thanks fr my friend : https://hibernate.atlassian.net/browse/HHH-5764 which most probably is the reason for this behaviour.
And I found a workaround :
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
// for the next 3 fields there are no public acessors, so the public API of the class was not changed !
#Id
#Column(name = "PROJECT")
private String projectId;
#Id
#Column(name = "PERMISSIONCONTEXTTYPE")
private String permissionContextTypeId;
#Id
#Column(name = "PERMISSIONCONTEXT")
private String permissionContextId;
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID", updatable = false, insertable = false)
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
// previously they where private as well, but removed public constructor for the sake of simplicity of the question - so no changes where necesary in public API of the class !
private String id;
private String projectId;
private String permissionContextTypeId;
private String permissionContextId;
public PermissionPk () {}
public PermissionPk (String aId, PermissionContextPk aPermissionContext) {
this.id = aId;
permissionContextId = aPermissionContext.id;
permissionContextTypeId = aPermissionContext.permissionContextType.id;
projectId = aPermissionContext.permissionContextType.project;
}
... eq and hashCode here ...
}
}
The good thing about this workaround is that it does not change the public API of the class in any way
(the only change was that I needed to make fields in Pk's of context and contexttype visible to the PermissionPk - they where private before with only a public constructor [but again simplified for the question]), nor did it change the jpql queries, and at the same time workaround is scalable (to any tier amount - as long as every even pk does not contain another pk), so if the bug will be resolved it will be easy to remove the workaround.
I would still gladly accept any comments on either my workaround or the question in itself.
Today I found another workaround :)
You can omit #IdClass entirely and use hibernate specific ability to create composite keys on the fly as apparently it is not affected by this bug.
The drawback here is that:
it is entirely Hibernate specific not covered by JPA at all.
you cannot do em.find(ClassName.class,new ClassPk(args...)) as there is no ClassPk at all.
But if you could use anything else than hibernate you could just as well use something without this bug - so probably 1 is not a problem really. and there is a possibility that you don't really need the em.find for this entity (or can live with creating it thru session or jpql query).

Handling creation of ORM objects prior to persistence/generation of primary keys?

Bear with me as I try to simplify my issue as much as possible.
I am creating a new ORM object. This object has an auto generated primary key which is created on the database using as an identity. Within this object, is a child object with a many to one relationship with the parent object. One of the attributes I need to set to create the child object is primary key of the parent object, which has not been generated yet. It is important to note that the primary key of the child object is a composite key that includes the primary key of the parent object.
Diagram http://xs941.xs.to/xs941/09291/fieldrule.1degree221.png
In this diagram FieldRule is the child table and SearchRule is the parent table. The problem is that SearchRuleId has not been generated when I am creating FieldRule objects. So there is no way to link them.
How do I solve this problem?
Here is are some relevant snippets from the entity classes, which use annotation based mappings.
From SearchRule.java (Parent Class):
public class SearchRule implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = true)
#Column(name = "ID")
private Integer id;
#Basic(optional = false)
#Column(name = "Name", unique = true)
private String name;
#Basic(optional = false)
#Column(name = "Threshold")
private int threshold;
#Basic(optional = false)
#Column(name = "LastTouched", insertable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
#Column(name = "TouchedBy")
private String touchedBy;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "searchRule", fetch = FetchType.LAZY)
private Collection<FieldRule> fieldRuleCollection;
#JoinColumn(name = "IndexTemplateId", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private IndexTemplate indexTemplateId;
From FieldRule.java (Child Class):
public class FieldRule implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected FieldRulePK fieldRulePK;
#Basic(optional = false)
#Column(name = "RuleValue")
private String ruleValue;
#JoinColumns({#JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), #JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Field field;
#JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private SearchRule searchRule;
From FieldRulePK.java (Child PK Class):
#Embeddable
public class FieldRulePK implements Serializable {
#Basic(optional = false)
#Column(name = "IndexTemplateId")
private Integer indexTemplateId;
#Basic(optional = false)
#Column(name = "FieldNumber")
private Integer fieldNumber;
#Basic(optional = false)
#Column(name = "SearchRuleId")
private Integer searchRuleId;
Why do you have to set the primary key of the initial object in the sub-objects? With a proper mapping the reference will get set by the JPA application automatically.
So the answer is: do a correct mapping.
If you need a more detailed answer provide a more detailed question. Including:
source code of the involved classes
source code used to create and persist the instances
exceptions experienced
information on which jpa implementation you use
Edit, after more details where provided in the question:
I think your embeddable PK should look something like this:
#Embeddable
public class FieldRulePK implements Serializable {
#Basic(optional = false)
#Column(name = "IndexTemplateId")
private Integer indexTemplateId;
#Basic(optional = false)
#Column(name = "FieldNumber")
private Integer fieldNumber;
#ManyToOne( ... some not so trivial details here ..)
private SearchRule searchRule;
}
And the searchRule property of your FieldRule should be dropped. The entity reference in the embeddable should result in an id field in the database.
This is a database design issue, I think. If the FieldRule can be created independently of the SearchRule (in other words, SearchRuleId is not a "not null" field) then you need to not include it in your composite primary key. If SearchRuleId cannot be null, then you just have to save the objects in the right order, which your ORM should handle for you if your mapping is correct.
I think the problem is with the way you're doing your mapping, where you're trying to pull too many database concepts into your OO model. ORM was a little confusing to me as well, when I started doing it. What you need to understand is that the concept of a primary key field is a database concept and not an OO concept. In OO, each object reference is unique, and that's what you use to identify instances.
Object references do not really map well to the database world, and that's why we have primary key properties. With that said, the use of primary key properties should be kept to a minimal. What I find helpful is to minimize the type of primary key properties that map directly to the primary key columns (usually, integer properties that map to a primary key column).
Anyway, based on that, here's how I think you should do your mapping (changes highlighted with horizontal separators):
From FieldRule.java (Child Class):
public class FieldRule implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected FieldRulePK fieldRulePK;
#Basic(optional = false)
#Column(name = "RuleValue")
private String ruleValue;
// Removed field and searchRule mapping as those are already in the
// primary key object, updated setters/getters to pull properties from
// primary key object
public Field getField() {
return fieldRulePK != null ? fieldRulePK.getField() : null;
}
public void getField(Field field) {
// ... parameter validation ...
if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
fieldRulePK.setField(field);
}
public SearchRule getSearchRule() {
return fieldRulePK != null ? fieldRulePK.getSearchRule() : null;
}
public void setSearchRule(SearchRule searchRule) {
// ... parameter validation ...
if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
fieldRulePK.setSearchRule(searchRule);
}
From FieldRulePK.java (Child PK Class):
#Embeddable
public class FieldRulePK implements Serializable {
// Map relationships directly to objects instead of using integer primary keys
#JoinColumns({#JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), #JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Field field;
#JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private SearchRule searchRule;
SearchRule.java should be fine as it is.
I hope this all makes sense.
Note that this is untested, it would take too much time for me to set up a test database and create all the necessary test code, but I hope it gives you an idea on how to proceed.
Posting this mostly because I can't leave this complicated of comment... but anyway...
Normally when I look at EmbeddedId type things I see things like from this example of Embeddable keys. Normally I'd expect something like
From ChildPK.java:
#Basic(optional = false)
#Column(name = "ParentId")
private Parent parent;
But here I guess we've got 2 other FKs being made into a composite PK, IndexTemplateId and FieldNumber... and this Parent object's ID is auto-generated using a sequence.
Now I suppose that you must already be persisting the Parent object prior to trying to persist the child object or you must mark the Parent object in child as cascading, that should ensure the id gets populated, the composite keys seem to greatly complicate the problem.
Since this is a new ORM I would suggest that you use a single PK on each table instead of composite ids and simply have FK relations between the tables.
Apologies if I'm not grasping something here, but I'm not quite sure there is enough information here - I would ask for the entire Entity field declarations just to see how you're trying to put this together each of your 3 classes...
Something is a bit fishy here. Generally speaking if you have parent entity A and child entity B and you are persisting A with some children the correct order of operations is first inserting A into the database and then inserting children (I am assuming proper cascade from A to B). So in this general case the ids will be properly generated and everything should OK.
However it appears that in your case children (FieldRules) are saved first. The only reasonable explanation for this I can think of is that if you have an additional entity C (in your case probably Field entity) which is already saved when your code is running and it has a cascade to FieldRules. In this case you have two conflicting cascades: one SearchRule -> FieldRule and another Field -> FieldRule. Since JPA doesn't perform smart analysis of this it is a matter of chance (and loading order) which one will get invoked first. And in your case the Field->FieldRules is probably invoked which causes the children to be inserted before parent.
So I would try to search for any additional cascades TO FieldRules in your code and try to remove those. If you can remove them all it will probably solve your problem
Bottom line, your searchRule MUST be saved before your fieldRules can be.
However, rather than having the column definition on the field, you could try having it on a getter...
#Embeddable
public class FieldRulePK implements Serializable {
//snip other columns
#Basic(optional = false)
#Column(name = "SearchRuleId")
private Integer getSearchRuleId()
{
return this.fieldRule.searchRule.getId();
}
private void setSearchRuleId(Integer id)
{
this.fieldRule.searchRule = new SearchRule(id);
}
This would mean that when the saveSearchRule(searchRule) cascades into the FieldRuleCollection to save that, the searchRuleId is automatically retrieved from the searchRule after it is saved, rather than having to hackily be added in.
It means whatever creates your FieldRulePK object has to pass a reference to it's parent, but otherwise means your hacky setSearchRuleId() loop is unnecessary.
Why does the "sub-object" (I think you mean "child") need to have the key to the parent object? If you have a OneToMany on the Parent object and a ManyToOne on the Child object with mappedBy, your child object will already have a foreign key (and a reference to the parent object).
Also, you need to check you cascade in your Parent object OneToMany annotation.
Simple answer: don't rely on your persistence layer generating the IDs at the time of persistence. Create the entity IDs at the time you create the objects.
Unless you are coding some specific meaning into your keys (a database anti-pattern), they can be any random, unique value such as a UUID (GUID for the Microsofties).
And here's something to think about when you use your persistence layer to generate the ID/primary key: do you use the entity's primary key in the hashcode or equals method?
If you do use the ID/primary key in the hashcode/equals method then you will break the contract expected of objects when stored in a Java collection. See this Hibernate page for more details.
Right now my work around is doing something like,
Collection<FieldRule> fieldRules = searchRule.getFieldRuleCollection();
if (searchRule.getId() == null)
{
//null out the collection so it doesn't cascade on persist
searchRule.setFieldRuleCollection(null);
//save to get id
dao.saveSearchRule(searchRule);
for (FieldRule fr : fieldRules) {
fr.getFieldRulePK().setSearchRuleId(searchRule.getId());
}
}
//re set collection
searchRule.setFieldRuleCollection(fieldRules);
//remove double refrence, which jpa doesn't like, to FieldRuleCollection
fieldRules = null;
//save again, this time for real
dao.saveSearchRule(searchRule);
That seems really hackey to me, but it does work (maybe, I'm hitting some other issues but they may be unrelated).
There must be a better way to turn off casacade for a single persist.

Categories

Resources