jpa #OneToMany Map<SomeEntity,String> seems impossible - java

DB tables (not quite SQL but you get the gist..)
definition (
id int, -- PK
type int,
label varchar(20) -- definition label can change over time
)
asset (
id int, -- PK
-- other asset fields...
)
property (
id int, -- PK
asset_id int, -- with FK to asset + on delete cascade
definition_id int, -- with FK to definition + on delete cascade
payload varchar(256)
)
A map of the definition id int to payload (Map<int,String>), like this below, works but that's not what I want.
[Asset class]
#ElementCollection
#CollectionTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")})
#MapKeyColumn(name = "definition_id")
#Column(name = "payload")
Map<Integer,String> properties;
Instead, I'm trying to have the following map in my Asset class:
Map<Definition,String> properties;
but can't figure what to do. The #MapKeyJoinColumn annotation is intended to pull a relation out of the a entity-type map value so I cannot complete this below:
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")},
//inexistant - inverseJoinColumns = {#JoinColumn(name = "property_id", referencedColumnName = "id")})
#MapKeyJoinColumn(name = "definition_id")
Map<Definition,String> properties;
I want a value-type map value. My 'properties' table has the definition_id so I really just need jpa/hibernate to join a table for the entity key, not the value.
Any clue?

From a commenter, this works:
[Asset class]
#ElementCollection
#CollectionTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")})
#MapKeyJoinColumn(name = "definition_id")
#Column(name = "payload")
Map<Definition,String> properties;
The non-intuitive answer was to force #MapKeyJoinColumn and the key Entity type, regardless of the #ElementCollection presence.
I have no idea if this is just a fluke with hibernate abilities, or if it is actually supported by JPA.

Related

Foreign keys in ManyToMany Java + Hibernate + MySQL

When I am trying to delete Role from the roles table with the help of Hibernate using its id it occurs an exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`ewp`.`permissions`, CONSTRAINT `FK_sq51ihfrapwdr98uufenhcocg` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`))
Class User
...
#NotNull
#ManyToMany(fetch = FetchType.EAGER, targetEntity = Role.class)
#JoinTable(name = "permissions",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Role> roles;
...
Class Role
...
#ManyToMany(fetch = FetchType.EAGER)
private List<User> users;
...
When I add to Role
#JoinTable(name = "permissions",
joinColumns = {#JoinColumn(name = "role_id")},
inverseJoinColumns = {#JoinColumn(name = "user_id")})
private List<User> users;
Roles deleted without exceptions.
But I think that it can work without mapping in Role.
You either need to determine which side of the Many-To-Many owns the relationship or make sure that you alter both sides prior to removing the associated Role objects.
As mentioned in the comments, you can imply ownership by adding a mappedBy attribute to either side of the relationship. By adding mappedBy="roles" to the Role entity, this will tell the persistence provider that the relationship is managed by the User object's roles property and thus to manipulate the relationship using its join-table mapping.

JPA : Delete object from unidirectional many-to-many relationship

I have a unidirectional relation between two entities and I want to remove property object from join table only. I tried this but it only removes the property object from template and not from database join table. How can I remove it from join table:
template.getProperties().remove(property);
Entity calss:
#Table(name = "template")
public class GridTemplate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id_grid")
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "grid_property",
joinColumns =
#JoinColumn(name = "id_grid", referencedColumnName = "id"),
inverseJoinColumns =
#JoinColumn(name = "id_property", referencedColumnName = "id"))
private List<Property> properties;
}
join table:
CREATE TABLE [dbo].[grid_property](
[id_grid] [int] NOT NULL,
[id_property] [int] NOT NULL,
CONSTRAINT [PK_grid_property] PRIMARY KEY CLUSTERED
(
[id_grid] ASC,
[id_property] ASC)

How to control JPA Column names in Map

The entity Construction
has following map:
#Column(name = "paper_FK")
#ManyToMany
#JoinTable(inverseForeignKey = #ForeignKey(name = "construction_FK"), joinColumns = #JoinColumn(name = "construction_FK", referencedColumnName = "construction_FK"), name = "ConstructionPaperTracks", inverseJoinColumns = #JoinColumn(name = "paper_FK"))
private HashMap<Integer, Paper> tracks_field = new HashMap<Integer, Paper>();
Due to my database design guidelines the table ConstructionPaperTracks
should have the columns construction_FK, position and paper_FK.
JPA works with construction_id, position and paper_id.
How can i specify the column names?
best regards
Heiko
I am not sure I understand "JPA works with construction_id, position and paper_id."
Anyway, I believe the mapping will be as below:
#Entity
public class Construction{
#Id
#Column(name = "construction_id")
//specify a generation strategy
private Long id;
#ManyToMany
#JoinTable(name = "ConstructionPaperTracks",
joinColumns = #JoinColumn(name = "construction_FK"),
inverseJoinColumns = #JoinColumn(name = "paper_FK"))
#MapKeyColumn(name = "position")
private HashMap<Integer, Paper> paper;
}
You need to specify the #MapKeyColumn, the documentation for which states that:
If the map key is for a ManyToMany entity relationship or for a
OneToMany entity relationship using a join table, the map key column
is in a join table
For the joinColumn and inverseJoinColumn the referencedColumn names will default to the primary key column of the referenced tables (construction_id, paper_id) so you don't have to specify these.

Why is #ManyToMany not working with non-primary key columns?

I have 2 entities - User and Role which have following relations: User has a manytomany relation to itself and a manytomany relation with the Role entity.
#Entity
public class UserEntity implements Serializable {
#Id
#Column(length = 12, columnDefinition = "BINARY(12)", name = "Id", unique = true)
private byte[] id;
#Column(name = "Login", unique = true, nullable = false)
private String login;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Login") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "User_User",
joinColumns = { #JoinColumn(name = "UserParent") },
inverseJoinColumns = { #JoinColumn(name = "UserChild") })
private Collection<UserEntity> children;
...
}
and Role:
public class RoleEntity implements Serializable{
#Id
#Column(name = "Id", unique = true, nullable = false)
private String id;
...
}
The strange thing about the setup of DB is that the User_User relation is based on the binary Id keys
create table if not exists User_User (
UserParent binary,
UserChild binary
);
and the user-role is based on varchars
create table if not exists KNUser_UserRole (
UserLogin varchar,
RoleId varchar,
);
Now, when it runs, the user-user relationship work well. However, when I try to access the collection returned for roles, I get a ClassCastException:
java.lang.ClassCastException: **.entity.UserEntity cannot be cast to [B
at org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractHashCode(PrimitiveByteArrayTypeDescriptor.java:41)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:201)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:205)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:114)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:79)
at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:240)
at org.hibernate.engine.internal.StatefulPersistenceContext.getCollectionOwner(StatefulPersistenceContext.java:740)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1181)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:800)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:651)
at org.hibernate.loader.Loader.doQuery(Loader.java:856)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:289)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2175)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:622)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:82)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1606)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:379)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:112)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
It looks like the UserEntity is being cast to some binary(?) thing. However, the first relation between users themselves works fine, but the one with another table is wrong.
I am using different columns of different types to join tables. Is it allowed to do it this way?
Another strange thing is that when I switch the #Id annotation to be on the login field, the roles work fine, no issue, but then of course the self-join PersistentBag key is the Login instead of Id, which breaks the relation and no results are retrieved. But the conversion from UserEntity to the "[B" is not done.
Also if I leave things as in example and change the Id type to String (and the DB to varchar) it also starts working (of course not consistently with the User_User table).
What am I doing wrong? What is the reason for getting the classcastexception in this case? Why it work when I change the byte[] to String? Please let me know if you have any ideas. I do not want to change the DB design cause this would lead to lots migration and compatibility issues for clients already using the DB.
Just a note: the #Id has to be on the Id binary field as otherwise I wouldn't be able to make a self-join (I was unable to point twice to a column not being a primary key see: Is Hibernate ManyToMany self-join possible for non-key columns? getting mappingException).
Cheers
Adam
the referred column in your join table must be unique entry, here if you put #Id on login field then it works fine,but when you change it to different other than #Id column you cant be sure about the entries will be unique.what you can do is,
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Id") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
I think it should work.

Hibernate #ManyToMany mapping with composite keys

I'm trying to map a ManyToMany relationships between 2 tables, both having composite primary keys
LSFOCTB which primary key is composed of : LSFOC_CODSOC,LSFOC_CODLSC,LSFOC_CODFOC
LSFORTB which primary key is composed of : LSFOR_CODSOC,LSFOR_CODLSC,LSFOC_CODFOR
The table in charge of the ManyToMany relationship is :
LSFCFTB, with : LSFCF_CODSOC,LSFCF_CODLSC,LSFCF_CODFOC,LSFCF_CODFOR
So, in the hibernate model mapping LSFOCTB, I tried :
#ManyToMany(targetEntity = package.LSFOCTB.class, cascade = { CascadeType.PERSIST,
CascadeType.MERGE })
#JoinTable(name = "LSFCFTB", joinColumns = {
#JoinColumn(name = "LSFCF_CODLSC", referencedColumnName = "LSFOC_CODLSC"),
#JoinColumn(name = "LSFCF_CODFOC", referencedColumnName = "LSFOC_CODFOC"),
#JoinColumn(name = "LSFCF_CODSOC", referencedColumnName = "LSFOC_CODSOC") },
inverseJoinColumns = { #JoinColumn(name = "LSFCF_CODLSC", referencedColumnName = "LSFOR_CODLSC"),
#JoinColumn(name = "LSFCF_CODFOR", referencedColumnName = "LSFOR_CODFOR"),
#JoinColumn(name = "LSFCF_CODSOC", referencedColumnName = "LSFOR_CODSOC") })
before the getter.
But it won't work...
The error, when trying to access the distant collection is :
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [beans-dao.xml]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Repeated column in mapping for collection: package.LSFOCTB.distantCollection column: LSFCF_CODLSC
Have already managed to make an hibernate mapping work for a ManyToMany relationship ?
If so, what is wrong with my mapping ?
Thank you for your help !
The problem seems to be that you are creating a join table with 6 columns and there are duplicate names for your columns. You are actually creating 2 columns with the name LSFCF_CODLSC and 2 columns named LSFCF_CODFOR and 2 columns named LSFCF_CODSOC.
I would suggest that you try this:
#JoinTable(name = "LSFCFTB", joinColumns = {
#JoinColumn(name = "LSFOC_LSFCF_CODLSC", referencedColumnName = "LSFOC_CODLSC"),
#JoinColumn(name = "LSFOC_LSFCF_CODFOC", referencedColumnName = "LSFOC_CODFOC"),
#JoinColumn(name = "LSFOC_LSFCF_CODSOC", referencedColumnName = "LSFOC_CODSOC") },
inverseJoinColumns = { #JoinColumn(name = "LSFOR_LSFCF_CODLSC", referencedColumnName = "LSFOR_CODLSC"),
#JoinColumn(name = "LSFOR_LSFCF_CODFOR", referencedColumnName = "LSFOR_CODFOR"),
#JoinColumn(name = "LSFOR_LSFCF_CODSOC", referencedColumnName = "LSFOR_CODSOC") })
or something similar (according to your naming convention) to give each column a unique name.

Categories

Resources