JPA: Map a Map<Enum, Entity> - jpa

i want to Map a map in JPA, but I get a Exception:
My java-code looks like that:
Issue.java:
#ElementCollection
#CollectionTable(
name="ISSUE_EMPLOYEE",
joinColumns=#JoinColumn(name="ISSUE_ID", referencedColumnName="ID")
)
#MapKeyColumn(name="EMPLOYEEPOSITION_ID")
#MapKeyConvert("myEnumConverter")
#JoinColumn(name="EMPLOYEE_ID")
private Map<EmployeePosition, Employee> namedEmployees = new Hashtable<EmployeePosition, Employee>();
EmployeePosition is a Enum andEmployee is a Entity.
I get this Exception :
Internal Exception: java.sql.SQLException: ORA-00904: "EMPLOYEES": invalid identifier
Error Code: 904
Call: INSERT INTO ISSUE_EMPLOYEE (ISSUE_ID, EMPLOYEES, EMPLOYEEPOSITION_ID) VALUES (?, ?, ?)
bind => [27, [B#18b85d, SERVICE]
It seems to ignore the #JoinColumn annotation and tries to insert the object in the DB.
What´s wrong with my mapping/Is it possible to match Entities like this?

As far as I understand, you need #OneToMany instead of #ElementCollection when value of a Map is an entity. Something like this:
#OneToMany
#JoinTable(name = "ISSUE_EMPLOYEE",
joinColumn = #JoinColumn(name = "ISSUE_ID"),
inverseJoinColumn = #JoinColumn("EMPLOYEE_ID"))
#MapKeyColumn(name="EMPLOYEEPOSITION_ID")
private Map<EmployeePosition, Employee> namedEmployees = new Hashtable<EmployeePosition, Employee>();
EDIT: The mapping above works fine in Hibernate, but doesn't work in EclipseLink. EMPLOYEEPOSITION_ID column in ISSUE_EMPLOYEE is created, but not used in queries. It happens not only with enum keys, but also with other primitive types.
It looks like a bug in EclipseLink. I can't find in in their Bugzilla, so perhaps it would be better to report it.

Related

Spring Data JPA + JpaSpecificationExecutor + NamedEntityGraph

I have two Entities. Parent and Child.
#Entity
public class Parent {
#Id
#Column(name = "PARENT_ID")
private BigInteger id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
}
#Entity
#NamedEntityGraph(name = "Child.parentObj", attributeNodes = #NamedAttributeNodes("parentObj"))
public class Child{
//blah blah blah
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="PARENT_ID")
Parent parentObj;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name="CHILD_ID")
Set<Address> address
//blah blah blah
}
ChildRepository.java
public interface ChildRepository extends JpaRepository<T, ID>, JpaSpecificationExecutor<T>{
#EntityGraph(value="Child.parentObj")
public List<Child> findAll(Specification<Child> spec);
}
I am trying to find child entities by criteria and it should always have parent.
I am getting exception that it is trying to find parentObj in Address table.
Caused by: org.hibernate.QueryException: could not resolve property: parentObj of: com.xxx.Address [..]
I found this link and tried solution given by Joep but same error.
Spring Data JPA + JpaSpecificationExecutor + EntityGraph
what am I missing. I am not able to understand why/how i limit to look for parentObj in just Child Object as Address has no reference to Parent.
I am doing search by Address.street.
I have tried ad-hoc entity graph too. exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: could not resolve property: parentObj of: com.xxx.Address
Caused by: org.hibernate.QueryException: could not resolve property: parentObj of: com.xxx.Address
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:83)
at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:77)
at org.hibernate.persister.entity.AbstractEntityPersister.toType(AbstractEntityPersister.java:1978)
at org.hibernate.hql.internal.ast.tree.FromElementType.getPropertyType(FromElementType.java:367)
at org.hibernate.hql.internal.ast.tree.FromElement.getPropertyType(FromElement.java:500)
at org.hibernate.engine.query.spi.EntityGraphQueryHint.getFromElements(EntityGraphQueryHint.java:95)
at org.hibernate.engine.query.spi.EntityGraphQueryHint.toFromElements(EntityGraphQueryHint.java:68)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:676)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:665)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.collectionFunctionOrSubselect(HqlSqlBaseWalker.java:4905)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.comparisonExpr(HqlSqlBaseWalker.java:4606)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2104)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2029)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2029)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.whereClause(HqlSqlBaseWalker.java:796)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:597)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:301)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:249)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:278)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:206)
... 60 more
I wanted to find Child entries by criteria search and get ParentObj for each child. I wanted to do join and not individual select for Parent. I was trying to solve that by using EntityGraph but it didn't work. As #EKlavya pointed out Specification and EntityGraph don't work togather.
The way I solved was:
root.fetch("parentObj", JOIN.LEFT);
in my toPredicate method. this will get Child entity with Parent in 1 query.
You are using Child.parentObj as Entity graph name but you named the entity graph as Child.parent. Use
#EntityGraph(value="Child.parent")
Without #NamedEntityGraph
we can define an ad-hoc entity graph, without #NamedEntityGraph just use
#EntityGraph(attributePaths = {"parentObj"})
Update:
Entity graph and specification both are not working together.
There is a way to solve this. Don't do eager for parent fetch child only first then make a list of child ids from child's and get parents using in clause query using child ids. Total only 2 queries needed. If you want to solve this using 1 query use DSL to do raw query.

Why does JPA call sql update on delete?

Let´s assume these two entities:
#Entity
public class MyEntity {
#Id private String id;
#OneToMany(mappedBy = "myEntity", cascade = ALL) private Set<MyEntityPredecessor> predecessors;
}
#Entity
public class MyEntityPredecessor{
#Id private String id;
#ManyToOne(name = "entityID", nullable = false) private MyEntity myEntity;
#ManyToOne(name = "entityPre", nullable = false) private MyEntity predecessor;
}
When I try to call a delete with Spring Boot Data (JPA) with a MyEntity Instance, it will work some times (I see the select and then the delete statements in correct order), but sometimes it will try to run an update on the second entity trying to set the "entityPre" Field to null (even thoug it is set to nullable=falsE), causing the DB to send an error (null not allowed!! from DB constraint).
Strangely, this will happen at "random" calls to the delete...
I just call "myEntityRepository.getOne(id)", and then myEntityRepository.delete() with the result... There is no data difference in the DB between calls, the data structure has no null values when calling the delete method, so that should not be the reason.
Why is JPA sometimes trying to call updates on the Predecessor Table, and sometimes directly deleting the values? Am I missing something?
Add a similar ManyToOne annotated set to MyEntity which refers to the other non-nullable property, like:
#OneToMany(mappedBy = "predecessor", cascade = ALL) private Set<MyEntityPredecessor> other;
some explanation:
The issue doesn't happen randomly, but happen when you try to delete an entity which is linked to one (or more) MyEntityPredecessor via the predecessor property (which is mapped to the entityPre field)
Only the other field (entityID) is mapped back to the MyEntity object, so the deletion-cascade only happens via by that field.

Saving value in Hibernate to another table than entity table

I have two tables bo_operator and hist_bo_operator_password. In bo_operator the id column is foreign key to hist_bo_operator_password and I can have many the same operator_id in hist_bo_operator_password and only one id in bo_operator.
My entity:
#Entity
#Table(name="bo_operator")
public class Operator implements Serializable
and that's how I am getting values from hist_bo_operator_password:
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
public List<String> oldPasswords = new ArrayList<String>();
but when I'm trying to get only one value by:
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
public String oldPassword;
I'm getting error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: local.vlex.operator.model.Operator.oldPassword
and all I want to do is makeing an insert into hist_bo_operator_password by
operator.setOldPassword(oldPassword);. I think the problem is that it doesn't know which password take if there is many values for the same id.
How to achive it?
#Edit
I also tried:
#Table(name="bo_operator")
#SecondaryTable(name = "hist_bo_operator_password",pkJoinColumns=#PrimaryKeyJoinColumn(name="id_operator", referencedColumnName="id"))
I even found ORDER BY so:
#Column(name="password", table="hist_bo_operator_password")
#OrderBy("data_ins")
public String oldPassword;
but seems like there is no #Limit or something like this in JPA and I still have many values to the same id which cause error:
org.hibernate.HibernateException: Duplicate identifier in table for: [local.vlex.operator.model.Operator#1]
As you described at the first sentence you have one-to-many relationship
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
#OrderBy("data_ins")
public List<String> oldPasswords = new ArrayList<String>();
Then add necessary getter
Optional<String> getPassword() {
return oldPasswords.size() > 0
? Optional.of(oldPasswords.get(0))
: Optional.empty();
}
And setter
void setPassword(String password) { // or maybe addPassword?
oldPasswords.add(password);
}
Why don't you create entity of hist_bo_operator_password?
Then you could have List of that entities (instead of just String List) and just add another object to List and save entity Operator.

how to map a ManyToMany Map<Basic, Entity> with JPA (eclipselink)

i need to persist a map with a basic key and a entity value. this runs, if i do not try to add the same second entity twice:
#ManyToMany
#JoinTable(name="THIRD_TABLE")
#MapKeyColumn(name="INTEGER_COLUMN", table="THIRD_TABLE")
private Map<Integer, SecondEntity> secondEntities = new HashMap<>();
but if i try (year1: secondE_1, year2: secondE_1) my app throws this:
java.sql.SQLException: Violation of unique constraint SYS_PK_294:
duplicate value(s) for column(s) FIRSTENTITY_ID,SECONDENTITY_ID in statement [INSERT INTO COVERANALYSIS_EXPENSERECORDSETS (secondentity_id, firstentity_id, INTEGER_COLUMN) VALUES (?, ?, ?)]
Error Code: -104
seems like the mapkey isn't used as it should. i've tried some other annotations and endless google searches but nothing seems to work.
please help me.
It's a bug in EclipseLink that has been reported.
Bug 344448 - wrong mapping of java.util.Map .
Hibernate handles it OK.
in the end i've installed a junction-helper-object in between.
the FirstEntity inverse maps the list:
#OneToMany(mappedBy="firstEntity", cascade = CascadeType.ALL)
private List<SecondEntityJunction> secondEntities;
the SecondEntityJunction contains the parent Key, the year and the secondEntity FK:
#ManyToOne
private FirstEntity firstEntity;
private int year;
private SecondEntity secondEntity;
that's it.

Ecliplselink - #CascadeOnDelete doesn't work with #Customizer

I have two entities. "Price" class has "CalculableValue" stored as SortedMap field.
In order to support sorted map I wrote customizer. After that, it seems #CascadeOnDelete is not working. If I remove CalculableValue instance from map and then save "Price" EclipseLink only updates priceId column to NULL in calculableValues table...
I really want to keep the SortedMap. It helps to avoid lots of routine work for values access on Java level.
Also, there is no back-reference (ManyToOne) defined in the CalculableValue class, it will never be required for application logic, so, wanted to keep it just one way.
Any ideas what is the best way to resolve this issue? I actually have lots of other dependencies like this and pretty much everything is OneToMany relation with values stored in sorted map.
Price.java:
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames={"symbol", "datestring", "timestring"})
})
#Customizer(CustomDescriptorCustomizer.class)
public class Price extends CommonWithDate
{
...
#CascadeOnDelete
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#MapKeyColumn(name="key")
#JoinColumn(name = "priceId")
private Map<String, CalculatedValue> calculatedValues =
new TreeMap<String, CalculatedValue>();
...
}
public class CustomDescriptorCustomizer implements DescriptorCustomizer
{
#Override
public void customize(ClassDescriptor descriptor) throws Exception
{
DatabaseMapping jpaMapping = descriptor.getMappingByAttribute("calculatedValues");
((ContainerMapping) mapping).useMapClass(TreeMap.class, methodName);
}
}
Your customizer should have no affect on this. It could be because you are using a #JoinColumn instead of using a mappedBy which should normally be used in a #OneToMany.
You can check the mapping in your customizer using, isCascadeOnDeleteSetOnDatabase()
or set it using
mapping.setIsCascadeOnDeleteSetOnDatabase(true)