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.
Related
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.
I wish to have a one to one association with a join table in unidirectional way. -
Tables :
A (A_id, D_id, A_Data)
B (A_id, C_id) // Join table just contain relation between A and C
C (C_id, C_Data)
Class A {
.
.
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "B",
joinColumns = #JoinColumn(name = "A_id", referencedColumnName = "A_id"),
inverseJoinColumns = #JoinColumn(name = "C_id", referencedColumnName = "C_id"))
private C c;
}
I am using hibernate with jpa 2.0.
Entity D is not important in the model hence ignored.
I only wish to read data ,hence insert/update/delete use cases should not be concern, but one can suggest best practice in that case also.
This setup does not work. Can some one suggest how to do it in correct way?
It gives following exception
org.hibernate.MappingException: Unable to find column with logical name: A_id in org.hibernate.mapping.Table(A) and its related supertables and secondary tables
In order to get your desired schema:
// Given the following C entity
#Entity
public class C {
#Id
#Column(name = "C_ID")
private long id;
private String C_Data;
//...
}
// A Entity should be
#Entity
public class A {
#Id
#Column(name = "A_ID")
private long id;
private String A_Data;
#OneToOne(cascade = CascadeType.ALL )
#JoinTable(name = "B", joinColumns = #JoinColumn(name = "A_id"), inverseJoinColumns = #JoinColumn(name = "C_id", unique = true))
private C c;
//...
}
I've omitted referencedColumnName, so hibernate will map it to the entity primary key.
Note also that A_id column of B table will be the primary key.
I want to use Hibernate annotations to represent a unidirectional one-to-many relationship using a join. I want an added condition on the join so it only happens when a column in the source table (the "one") is equal to a constant value. For example.
SELECT *
FROM buildings b
LEFT JOIN building_floors bf on bf.building_id = b.id AND b.type = 'OFFICE'
I want to represent the b.type = 'OFFICE' part of that query.
My question is quite similar to this one, except I have a condition on the source table. JPA/Hibernate Join On Constant Value
The Java entities look like this:
#Entity
#Table(name = "buildings")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
#Fetch(FetchMode.JOIN)
// buildings.type = 'OFFICE' ????
private Set<BuildingFloors> buildingFloors;
// getters/setters
}
#Entity
#Table(name = "building_floors")
public class BuildingFloor {
#Id
#Column(name = "building_id")
private int buildingId;
#Id
#Column(name = "floor_id")
private int floorId;
#Column(name = "description")
private String description;
// getters/setters
}
I've tried a few things where I have that placeholder comment:
#Where annotation
This doesn't work since that applies to the target entity.
#JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "'OFFICE'", referencedColumnName = "type")
})
This doesn't work because I get the following error (simplified for clarity): Syntax error in SQL statement "SELECT * FROM buildings b JOIN building_floors bf on bf.building_id = b.id AND bf.'OFFICE' = b.type"
A different #JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "buildings.type", referencedColumnName = "'OFFICE'")
})
This doesn't work because when using a unidirectional OneToMany relationship, the referencedColumnName is from the source table. So I get the error: org.hibernate.MappingException: Unable to find column with logical name: 'OFFICE' in buildings
Thanks in advance!
Why not use inheritance ? (I use it with JPA, I never use hibernate directly)
#Entity
#Inheritance
#Table(name = "buildings")
#DiscriminatorColumn(name="type")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
}
And :
#Entity
#DiscriminatorValue("OFFICE")
public class Office extends Building {
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
private Set<BuildingFloors> buildingFloors;
}
Create database View with the following select:
SELECT bf.* FROM building_floors bf JOIN buildings b on bf.building_id = b.id AND b.type = 'OFFICE'
Map it to a class OfficeBuildingFloors as an ordinary entity and then use #OneToMany for it in Building class.
Of course, you won't be able to modify such collection and to avoid any exception you can use #Immutable on OfficeBuildingFloors.
In my opinion you should create a specific query to achieve your goals, rather than put specific annotations with constant parameter. I'm not see you mention another frameworks besides Hibernate so I would give some example with Hibernate. In your Building class your unidirectional mappings look like this:
#OneToMany(fetch = FetchType.Lazy, cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "building_floors", joinColumns = #JoinColumn(name = "id"), inverseJoinColumns = #JoinColumn(name = "building_id")
private Set<BuildingFloor> buildingFloors;
Then you can fetch your data using TypedQuery like this.
TypedQuery<Customer> query = getEntityManager().createNamedQuery("select b from building b inner join fetch b.buildingFloors where b.type = 'OFFICE'", Building.class);
List<Building> result = query.getResultList();
My solutions is not Hibernate specific, actually you could perform this with simple JPA. Hope this can help you to achieve your goals.
As you want filter source table you could use #Loader annotation
#Entity
#Table(name = "buildings")
#Loader(namedQuery = "building")
#NamedNativeQuery(name="building",
query="SELECT * FROM buildings b"
+ " LEFT JOIN building_floors bf on bf.building_id = b.id"
+ " WHERE b.type = 'OFFICE' AND b.id = ?",
resultClass = Building.class)
class Building
Approach with view in DB would be better and more clearly, if it could be used inside DB also. Otherwise rename Building to something which explicitly represent filtering.
Another approaches to mention: #Filter, #FilterDef.
I have two entities that used to be linked by a one to many relation but now they are linked by a many to many relation declared as follow :
SalesTeam entity :
#Entity
#Table(name = "SALES_TEAMS")
public class SalesTeam {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "WORKFLOW_FOR_SALESTEAM", inverseJoinColumns = {
#JoinColumn(name = "WFC_ID")
})
private List<WorkFlowCode> workFlowCodes = new ArrayList<>();
}
And the WorkFlowCode entity :
#Entity
#Table(name = "WORK_FLOW_CODE")
public class WorkFlowCode {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "WORKFLOW_FOR_SALESTEAM", inverseJoinColumns = {
#JoinColumn(name = "ST_ID")
})
private List<SalesTeam> salesteam = new ArrayList<>();
}
As I said the relation use to be one SalesTeam for several workflow codes but the requirement change and now it need to be a many to many relation. So I had a relation table and remove the former SALES_TEAM_ID column from the WORK_FLOW_CODE table. The problem is that now I always get an error when I try to get the WorkFlowCode from a SalesTeam. It appears that hibernate still adds the removed column to the query thus the relation had changed and nothing is left from the former relation description.
Here is the hibernate generated query :
select workflowco0_.SALES_TEAMS_ID as SALES_TE3_13_0_, workflowco0_.WFC_ID as WFC_ID4_16_0_, workflowco1_.ID as ID1_17_1_ from WORKFLOW_FOR_SALESTEAM workflowco0_ inner join WORK_FLOW_CODE workflowco1_ on workflowco0_.WFC_ID=workflowco1_.ID where workflowco0_.SALES_TEAMS_ID=?
As you can see the former SALES_TEAM_ID from WORK_FLOW_CODE table is still there.
How can I remove it ?
Thx
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.