Spring data jpa one-to-one optional relationship - spring-data-jpa

Is it possible to map relationship to optional object?
In my case during Create Project wizard I want to save incomplete step number in separate ProjectSetupCompletion table.
Once project create wizard completed I don't need any record in that table.
#Entity
#IdClass(ProjectPk.class)
public class Project extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.TABLE, generator="nextProjectId")
#TableGenerator(
name="nextProjectId",
table="projectId",
pkColumnName = "table_name",
pkColumnValue = "project",
valueColumnName = "proj_id"
)
private Long projId;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "projId", insertable = false, updatable = false)
private ProjectSetupCompletion step;
// getters and setters omitted for brewity
}
#Entity
public class ProjectSetupCompletion {
#Id
private Long projId;
private int setupStep;
#OneToOne(mappedBy = "step")
private Project project;
// getters and setters omitted for brewity
}
During test when I retrieve Project without step I'm getting error javax.persistence.EntityNotFoundException: Unable to find com.sbsuite.sbsuitedata.domain.ProjectSetupCompletion with id 1
PS. This required by legasy design.

Related

Creating new JPA Entity with EmbeddedID

I have a very simple set of tables modelling a many to many relationship.
Foo -< FooBar >- Bar
Which is working fine when I select data but when I try to create a new instance of the relationship I can getting errors because JPA is trying to insert nulls.
I am having to set both the #ManyToOne values as well as the key (which represent the same things) and this makes me think this is not setup correctly.
Question is therefore how do I correctly setup the annotations to create a new FooBar relationship?
Entities
#Entity
#Table(name = "FOO")
public class Foo implements Serializable {
#Id
#Column(name = "FOO_ID")
private int id;
#column(name = "FOO_DESC")
private String desc;
#OneToMany(mappedBy = "foo")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
#Entity
#Table(name = "BAR")
public class Bar implements Serializable {
#Id
#Column(name = "BAR_ID")
private int id;
#column(name = "BAR_DESC")
private String desc;
#OneToMany(mappedBy = "bar")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
Intersection Table (and Key)
#Embeddable
public class FooBarKey implements Serializable {
private int fooId;
private int BarId;
//getters / setters / hashCode / equals
}
#Entity
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#EmbeddedId
private FooBarKey key;
#MapsId("fooId")
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#MapsId("barId")
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
Creating Relationship
Finally I am creating a new intersection instance:
//foo and bar exist and are populated
FooBar fb = new FooBar();
FooBarKey fbk = new FooBarKey();
fbk.setFooId(foo.getId());
fbk.setBarId(bar.getId());
fb.setKey(fbk);
fb.setDesc("Some Random Text");
entityManager.persist(fb);
at which point JPA errors with the insert saying it cannot insert null into FOO_ID.
I have checked and at the point I persist the object, the key is populated with both Foo and Bar IDs.
If I add
fb.setFoo(foo);
fb.setBar(bar);
prior to the persist it works but should the #MapsId not effectively tell JPA to map using the key?
I presume that I should be setting both the #ManyToOne and key values which are logically the same thing so I must have something not configured correctly?
I am using Eclipselink if that makes a difference.
When you use mapsId, you are telling JPA that this relationship, and the value of the target entity's primary key, is used to set the mapping named within the mapsId value. JPA then uses this relationship mapping to set the foreign key AND the basic mapping value when it flushes or commits. In your case, you left the relationship NULL, which forces the FK to be null when it gets inserted.
This allows sequencing to be delayed, as you may not have the primary key generated in the referenced entity when creating and traversing the graph - JPA will calculate and populate the values and propagate them through the graph when it needs them.
If you aren't using the ID class in your model, the simplest solution is just to remove it and avoid the overhead:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
}
IdClass has similar restrictions to what is needed for #EmbeddedId but with one more - the names within it must match the property names designated with #Id, but the types must be the same as the ID class within the referenced entity. Pretty easy if you are using basic mappings within Foo and Bar, but can be more complex.
Adding more to your composite key is easy:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Id
private int someValue
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
private int someValue
}
JPA will populate your foreign keys for you when the relationships are not-null, but any other fields require either sequencing or your own mechanisms to ensure they are populated prior to insert, and all Ids should be treated as immutable within JPA.

Spring Data JPA does findAll retrieve data from associated table in a onetoone mapping?

I am using Spring Data JPA. I have 2 tables as follows:
Investment and Investment_Type. There is a one to one relationship between Investment and Investment_Type.
My Investment class is as follows:
#Entity
public class Investment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int investmentId;
#NotNull(message = "Cannot be empty")
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "investment_type_id")
private InvestmentType investmentType;
#NotNull(message = "Cannot be empty")
private String investmentNumber;
//getter and setter methods
}
My InvestmentType class is as follows:
#Entity
public class InvestmentType {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer investmentTypeId;
private String investmentTypeName;
//getter and setter methods
}
My InvestmentRepository is as follows:
public interface InvestmentRepository extends JpaRepository <Investment, Integer>{
}
My controller has the following code:
List<Investment> investments = investmentRepo.findAll();
for(Investment investment:investments){
logger.info(" Got investment with id "+investment.getInvestmentId());
if(investment.getInvestmentType() != null){
logger.info("Investment Type is "+investment.getInvestmentType().getInvestmentTypeName());
}
else{
logger.info("null investment type ");
}
}
However, the for loop always outputs "null investment type"
So it appears that the findAll method does not retrieve the data from the InvestmentType table to which there is a OneToOne mapping.
Am I doing something wrong? Do I need to do something explicit in order to also retrieve data from the associated table with the mapping?
one-to-one association is the only one that can not be proxied.Add fetch type lazy and use mappedby on related table colum.
Parent table
#NotNull(message = "Cannot be empty")
#OneToOne(fetch = FetchType.LAZY, optional = false)
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "investment_type_id")
private InvestmentType investmentType;
Related table
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#OneToOne(mappedBy = "investment")
private Integer investmentTypeId;

JPA: How to fill related entity after the master is inserted

I have two tables called SL_DOCUMENT and SL_PROPOSE. The SL_DOCUMENT has its own ID (ID_DOCUMENT) and a foreign key to SL_PROPOSE (ID_PROPOSE). The SL_PROPOSE ID column is ID_PROPOSE. The particularity is that SL_PROPOSE ID value is actually the SL_DOCUMENT.ID_DOCUMENT value. i.e., after a new SL_DOCUMENT is inserted, the related SL_PROPOSE should be inserted with the SL_DOCUMENT.ID_DOCUMENT as ID and later the same value should be used in SL_DOCUMENT.ID_PROPOSE column.
I did my JPA mapping as follows:
#Entity
#Table(name = "SL_DOCUMENT")
public class DocumentORM {
#Id
#Column(name = "ID_DOCUMENT")
#SequenceGenerator(name = "SEQ_SL_DOCUMENT", sequenceName = "SEQ_SL_DOCUMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SL_DOCUMENT")
private Long id;
#OneToOne(mappedBy = "document", cascade = { CascadeType.PERSIST })
// #JoinColumn(name = "ID_PROPOSE", updatable = false)
private ProposeORM propose;
// ...
}
#Entity
#Table(name = "SL_PROPOSE")
public class ProposeORM {
#Id
#Column(name = "ID_PROPOSE")
private Long id;
#MapsId
#OneToOne
#JoinColumn(name="ID_PROPOSE")
private DocumentORM document;
// ...
public ProposeORM(DocumentORM document) {
super();
this.document = document;
this.document.setPropositura(this);
}
}
To create the new instances of of DocumentORM and ProposeORM:
DocumentORM document = new DocumentORM();
ProposeORM propose = new ProposeORM(document);
And finally to insert the new Document with ProposeORM:
this.documentoDAO.insert(document);
When I really insert a document, according the snippets above, I see in the console (Websphere 8.5) the INSERT commands for the SL_DOCUMENT, SL_PROPOSE running correctly. However, when I see the tables, the column SL_DOCUMENT.ID_PROPOSE is still NULL. Even If I uncomment the #JoinColumn annotation over DocumentORM.propose, the SL_DOCUMENT.ID_PROPOSE column continues to be not filled.
The ideal would be if SL_DOCUMENT had a discriminator column and ProposeORM was a DocumentORM subclass, using the JOINED InheritanceType (there are other tables with the same kind of relationship with SL_DOCUMENT). However, these are legacy tables and it is not possible to change it.
So, what is the alternative to fill SL_DOCUMENT.ID_PROPOSE? A workaround I was thinking is fill this column using a native SQL. Do you have better ideas?
Thanks,
Rafael Afonso
The solution I see is to make ProposeORM's ID not auto-generated, since you always want it to have the ID of the document it's linked to, AND still have a join column in the document table:
#Entity
#Table(name = "SL_DOCUMENT")
public class DocumentORM {
#Id
#Column(name = "ID_DOCUMENT")
#SequenceGenerator(name = "SEQ_SL_DOCUMENT", sequenceName = "SEQ_SL_DOCUMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SL_DOCUMENT")
private Long id;
#OneToOne
#JoinColumn(name = "ID_PROPOSE")
private ProposeORM propose;
// ...
}
#Entity
#Table(name = "SL_PROPOSE")
public class ProposeORM {
#Id
#Column(name = "ID_PROPOSE")
private Long id;
#OneToOne(mappedBy = propose)
private DocumentORM document;
// ...
public ProposeORM(DocumentORM document) {
super();
this.id = document.getId();
this.document = document;
this.document.setPropositura(this);
}
}
You'll have to persist the document first, flush the EntityManager to make sure the document has a generated ID, and then persist the propose and set it into the document.

using #Embedabble with a foreign key and manyToMany relation

I wrote an example for the code i am trying to implement, i get an error with Constraint "Student_Teacher_FK" already exists.
the #embiddable class has a foreign key that is created twice with current code.
#Entity
public class Teacher {
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Entity
public class Student{
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Embeddable
public class StudentList implements Serializable {
#ManyToMany
#JoinTable(name = "Student_Teacher",
joinColumns =
#JoinColumn(name = "Student_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "Teacher_ID", referencedColumnName = "ID")
)
#ForeignKey(name = "Student_Teacher_FK", inverseName = "Teacher_Student_FK")
public List<Student> studentList = new ArrayList<Student>();
}
#Entity
public class HistoryTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class LangTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class RetiredTeacher extends Teacher {
// has no students
}
#embeddable : Defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity (http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html)
As you are declaring it in 2 different entity, jpa will create associated association table (student-teacher) 2 times with associated fk, which is explicitely named, and so created 2 times too with the same name. Here is your error.
I don't think using #embeddable is appropriated for what you're intending to do. A student has is own existence and is not part of teacher itself (not an uml composition / black diamond) so it's not an embeddable entity. Student list should be held by teacher entity using a simple manyToMany association.

Why am I getting "Duplicate entry" errors for related objects upon merge in eclipselink?

I have an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?