I am facing the issue reported as subject when dealing with the following example:
[other irrelevant annotations]
#IdClass(KeyDAO.class)
public class KeyDAO implements Serializable {
#Id
#NotNull
private String KEYGROUP;
#Id
#NotNull
private String KEYVAL;
[...]
#OneToMany(mappedBy = "KEY")
#ToString.Exclude
private List<OrderDateDAO> ODATES;
}
[other irrelevant annotations]
#IdClass(OrderDateDAO.class)
public class OrderDateDAO implements Serializable {
[...]
#Id
#NotNull
private String ODTTYPE;
[...]
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(value = Constants.DART_KEYGROUP_ODTTYPE, referencedColumnName = "KEYGROUP")),
#JoinColumnOrFormula(column = #JoinColumn(name = "ODTTYPE", referencedColumnName = "KEYVAL", insertable = false, updatable = false))
})
#ToString.Exclude
#OrderBy("ORDERASC ASC")
private KeyDAO KEY;
}
Basically, OrderDateDAO shall be joined with KeyDAO, and both use an #IdClass annotation. By following the tips on this site, I decided to use a JoinColumnsOrFormulas annotation, because:
the real column to join is ODTTYPE <-> KEYVAL
the KEY table shall be narrowed down by setting a specific fixed value for the KEYGROUP column
Currently, I am getting the following exception:
Caused by: org.hibernate.AnnotationException: referencedColumnNames(KEYGROUP, KEYVAL) of eu.unicredit.dtm.dtm_be.dao.oracle.OrderDateDAO.id.KEY referencing eu.unicredit.dtm.dtm_be.dao.oracle.KeyDAO not mapped to a single property
I have tried several other options, but still the error occurs.
Any help or suggestions here?
Thank you.
Regards,
A.M.
Related
I am facing an issue when trying to insert 1 record in a parent table (DART_ORDER) and 2 records in a child table connected to the parent (DART_ODATE). Those 2 tables are linked by a OneToMany-ManyToOne relation as follows:
#Getter
#Setter
#ToString
#NoArgsConstructor
#Accessors(chain = true)
#Entity
#Table(
name = "DART_ORDER",
schema = Constants.ORACLE_DB_SCHEMA
)
public class OrderDAO implements Serializable {
#NotNull
private String WHY;
#NotNull
private String WHO;
#Id
#NotNull
private String ORDERID;
private String DESCRIPTION;
#NotNull
private String STATUS;
#NotNull
private String COUNTRY;
#NotNull
private String TYPE;
#NotNull
private String REPETITION;
private String REF_REFORDERID;
#OneToMany(mappedBy = "ORDER", cascade = CascadeType.ALL)
#OrderBy("DID ASC")
#ToString.Exclude
private List<OrderDateDAO> DATES;
#OneToMany(mappedBy = "ORDER", cascade = CascadeType.ALL)
#OrderBy("DID ASC")
#ToString.Exclude
private List<OrderDocDAO> DOCS;
}
#Getter
#Setter
#ToString
#NoArgsConstructor
#Accessors(chain = true)
#Entity
#Table(
name = "DART_ODATE",
schema = Constants.ORACLE_DB_SCHEMA,
indexes = #Index(
name = "PK_DART_ODATE",
columnList = "REF_ORDERID, ODTTYPE",
unique = true
)
)
#IdClass(OrderDateDAO.class)
public class OrderDateDAO implements Serializable {
#NotNull
private String WHY;
#NotNull
private String WHO;
#Id
#NotNull
private String REF_ORDERID;
#Id
#NotNull
private String ODTTYPE;
#NotNull
private LocalDate ODATE;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "REF_ORDERID", referencedColumnName = "ORDERID", insertable = false, updatable = false)
#ToString.Exclude
private OrderDAO ORDER;
}
When I try saving a record giving a complete OrderDAO object, I get the following exception:
Caused by: java.sql.SQLException: Invalid column index
at oracle.jdbc.driver.OraclePreparedStatement.setFormOfUseInternal(OraclePreparedStatement.java:10470)
at oracle.jdbc.driver.OraclePreparedStatement.setFormOfUseInternal(OraclePreparedStatement.java:10451)
at oracle.jdbc.driver.OraclePreparedStatement.setString(OraclePreparedStatement.java:5240)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.setString(OraclePreparedStatementWrapper.java:255)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setString(HikariProxyPreparedStatement.java)
at org.hibernate.type.descriptor.sql.VarcharTypeDescriptor$1.doBind(VarcharTypeDescriptor.java:46)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:73)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271)
at org.hibernate.type.ComponentType.nullSafeSet(ComponentType.java:340)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrateId(AbstractEntityPersister.java:3121)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:3079)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3372)
... 87 more
By enabling the logs for Hibernate's SQL statements, I noticed that the insert one for the DART_ODATE table has the correct number of ? symbols as parameters, but, for no apparent reason, the log immediately below shows 7 parameters passed, instead of 6, among which 1 seems to be null.
2022-05-03T13:27:24,074 TRACE [http-nio-8080-exec-5] o.h.t.d.s.BasicBinder: binding parameter [4] as [VARCHAR] - [null]
I don't honestly know why it keeps considering 1 parameter in addition, since the only additional mapping set in the DAO is the JoinColumn one...
Besides, I already tried upgrading both my SpringBoot and Hibernate Validator versions to the latest ones, but I still got the error.
Has any of you experienced the same issue?
Thank you.
Regards,
A.M.
I finally managed to understand my mistake!
The issue was related to a wrong use of the JoinColumn annotation.
Basically, I removed the ORDER attribute within the *OrderDateDAO", and changed the parent DATES attribute as follows:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "REF_ORDERID", referencedColumnName = "ORDERID")
#ToString.Exclude
private List<OrderDateDAO> DATES;
Using a uni-directional relation, the INSERT statement worked like a charm! :-)
When trying to insert an Entity - Awith a set Another Entity B, B should get the Auto generated Id from A but its null.
Tried and failed:
#MapsId("taskPKId.storyId.id") - Same error.
#Embeddable
class StoryId {
#ManyToOne(fetch = FetchType.Lazy)
#JoinColumn(name = "STORY_ID")
Long id;
} //Incomprehensible Null pointer exception
mappedBy("story") - same error
Tried with mappedBy('story') but getting an error with repeated column and so had to map it with insertable=false and updatable=false [Hibernate doesn't recognize insertable=false for #EmbeddedId]
I am getting STORY_ID = null and therefore saveAll fails on storyRepository.saveAll(stories) where storyRepository is a Spring Data repository
#Table(name = "STORY")
#EqualsAndHashCode
class Story {
#Id
#GeneratedValue(stratergy=GenerationType.Auto)
#Column(name="STORY_ID")
Long id;
#Column(name="STORY_NAME")
String name;
//#OneToMany(cascade=ALL, mappedBy="taskPKId.storyId.id", fetch = FetchType.Lazy) // tried this as well
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task;
}
#Table(name = "TASK_XREF")
#EqualsAndHashCode
Class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID", nullable = false, insertable = false, updatable = false)
Story story;
}
#Embeddable
#EqualsAndHashCode
Class TaskPKId implements Serializable {
TaskId taskId;
TaskTypeId taskTypeId;
StoryId storyId;
}
#Embeddable
#EqualsAndHashCode
class StoryId implements Serializable {
#Column(name = "STORY_ID")
Long id;
}
Tables:
STORY [STORY_ID, STORY_NAME]
TASK_XREF [(TASK_ID(FK), TASK_TYPE_ID(FK), STORY_ID(FK)) PK,TASK_NAME]
Story gets inserted (before commit ofcourse), but fails because STORY_ID is sent as null to TASK_XREF for the next inserts
I'm not quite sure why your configuration does not work. I have a similar configuration in one of my projects that works just fine. I was able to find a solution however, by adding a #MapsId annotation to the ManyToOne in the Task class. (see can someone please explain me #MapsId in hibernate? for an explanation about MapsId) I also removed insertable=false and updatable=false. See below for the code.
I didn't get MapsId to work with the StoryId class, so i changed the type of TaskPKID.storyId from StoryId to long. The StoryId class doesn't seem to add much, so hopefully this isn't to much of a problem. If you find a solution please let me know in the comments though!
By the way, your code has a lot of problems. There's a bunch of typo's, and there is a OneToMany mapping on a property that is not a Collection (which isn't allowed) This made it more difficult for me to debug the problem. Please make sure to post better quality code in your questions next time.
Here is the Task class the way I implemented it:
#Entity
#Table(name = "TASK_XREF")
class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#MapsId("storyId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "STORY_ID")
Story story;
//getters, setters
}
And here is the TaskPKID class:
#Embeddable
class TaskPKId implements Serializable {
long taskId;
long taskTypeId;
#Column(name="STORY_ID")
long storyId;
public long getTaskId() {
return taskId;
}
public void setTaskId(long taskId) {
this.taskId = taskId;
}
public void setTaskTypeId(long taskTypeId) {
this.taskTypeId = taskTypeId;
}
}
I'm not sure what you want to achieve, but it looks like you have combined #OneToMany annotation with #OneToOne-like implementation (which can lead to unexpected behavior like this one).
Possible solutions:
If one story owns multiple tasks
// Story.java
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task; // basically Set, List or any other collection type
// Task.java
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
If one story owns only one task
// Story.java
#OneToOne(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Task task;
// Task.java
#OneToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
Further reading:
#OneToOne
#OneToMany
I am experiencing a problem with hibernate and lazy loading of objects.
basically I want to load an class which has an eagerly loaded field and not load the lazy fields of child classes
Take the following QuestionVO class
#Entity
#Table(name = "question")
public class QuestionVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5867047752936216092L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "questionText", unique = false, nullable = false, length = 4000)
#Size(min = 3, max = 4000)
#Pattern(regexp = MobileAppsRegexConstants.GENERAL_ALLOWED_CHARCHTERS, message = "Question Text Not valid.")
private String questionText;
#ManyToOne(fetch = FetchType.EAGER)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name = "MENU_STYLE_ID", nullable = true)
private MenuStyleVO menuStyle;
}
Take the following MenuStyleVO class
#Entity
#Table(name = "menu_style")
public class MenuStyleVO extends BaseDAOVO implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3697798179195096156L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "menuStyleName", unique = false, nullable = false, length = 200)
private String menuStyleName;
#Column(name = "menuTemplate", unique = false, nullable = false, length = 200)
private String menuTemplate;
#OneToOne(fetch = FetchType.LAZY, optional=false)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name="logo_id")
#JsonProperty("logo")
private ApplicationImageVO logo;
}
And this ApplicationImageVO class
#Entity
#Table(name = "application_image")
public class ApplicationImageVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9158898930601867545L;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1242x2208")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1242x2208;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1536x2048")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1536x2048;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image2048x1536")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage2048x1536;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "logo")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private MenuStyleVO menuStyleLogo;
}
If L load the QuestionVO class from the database using the following hibernate criteria code - all the lazy fields of MenuStyleVO and ApplicationImageVO are also loaded.
On complicated use cases, this results in this query getting very slow
public QuestionVO findMasterAppQuestionById(int id) {
Criteria criteria = currentSession().createCriteria(QuestionVO.class);
criteria.add(Restrictions.eq("id", id));
QuestionVO questionVO = (QuestionVO) criteria.uniqueResult();
return questionVO;
}
What I am wondering is - would it be possible to load the QuestionVO class and its eager fields and tell hibernate to ignore lazy fields from the other classes bar those that are needed?
Cheers
Damien
Last time we faced an issue like this we used a constructor on parent class, which use only the desired fields of determined query.
I can't remember in fully how constructor inside a jpql query works, but it must be something like this:
select new com.package.class(c.field1, c.field2) from com.package.class c
Remember, a constructor with same arguments must be present on the desired entity.
Pros:
- Better query perfomance;
- Can be replicated with other arguments;
Cons:
- Pretty limited, you can only use this hack on the main entity you are querying;
- Includes a constructor only for determined query, poor design;
Also, you should take a look on EnttyGraphs of JPA. Seems quite promising, but didn't work as desired in our project.
Btw, Hibernate has put us many times on performance issues, hope this hack help you, good luck!
Edit:
Why this pattern would help in performance issues?
Basically, with the example i've showed before, you are not loading everything via Hibernate, only the two fields (field1 and field2) of the main entity. Without using a constructor you shoudn't be able to do that, because your query would not result in a collection of the desired entity, but in a collection of two objects each iteration (Object[]). Using the constructor pattern you are creating instances of the desired entity, but only selecting a few fields from database, and that's why this pattern can help you, you are returning a collection of the desired entity with only a few fields.
Code from my Entity Role
#Embedded
#LazyCollection(LazyCollectionOption.FALSE)
#CollectionOfElements
#JoinTable(name = "TEST_TABLE", joinColumns = #JoinColumn(name = "ROLE_ID"))
#AttributeOverrides({
#AttributeOverride(name = "code", column = #Column(name = "TSTCODE")),
#AttributeOverride(name = "work", column = #Column(name = "TSTWRK"))
})
private List<TestID> tests;
}
TestID class
#Embeddable
#AccessType("field")
public class TestID implements Serializable
{
private String code;
private String work;
// getters, setters
}
Get exception SQLGrammarException
Caused by: java.sql.SQLException: ORA-00904: "TESTS0_"."WORK": invalid identifier
Entity manager create query that trying get access to columns CODE and WORK instead of TSTCODE and TSTWRk that are in #Column annotations.
Any ideas?
Hibernate-annotation 3.2.1.ga
persistance-api 1.0
jboss-4.2.3.GA
UPDATE:
if rename fields in TestID class to table's columns names, then all warks normally
#Embeddable
#AccessType("field")
public class TestID implements Serializable
{
private String tstcode;
private String tstwks;
Remove #CollectionOfElements and just use #Embedded. I think you're double-mapping it as it is right now. Also, JPA's #ElementCollection is recommended over Hibernate's #CollectionOfElements.
Update: I kinda missed the fact that you're mapping a collection of components. You'll want to add an #Column(name="...") to the fields in your TestID to make it map correctly in that case. Even though it's somewhat contrary to the way embedded components are supposed to work, that's the only way I know to do it.
According to the hibernate recommendations (2.2.5.3.3. Collection of basic types or embeddable objects) you should use
#ElementCollection
#CollectionTable(name="TEST_TABLE", joinColumns = #JoinColumn(name = "ROLE_ID"))
#AttributeOverrides({
#AttributeOverride(name = "code", column = #Column(name = "TSTCODE")),
#AttributeOverride(name = "work", column = #Column(name = "TSTWRK"))
})
In a legacy database, I have three tables: Users, Workgroups, and UsersWorkgroup. UsersWorkgroup stores what role a user has in a workgroup.
Here are the relevant code snippets:
#Entity
#Table(name = "users_workgroup")
public class UsersWorkgroup implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected UsersWorkgroupPK usersWorkgroupPK;
#JoinColumn(name = "idworkgroup", referencedColumnName = "idworkgroup")
#ManyToOne(optional = false)
private Workgroup workgroup;
#JoinColumn(name = "user_name", referencedColumnName = "user_name")
#ManyToOne(optional = false)
private Users users;
#Column(name = "role")
private Integer role;
#Embeddable
public class UsersWorkgroupPK implements Serializable {
#Basic(optional = false)
#Column(name = "idworkgroup", insertable=false, updatable=false)
private int idworkgroup;
#Basic(optional = false)
#Column(name = "user_name", insertable=false, updatable=false)
private String userName;
#Entity
#Table(name = "workgroup")
public class Workgroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idworkgroup")
private Integer idworkgroup;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "idworkgroup")
private Collection<UsersWorkgroup> usersWorkgroupCollection;
And of course, problem is, it doesn't work.
Currently I get this exception:
Exception Description: An incompatible
mapping has been encountered between
[class entity.Workgroup] and [class
entity.UsersWorkgroup]. This usually
occurs when the cardinality of a
mapping does not correspond with the
cardinality of its backpointer.
Which I don't understand since OneToMany should match ManyToOne... Or is it a ManyToMany relationship? If I switch to #ManyToMany, I get this:
Exception Description: The target
entity of the relationship attribute
[workgroup] on the class [class
com.ericsson.rsg.ejb.entity.UsersWorkgroup]
cannot be determined. When not using
generics, ensure the target entity is
defined on the relationship mapping.
I'm trying to understand compound keys (embedded), but all the examples I could find have only simple columns that are not foreign keys (but that's the whole point of a compound key, isn't it?). Can the UsersWorkgroup table secretly be a join table?
Should I declare the PK class as a strict POJO class? Or should I put the #JoinColumn annotations in the PK class? How do I refer to the columns within the compound key from another table? Should I initialize the PK object in the refering class constructor, or is it not necessary?
I feel stuck completely.
First of all, I think your relation is a Many To Many, as a user can be in many groups, and a group can have many users (or I would assume so).
Second, as far as I know you have to reference both id_workgroup and user_name as JoinColumns, because they are part of the PK and a unit, so both should be referenced.
Also, I see the "equals" and "hashCode" methods missing from your embedded PK, as well as the getters/setters. I believe they are mandatory.
Your mapping looks fine except for mappedBy - it should be a property name, not a column name:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "workgroup")
private Collection<UsersWorkgroup> usersWorkgroupCollection;