JPA OnetoOne mapping returns null - java

I have the following table in the DB:
material (id_mat, name, initial_weight, cargo_number, exp_date, left_amount)
I had to add an additional table, which shows constructions that were built using the materials from material table. Here how it looks:
material_construction (mat_id, construction_number)
I then created an entity class called MatConstructionMapping for the table material_construction:
#Entity(name = "material_construction")
public class MatConstructionMapping implements Serializable {
private static final long serialVersionUID = 1739614249257235075L;
#Id
#OneToOne
#JoinColumn(name = "mat_id", referencedColumnName = "id_mat", insertable=false, updatable=false)
private Material mat;
#Column(name="construction_number")
private Integer number;
public Integer getConNumber() {
return number;
}
}
And, added following getter in the Materialentity:
#OneToOne
#JoinColumn(name = "mat_id")
public MatConstructionMapping getMaterialConstructionNumber() {
return conNumber;
}
The issue is that, when I am retrieving the conNumber for any materials, its always null, however there are the values in the DB. What am I doing wrong?

you cannot have JoinColumn at both sides, #JoinColumn should be at the owning entity which you can define in any side in one to one relation, the other side should have mappedBy attribute to indicate the reverse relation, say for example MatConstructionMapping is the owning entity, then you should edit your Material
#OneToOne(mappedBy="mat")
public MatConstructionMapping getMaterialConstructionNumber() {
return conNumber;
}

Related

hibernate #OneToMany join table not being created

I have two tables Quiz and Question which I want to associate with a #OneToMany relationship but the join table is not being created in mysql workbench database. Here are the entities :
Quiz.java
#Entity
public class Quiz {
// attributes :
private Integer idQuiz;
private String quizTopic;
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
// constructors
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "quiz_Id")
public Integer getIdQuiz() {
return idQuiz;
}
#Column(name = "quiz_topic")
public String getQuizTopic() {
return quizTopic;
}
//setters
}
Question.java
#Entity
public class Question {
// attributes
private Integer idQuestion;
private String value;
private String op1;
private String op2;
private String op3;
private String correctAnswer;
// constructors
// Getters :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "question_Id")
public Integer getIdQuestion() {
return idQuestion;
}
#Column(name = "question_value")
public String getValue() {
return value;
}
#Column(name = "question_op1")
public String getOp1() {
return op1;
}
#Column(name = "question_op2")
public String getOp2() {
return op2;
}
#Column(name = "question_op3")
public String getOp3() {
return op3;
}
#Column(name = "question_op4")
public String getCorrectAnswer() {
return correctAnswer;
}
//setters
}
no foreign keys are created
here is what I get after running
Hibernate: create table question (question_id integer not null auto_increment, question_op4 varchar(255), question_op1 varchar(255), question_op2 varchar(255), question_op3 varchar(255), question_value varchar(255), primary key (question_id)) engine=InnoDB
Hibernate: create table quiz (quiz_id integer not null auto_increment, quiz_topic varchar(255), primary key (quiz_id)) engine=InnoDB
application.properties:
spring.main.web-application-type=none
spring.application.ui.title=Quiz
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/quiz?createDatabaseIfNotExist=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=justpass
spring.jpa.show-sql=true
The annotation
#OneToMany
private ArrayList<Question> questions;
will not create a join table. It should create a foreign-key in the Question table to the Quiz table. This foreign key is not the same as the
private Integer idQuestion;
column.
For your solution it dependes wether you want to build a uni- or bidirectional #OneToMany relationship. If you want a unidirectional one-to-many relationship you have to define a #JoinColumn to tell Hibernate that it shall create a foreign key with the given name in the related table. Most of the time a unidirectional one to many relationship is the easiest and sufficient way to go.
Unidirectional
#OneToMany
#JoinColumn(name = "quiz_id")
private List<Question> questions;
By this Hibernate will create the foreign key with name "quiz_id" in the question table.
In the bidirectional case we are able to access the quiz from the question vice versa the questions from the quiz. In this case you will you will have to define the variable which shall represent the parent class in the child class. For example if the quiz shall be the parent class, you will define the annotation. #OneToMany(mappedBy = "quiz"). Additionally to this you will have to define the question to be the #ManyToOne side as well. All in all:
Bidirectional
Quiz.java:
#OneToMany(mappedBy = "quiz")
private List<Question> questions;
Question.java
#ManyToOne(fetch = FetchType.LAZY)
private Quiz quiz;
You will want to define the fetch type to be lazy due to performance reasons (I even ran into some overflow errors in the past, without the lazy method).
Keep in mind that if you are using the bidirectional mapping together with serialization libraries such as jackson, you will run into the JSON infinite recursion problem when de-/serializing from/to JSON. In this case you will want to use #JsonIdentityInfo to serialize the id only instead of the complete entity (which will lead to infinite recursion) or #JsonIgnore respectively #JsonManagedReference and #JsonBackReference to not serialize the child dependencies of an entity.
I also recommend to use the properties cascade defining the cascade type and orphanRemoval for the deletion of entities when you work with relationships, but I did not want to blow up my answer with unrelated information.
It looks suspicious that you try to mix up access strategies in the Quiz entity.
As it is stated in the documentation:
As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the #Id annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. When placed on the identifier getter, Hibernate will use property-based access.
So, instead of this:
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
put the #OneToMany and #JoinColumn(name = "quiz_Id") annotations on the appropriate getter:
#OneToMany
#JoinColumn(name = "quiz_Id")
public List<Question> getQuestions() {
return questions;
}
then try to recreate your schema by setting the spring.jpa.hibernate.ddl-auto property like this:
spring.jpa.hibernate.ddl-auto=create

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

JPA and a novice's issue with relationship-mapping

I am trying to get the following type of mapping to work
Table event has the following columns:
id (PK)
prodgroup
errandtype
table errandtype : errandtype
table prodgroup: prodgroup
I have corresponding JPA classes
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
Prodgroup prodgroup;
// what mapping should go here?
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
Ok so questions are marked as comments in the code but I'll try to be explicit anyway.
In the above example I want my Prodgroup and ErrandType fields in the MyEvent class to be set to corresponding Prodgroup and Errandtype instances
I have tried #OneToOne relationships with #joincolumns and with mappedby attribute, but I just can't get it working and I've lost all sense of logical approach. My grasp of JPA entity mapping is clearly weak.
So can anyone bring some clarity?
It should be:
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "prodgroup_id", insertable = true, updatable = true)
Prodgroup prodgroup;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "errandtype_id", insertable = true, updatable = true)
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
FetchType Eager means the object will be always loaded (would be "Lazy" by default if not specified).
CascadeType.ALL means mearge/persist/remove will be also done to linked tables.
Sebastian
Your table columns event.prodgroup and event.errandtype are foreign keys to respective tables (prodgroup, errandtype). So you need #ManyToOne association (because many events may share one prodgroup or errantype).

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

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

Wrong SQL for view object using Hibernate Annotations

I'm working on a hibernate entity mapping for a database view; when I do a criteria query against it, hibernate is generating bad SQL. Any help figuring out what the problem is with my mapping would be greatly appreciated!
I have two mapped entities which I am trying to grab from a database view; the view has no other columns, just the FK of each entity. One of these FK's can be treated as a primary key, since the view has a row for each primary entity. So my DB schema for the view looks like:
primary(primary_id, some_other_fields)
history(history_id, primary_id, some_other_fields)
view_latest_status_history(primary_id, history_id)
Note the view is used because I want to pick out only the latest history for each primary, not all mapped history records. Here is the object I am using for the view, with entity annotations:
#Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
/**
* #return Returns the primary.
*/
#Id
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "primary_id", nullable = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
Both the Primary and History objects have complete, working entity annotations.
My criteria setup:
criteria.add(Restrictions.in("primary", [collection of primary objects]));
criteria.setFetchMode("primary", FetchMode.JOIN);
criteria.setFetchMode("history", FetchMode.JOIN);
And the (wrong) generated SQL:
select this_.primary as primary78_1_, this_.primary_id as prim2_78_1_, primary2_.history_id as unique1_56_0_, ...history fields
from DB_CATALOG.dbo.view_latest_status_history this_
left outer join DB_CATALOG.dbo.history primary2_ on this_.primary_id=primary2_.primary_id
where this_.specChange in (?, ?...)
I might've mucked up a few things when editing out the specifics of our project's DB schema, but the point is the first field in the 'select' clause is wrong:
this_.primary (view_latest_status_history.primary) is not a field; the field should be called primary_id. I think this may have something to do with the #Id annotation on the primary field? Any ideas how to fix this? If I remove the #Id, I get an error telling me that the entity has no primary key.
Update:
I no longer map the view as a field using a join table notation (as suggested below). The annotations have been revised, as follows. This solution works correctly in HQL, and generates the expected schema when hbm2ddl is enabled, but I have not re-tested it using the criteria query.
#Entity
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private String id;
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#OneToOne(optional = true)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
#Id
#Column(name = "primary_id", nullable = false)
#Override
#AccessType(value = "field")
public String getId() {
return id;
}
/**
* #return Returns the primary.
*/
#PrimaryKeyJoinColumn(name = "primary_id", referencedColumnName = "unique_id")
#OneToOne(optional = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
It most certainly is due to #Id annotation - primary_id is NOT a primary key in this case. Nor can you realistically have #Id and #ManyToOne on the same property.
Let me ask you this - why are you mapping ViewLatestStatusHistoryRow as an entity to begin with? It's not like you ever going to persist it. Consider mapping your latest history entry directly (as read-only) on primary (as many-to-one) and using your view as join table.

Categories

Resources