How to Include non-matching id in JPA OneToOne mapping - java

So I have a table with a column that has non-foreign key (no actual table reference) reference on another table but the other table might not have a matching row
class Component {
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "PART_ID", referencedColumnName="PART_ID", nullable = true, insertable = false, updatable = false)
#NotFound(action = NotFoundAction.IGNORE)
private Part part
#Id
#Column(name = "COMPONENT_ID")
private Long id;
}
Part class
class Part {
#Id
#Column(name = "PART_ID")
private Long id;
private String name;
}
without this #NotFound(action = NotFoundAction.IGNORE) I am getting an error
but with this I am not getting an error and get null value but I need the id to be present
e.g) I am looking for this
{"component": {"id":12, "part":{"id":100,"name":null}}}
but I am getting this (if no match)
{"component": {"id":12, "part":null}}
but I am getting this (if match)
{"component": {"id":12, "part":{"id":100,"name":"part_name"}}}
Tried with nullable=false and some combinations for updatable and insertable and still nothing works

If table has reference on another table but the other table might not have a matching row , the database structure is probably broken.
Do you really need what you're asking for?
The database has 2 part_id. One in Component and the other in Part. Java code has only one - Part.
You can make an additional field
сlass Component {
private Long partId;
...
and in the #PostLoad method - create a new Part with the desired id.
#PostLoad
public void postLoad(){
if(part == null && partId!=null){
part= new Part();
part.setId(partId)
}
}
}

Related

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 OneToOne between PK's with lazy behaviour

I'm trying to achieve to have an entity called MyEntity along with another entity called MyEntityInfo using Hibernate 5.3.13.Final with annotations under Wildfly 18.
The idea is to have MyEntity store some commonly requested fields, and MyEntityInfo store some rarely requested fields. Both share the same primary key called SID (Long), and there is a FK from Info's SID to Entity's SID. There can be entities without info.
Normally you will not require the additional info. For example, I don't want the info entity to be fetched when I query my entity like this:
MyEntityImpl entity = em.find(MyEntityImpl.class, 1L);
However, when I run this code, I find that there's a second query, fetching the Info entity along the main one, as in an EAGER behaviour.
I'm mapping the relationship using #OneToOne. I've tried several combinations of FetchType, optional and #LazyToOne, but so far without success.
Here is the code for both MyEntity and MyEntityInfo classes (additional getters and setters removed):
MyEntity (ID generator is a custom sequence generator):
#Entity
#Table(name = MyEntityImpl.TABLE_NAME)
public class MyEntityImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY";
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_" +
TABLE_NAME)
#GenericGenerator(name = "GEN_" +
TABLE_NAME, strategy = CoreIdGenerator.ID_GENERATOR, parameters = {
#Parameter(name = "tableName", value = TABLE_NAME) })
#Column(name = "sid", nullable = false, unique = true)
private Long sid;
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#LazyToOne(LazyToOneOption.NO_PROXY)
private MyEntityInfoImpl info;
#Column
private String field;
MyEntityInfo:
#Entity
#Table(name = MyEntityInfoImpl.TABLE_NAME)
public class MyEntityInfoImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY_INFO";
#Id
#Column(name = "SID", nullable = false, unique = true)
private Long sid;
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;
#Column(name = "INFO_FIELD")
private String infoField;
I've tried this solution, but as I said, it didn't work for me:
Hibernate lazy loading for reverse one to one workaround - how does this work?
I've managed to do something somewhat similar using #OneToMany and managing data manually, but that's not what I'd like to do. However, another alternatives and information on whether this can be achieved or not using #OneToOne, or the right design pattern to do this are also welcome.
PS: Database tables creation for SQL Server, in case you want to try it:
create table TMP_MY_ENTITY (SID NUMERIC(19,0) NOT NULL, FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY ADD CONSTRAINT PK_TMP_MY_ENTITY PRIMARY KEY CLUSTERED (SID);
go
create table TMP_MY_ENTITY_INFO (SID NUMERIC(19,0) NOT NULL, INFO_FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY_INFO ADD CONSTRAINT PK_TMP_MY_ENTITY_INFO PRIMARY KEY CLUSTERED (SID);
go
CREATE SEQUENCE SEQ_TMP_MY_ENTITY START WITH 1 INCREMENT BY 1 MINVALUE 1 CACHE 20;
alter table TMP_MY_ENTITY_INFO add constraint FK_TMP_MY_ENT_INFO_MY_ENT FOREIGN KEY (SID) references TMP_MY_ENTITY(SID);
go
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 1');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 1');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 2');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 2');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 3 no info');
-- DELETE ALL
drop table TMP_MY_ENTITY_INFO;
drop table TMP_MY_ENTITY;
drop sequence SEQ_TMP_MY_ENTITY;
After following #SternK link, and upgrading to Wildfly 19 and Hibernate 5.4.14, it finally worked by using #MapsId.
The right mapping to use is this:
MyEntity:
public class MyEntityImpl {
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "SID")
private MyEntityInfoImpl info;
MyEntityInfo:
public class MyEntityInfoImpl {
#OneToOne(fetch = FetchType.EAGER, optional = false)
#MapsId
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;

Spring Data update entity with yesterday's date

I have some problems with update (and also insert) data into my database. I have an entity with some integer properties, some String properties, but also is there one property with LocalDate type, and it has to be unique.
I put a lot of entities like that into the database, but user needs to edit it and update some properties. When I tried to test it and change some String property and save updated entity to db I saw this error log in the console:
Duplicate entry '2019-07-27' for key 'work_day_workday_date_uindex'
As you can see, Hibernate tries to put object with yesterday's date. But... why? I checked it in traditional ( :D ) way -> by entering System.out.println instruction before saving object into database.
Log shows me a correct date in printing:
WorkDay{id=296, date=2019-07-28, workingTime=8,....
So I think that the problem is connected with differences in time between database and application.
I found some tips here, in StackOverflow. Somebody said that removing serverTimezone=UTC from application.properties in SpringBoot could help. And it fixed the problem - yesterday I updated the entity successfully. But today I come back to coding and the problem appeared again.
I hope that maybe some of you had this problem in past and know some solution - it will be very helpful for me :)
Here is WorkDay Entity:
#Entity
#Table(name = "work_day")
public class WorkDay implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_workday")
private Long id;
#NotNull
#Column(name = "workday_date", nullable = false, unique = true)
private LocalDate date;
#NotNull
#Column(name = "working_time", nullable = false)
private Integer workingTime;
#Column(name = "booked_artist")
private String bookedArtist;
#ManyToOne
#JoinColumn(name="workday_importance_id")
private WorkDayImportance workDayImportance;
#ManyToMany
#JoinTable(name = "workday_employee",
joinColumns = {#JoinColumn(name = "workday_id",
referencedColumnName = "id_workday")},
inverseJoinColumns = {#JoinColumn(name="employee_id",
referencedColumnName = "id_employee")})
private List<Employee> employers;
#OneToMany(mappedBy = "workDay", cascade = CascadeType.REMOVE)
private List<Comment> comments;
Here is some code where I perform this operation:
public void setBookedArtist(Long workDayId, String artist){
workDayRepository
.findById(workDayId)
.ifPresent(workDay -> workDayDetailsService.saveBookedArtist(workDay, artist));
}
void saveBookedArtist(WorkDay workDay, String artist){
if(artist != null && !artist.equals("")) {
workDay.setBookedArtist(artist);
workDayRepository.save(workDay);
}
}
The entity repository is Spring Data interface which extends JpaRepository.
Best regards!
Setting the Id of workDay before saving the record should work and as we don't want to update the date set updatable = false as to below
public void setBookedArtist(Long workDayId, String artist){
workDayRepository
.findById(workDayId)
.ifPresent(workDay -> workDayDetailsService.saveBookedArtist(workDay, artist));
}
void saveBookedArtist(WorkDay workDay, String artist){
if(artist != null && !artist.equals("")) {
workDay.setId(workDay.getId());
workDay.setBookedArtist(artist);
workDayRepository.save(workDay);
}
}
#NotNull
#Column(name = "workday_date", nullable = false, unique = true, updatable = false)
private LocalDate date;

How to delete Hibernate #Embeddables containing nullable fields?

When deleting an #Embeddable object, I run into some problems.
I have the following domain classes: SwitchVoipTrunkGroup and PrioritizedCodec. The latter contains several fields that are nullable.
class SwitchVoipTrunkGroup {
//...
#CollectionOfElements(fetch = FetchType.LAZY)
#JoinTable(
name = "SWITCH_VOIP_TKG_CODEC",
joinColumns = #JoinColumn(name = "FK_SWITCH_VOIP_TKG_ID")
)
#ForeignKey(name = "FK_CODEC_SWITCH_VOIP_TKG")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Embeddable
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
When I edit SwitchVoipTrunkGroup's prioritizedCodecs field (e.g. by deleting an entry) and save the entity, I see the following in my Hibernate logging:
13:54:31,919 INFO [STDOUT] Hibernate: delete from T_SWITCH_VOIP_TKG_CODEC where
fk_switch_voip_tkg_id=? and fax_mode=? and priority=?
From this question I understand why Hibernate uses all the fields in the where clause. However, this gives problems: in case some of these fields are empty, the query will look like so:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode = ''
and priority =''
This will however not delete any records, as what is really necessary is for Hibernate to check for NULL iso for an empty string. For example:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode
IS NULL and priority IS NULL
(cf. here for more info on why checking for an empty string does not suffice)
Any ideas on how to tackle this? Many thx!
I suggest to normalize your database, so both your classes become entities, and then to setup One-to-Many relation between SwitchVoipTrunkGroup and PrioritizedCodec, then you may setup cascading rules so Hibernate automatically updates collection of elements of PrioritizedCodec type, when you persist instance of SwitchVoipTrungGroup.
#Entity
class SwitchVoipTrunkGroup {
//...
#OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(name = "switchVoipTrunkGroup_id")
#ForeignKey(name = "FK_PrioritizedCodec_SwitchVoipTrunkGroup")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Entity
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
#Serice("someService")
public class SomeService {
#Autowired
private SwitchVoipTrunkGroupDao trunkDao;
public SwitchVoipTrunkGroup doOperation("criteria") {
SwitchVoipTrunkGroup tg = trunkDao.find("criteroa");
tg.getPrioritizedCodecs().[remove(2)]; //remove should be implemened, that is just lame statement
tg.getPrioritizedCodecs().get(5).setFaxMod("ENABLED");
return trunkDao.save(tg); //hibernate will remove missing elements from PrioritizedCodec table, and will update necessary entities.
}
}
Alternatively, you may specify default values for priority and faxMode fields via attributes of #Column annotation and enforce nullable constraints
#Column(columnDefinition = "VARCHAR(20) default 'NONE'", nullable = false)
private String faxMode;

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