Spring JPA, OneToMany referencing part of primary key - java

Given A class
#Entity
#Table(name = "ATABLE")
public class A implements Serializable {
public static final String DB_ID = "AID";
public static final String DB_MARKET = "AMARKET";
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = DB_ID)),
#AttributeOverride(name = "market", column = #Column(name = DB_MARKET))
})
public AIdClass id;
#OneToMany
#JoinColumn(name = B.DB_MARKET, referencedColumnName = DB_MARKET, insertable = false, updatable = false)
public List<B> bs;
}
and B class
#Entity
#Table(name = "BTABLE")
public class B implements Serializable {
public static final String DB_ID = "BID";
public static final String DB_MARKET = "BMARKET";
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = DB_ID)),
#AttributeOverride(name = "market", column = #Column(name = DB_MARKET))
})
public BIdClass id;
}
Each entities might be listed, but using that #OneToMany relation does throw the following error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory'
Caused by: org.hibernate.AnnotationException: Unable to map collection fr.zzz.domain.A.bs
Caused by: org.hibernate.AnnotationException: referencedColumnNames(AMARKET) of fr.zzz.domain.A.bs referencing fr.zzz.domain.B not mapped to a single property
An A entity relates to multiple B on A.AMARKET = B.BMARKET

You are having this issue because there is be a possibility that composite keys (AID,AMARKET) and (BID,BMARKET) will not be unique when doing a join on keys AMARKET = BMARKET. Therefore you are getting the error not mapped to a single property. Please bear with me, use the following sample data to analyze the issue;
For table A
AID AMARKET
1 1
2 1
3 2
For table B
BID BMARKET
1 1
2 2
3 2
The above scenario is absolutely possible (at least on a database level) and just using AMARKET and BMARKET to make the join #OneToMany is not possible. What is possible though is to use #ManyToMany, this will immediately solve the issue if the table structures are correct.
But what if it is required to use #OneToMany due to some business constraint. Then you must update the table B to include A.AID and add a foreign key constraint to ensure data integrity. Then only the result set will be valid for the relationship #OneToMany. And the join will be as follows;
#OneToMany
#JoinColumn(name = B.DB_AID, referencedColumnName = DB_ID)
#JoinColumn(name = B.DB_MARKET, referencedColumnName = DB_MARKET)
public List<B> bs;
In B:
#Entity
#Table(name = "BTABLE")
public class B implements Serializable {
public static final String DB_ID = "BID";
public static final String DB_MARKET = "BMARKET";
public static final String DB_AID = "AID";
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = DB_ID)),
#AttributeOverride(name = "market", column = #Column(name = DB_MARKET))
})
public BIdClass id;
#Column(name = DB_AID)
private Long aid; // assuming aid is a Long
}
Now the join is being done on the composite primary key of A.

Related

Hibernate Join not by Primary Key

(Aware that this is not the first time question being asked, but I tried all of the suggested solutions and it did no good.)
So, I have a Parent entity and a Child entity with a OneToOne mapping - the issue is that the join isn't (and shouldn't) being done by the primary key columns, but by some other id column (a nullable natural id).
I have 4 relevant entities here:
class EntityId implements Serializable {
int storeId;
long uniqueId;
}
#Entity
#IdClass(EntityId.class)
#Immutable
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
name = "disc_type"
)
public class Parent {
#Id
#Column(name = "store_id")
private int storeId;
#Id
#Column(name = "unique_id")
private long uniqueId;
// getters and setters omitted for brevity
}
#Entity
#DiscriminatorValue(value = "Extended")
public class ExtendedParent extends Parent {
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(mappedBy="parent")
#Fetch(FetchMode.JOIN)
private ExtendedChild child;
// getters and setters omitted for brevity
}
#Immutable
#MappedSuperclass
public abstract class AbstractChild {
#Id
#Column(name = "id", nullable = false)
private long id;
// getters and setters omitted for brevity
}
#Entity
#Table(name = "extended_child")
public class ExtendedChild extends AbstractChild {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "id", insertable = false, updatable = false, nullable = false)
private ExtendedParent parent;
}
Unfortunately, whenever I try to start the application it fails with "Failed to load Application Context" with the following error:
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.hibernate.sql.ANSIJoinFragment.addJoin(ANSIJoinFragment.java:81)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.addJoins(LoadQueryJoinAndFetchProcessor.java:281)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.renderEntityJoin(LoadQueryJoinAndFetchProcessor.java:184)
The problem is that it simply ignored the referencedColumnName and attempts to join via the primary key - visible when we investigate it further (breakpoint in the exception row - this is the root cause):
The right-hand side of the join in the left join (ignore the names of the columns) includes that are defined as the #Id of the Parent, instead of the "id" column defined in the ExtendedParent entity, as defined in the #JoinColumn in the ExtendedChild entity.
NOTE: I also tried setting the id column as a natural identifier with the #NaturalId annotation (moving it into the root entity Parent + adding the annotation there) but it made no difference - same error.
Ideas?

JPA: Join particular column from another table

I want to join only one column from another table.
I have 2 entities now:
#Entity
public class Message {
....
#ManyToOne
#JoinColumn(name = "ATTRIBUTE_ID")
private Attribute attribute;
}
#Entity
#Table(name = "ATTRIBUTE_TABLE")
public class Attribute {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
}
And I want to simplify code and don't use entity for only one column:
#Entity
#SecondaryTable(name = "ATTRIBUTE_TABLE", pkJoinColumns =
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ATTRIBUTE_ID")),
public class Message {
....
#Column(table = "ATTRIBUTE_TABLE", name = "NAME")
private String attribute;
}
But #SecondaryTable JoinColumn cannot reference a non-primary key.
How to add a column from another table without using additional entity for it?

How to use JPA projections with one-to-many attributes in Criteria API / JPQL

I have difficulties creating a query with Criteria API that projects attributes of the queried entity and instantiates a DTO. One of the projected attributes maps a one-to-many relationship with another entity, so it is a set of dependent entities. I am using fetch join to retrieve the set. But I am getting the following error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
I have already tried using a regular join, but in this case the set of dependent entities won't get populated. Removing the join and / or fetch completely didn't help either.
I am using JPA specification 2.0, Hibernate 4.2.21.Final, Spring Data JPA 1.10.11.RELEASE.
Could anybody advise me on this? I would be happy for a working JPQL also.
This is my implementation of the query:
#Override
public List<EntityADto> findByPartialKey1OrderByPartialKey2(String partialKey1) {
// Create query
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<EntityADto> criteriaQuery = criteriaBuilder.createQuery(EntityADto.class);
// Define FROM clause
final Root<EntityA> root = criteriaQuery.from(EntityA.class);
root.fetch(EntityA_.oneToManyAttribute);
// Define DTO projection
criteriaQuery
.select(criteriaBuilder.construct(
EntityADto.class,
root.get(EntityA_.id).get(EntityAId_.partialKey1),
root.get(EntityA_.id).get(EntityAId_.partialKey2),
root.get(EntityA_.stringAttribute1),
root.get(EntityA_.stringAttribute2),
root.get(EntityA_.oneToManyAttribute)))
.orderBy(criteriaBuilder.asc(root.get(EntityA_.id).get(EntityAId_.partialKey2)))
.distinct(true);
// Define WHERE clause
final ParameterExpression<String> parameterPartialKey1 = criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(root.get(EntityA_.id).get(EntityAId_.partialKey1), parameterPartialKey1));
// Execute query
final TypedQuery<EntityADto> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setParameter(parameterPartialKey1, partialKey1);
return typedQuery.getResultList();
}
The entities look as follows:
#Entity
#Table(name = "TABLE_A", uniqueConstraints =
#UniqueConstraint(columnNames = {
"PARTIAL_KEY_1", "STRING_ATTR_1", "STRING_ATTR_2" }))
public class EntityA {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "partialKey1", column = #Column(name = "PARTIAL_KEY_1", nullable = false)),
#AttributeOverride(name = "partialKey2", column = #Column(name = "PARTIAL_KEY_2", nullable = false))})
private EntityAId id;
#Column(name = "STRING_ATTR_1", nullable = false)
private String stringAttribute1;
#Column(name = "STRING_ATTR_2", nullable = false)
private String stringAttribute2;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "entityA")
private Set<EntityB> entityBs;
// getters and setters omitted for brevity.
}
#Entity
#Table(name = "TABLE_2")
public class EntityB {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "partialKey3", column = #Column(name = "PARTIAL_KEY_3", nullable = false)),
#AttributeOverride(name = "partialKey1", column = #Column(name = "PARTIAL_KEY_1", nullable = false)),
#AttributeOverride(name = "partialKey2", column = #Column(name = "PARTIAL_KEY_2", nullable = false))})
private EntityBId id;
#Column(name = "VALUE")
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "PARTIAL_KEY_1", referencedColumnName = "PARTIAL_KEY_1", nullable = false, insertable = false, updatable = false),
#JoinColumn(name = "PARTIAL_KEY_2", referencedColumnName = "PARTIAL_KEY_2", nullable = false, insertable = false, updatable = false)})
private EntityA entityA;
// getters and setters omitted for brevity.
}
And finally the DTO:
public class EntityADto implements Serializable {
private static final long serialVersionUID = -5343329086697620178L;
private String partialKey1;
private Integer partialKey2;
private String stringAttribute1;
private String stringAttribute2;
private Map<String, String> additionalAttributes;
public ProzessdatStandardDto() { }
public ProzessdatStandardDto(String partialKey1,
Integer partialKey2,
String stringAttribute1,
String stringAttribute2,
Set<EntityB> entityBs) {
this.partialKey1 = partialKey1;
this.partialKey2 = partialKey2;
this.stringAttribute1 = stringAttribute1;
this.stringAttribute2 = stringAttribute2;
final Map<String, String> entityBsConverted = new HashMap<>();
if (!CollectionUtils.isEmpty(entityBs)) {
for (EntityB entityB : entityBs) {
entityBsConverted.put(entityB.getPartialKey3(), entityB.getValue());
}
}
this.additionalAttributes = prozessdatExpansionsConverted;
}
// getters and setters omitted for brevity.
}
A join gives you a collection of rows result in sql:
Parent Child
p1 c1
p1 c2
p1 c3
and so on. There is no mechanism for passing the resulting collection into a constructor.
JPA Spec 4.14
constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable
Also, another issue is that your query might return more than one parent or child.
Parent Child Child2
p1 c111 c121
p1 c121
p1 c131 c122
p2 c211 c211
p2 c221 c212
p2 c231
I'm guessing that the reason is that becomes too complicated for the underlying JPA provider to know where to split this up or which values to use to pass to a child constructor or perhaps more subtle reasons I'm not familiar with. Bottom line it requires that you provide code for parsing this matrix and if you're going to do that you might as well just parse the result without JPA.

Is there any way to audit , with hibernate-envers, an entity having an #Embedded in its EmbeddedId

I have an entity BlocRecord having a composite code BlocRecordId, and in its composite code there is an #Embedded (relation code ManyToOne) pointing to another entiy Record and want to Audit the entity BlocRecord.
The entity BlocRecord
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "blocRecord")
#Access(value = AccessType.FIELD)
#Audited
public class BlocRecord {
#EmbeddedId
private BlocRecordId blocRecordId = new BlocRecordId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "record_identifier_", referencedColumnName = "identifier_", unique = false, nullable = false),
#JoinColumn(name = "record_recordType_", referencedColumnName = "recordType_", unique = false, nullable = false)})
#MapsId("record")
private Record record;
...
}
The id class BlocRecordID
#Embeddable
public class BlocRecordId implements Serializable {
#Embedded
private RecordId record;
#Column(name = "source_")
String source ;
#Column(name = "messageType_")
String messageType ;
The entity Record
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "records")
#Access(value = AccessType.FIELD)
#Audited
public class Record {
#EmbeddedId
private RecordId recordId = new RecordId();
#OneToMany(targetEntity = BlocRecord.class, fetch = FetchType.LAZY, mappedBy = "record")
private Set<BlocRecord> blocRecord = new java.util.HashSet<>();
...
}
The idClass of the entity Record
#Embeddable
public class RecordId implements Serializable{
#Column(name = "identifier_")
String identifier ;
#Column(name = "recordType_")
String recordType ;
}
Hibernate-envers fails when trying to generate the metadata of the field record in the embeddable BlocRecordId, the The flowing exception is thrown
org.hibernate.MappingException: Type not supported: org.hibernate.type.ComponentType
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addIdProperties(IdMetadataGenerator.java:121)
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addId(IdMetadataGenerator.java:230)
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:642)
at org.hibernate.envers.configuration.internal.EntitiesConfigurator.configure(EntitiesConfigurator.java:95)
at org.hibernate.envers.boot.internal.EnversServiceImpl.doInitialize(EnversServiceImpl.java:154)
at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:118)
at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:99)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:288)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:417)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:86)
at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:179)
Do you have any idea how to resolve the issue ?
Thanks
At the moment, Envers does not support the idea of nesting an embeddable inside an embeddable when we map the identifier columns like your example illustrates. The only valid mappings that Envers presently does support is if the attribute in the embeddable is a #ManyToOne or a #Basic type.
You can work around this problem but it involves being a bit more explicit and not using RecordId. What I mean is rewrite BlocRecordId to be the following:
#Embeddable
public class BlocRecordId implements Serializable {
#Column(name = "identifier_")
String identifier;
#Column(name = "recordType_")
String recordType;
#Column(name = "source_")
String source;
#Column(name = "messageType_")
String messageType;
#Transient
private RecordId recordId;
/** Helper method to assign the values from an existing RecordId */
public void setRecordId(RecordId recordId) {
this.identifier = recordId.getIdentifier();
this.recordType = recordId.getRecordType();
}
/** Helper method to get the RecordId, caching it to avoid multiple allocations */
public RecordId getRecordId() {
if ( recordId == null ) {
this.recordId = new RecordId( identifier, recordType );
}
return this.recordId;
}
}
I agree this is less than ideal but it does at least work around the current limitation of the code. I have gone added and added HHH-13361 as an open issue to support this. You're welcomed to contribute if you wish or I'll work in getting this supported for Envers 6.0.

JPA Entity field reference OneToOne recursive

I am getting this error when I will persist() my entity. I think that the cause of the error is the relation, my idea is that FolderEntity (represents a virtual folder) can be stay inside another (only one) Then I created the reference to self (In the extended class, because all resources can be inside a folder, and folder is an resource)
org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.editor.entity.FolderEntity.id in mappedBy of com.editor.entity.FolderEntity.folderId
This my main Entity:
#MappedSuperclass
public abstract class Entity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", nullable = false)
private Integer id;
/** getter/setter **/
}
Then I extends it in my ResourceEntity Entity:
#MappedSuperclass
public class ResourceEntity extends Entity {
#Column(name = "NAME", length = Lengths.NAME40, unique = true, nullable = false)
private String name;
#Column(name = "DESCRIPTION", length = Lengths.DESCRIPTION1000, unique = false, nullable = true)
private String description;
#JoinColumn(name = "FOLDER_ID", updatable = true, nullable = false)
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "id")
private FolderEntity folderId;
/** getter/setter **/
}
Finally, I am working with this entity:
#javax.persistence.Entity
#Table(name = "EDITOR_FOLDERS")
#NamedQueries({
#NamedQuery(name = FolderEntity.ALL_FOLDERS, query = "select f from FolderEntity f"),
#NamedQuery(name = FolderEntity.FOLDER_BY_NAME, query = "select f from FolderEntity f where name = :name and resourceType = :resourceType") })
public class FolderEntity extends ResourceEntity {
public static final String ALL_FOLDERS = "findAllFolders";
public static final String FOLDER_BY_NAME = "findAllFoldersByName";
#Column(name = "RESOURCE_TYPE", length = Lengths.CODE, unique = false, nullable = false)
private Integer resourceType;
/** getter/setter **/
}
Anybodys help me to solve this? Thanks!
You should check the meaning of mappedBy: It does not reference the field that contains the ID (JPA is clever enough to find that one by itself), but it references another XToOne field that "owns" the mapping
public abstract String mappedBy
(Optional) The field that owns the relationship. This element is only specified on the inverse (non-owning) side of the association.
(from javadoc of OneToOne)
In your case you don't need the mappedBy as you are on the owning side. And you should name the attribute folder as you are referencing no ID but an entity.
Another remark: Use an enum for resourceType if you intend to define the possible values in your application as constants.

Categories