Hibernate map an association as a map - java

I have a MessengerData class which contains a list of resources. This my object MessengerData:
"messengerData":{
"fr":[
{
"messengerType":"ImageCategoryTitle",
"imageURL":"https://assets.pernod-ricard.com/uk/media_images/test.jpg"
}
"EN":[
{
"messengerType":"ImageCategoryTitle",
"imageURL":"https://assets.pernod-ricard.com/uk/media_images/test.jpg",
}
]
This is how I define my object MessengerData:
#Entity
public class MessengerData
{
#Basic
#Id
#GeneratedValue(generator = "notification-system-uuid")
#GenericGenerator(name = "notification-system-uuid", strategy = "uuid")
private String messengerDataId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) /* , mappedBy = "idResource" */
#JoinTable(name = HemisTablesNames.MESSENGER_RESOURCES, joinColumns = #JoinColumn(name = "idResource"),
inverseJoinColumns = #JoinColumn(name = "messengerDataId"))
private Map<String, Set<Resource>> resources;
}
But I am getting this exception: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.ubiant.hemis.type.MessengerData.resources[java.util.Set]
Could someone help me with this ?

Hibernate doesn't seem to support multimaps (that's what resources is) directly but you could provide your own custom type like described here: https://xebia.com/blog/mapping-multimaps-with-hibernate/ .
However, since your data seems to be Json anyway you could go one more step and directly map the resources as json, i.e. into a text column (or a json column if the db supports it): http://fabriziofortino.github.io/articles/hibernate-json-usertype/
We're doing something similar, which on an outline looks like this (this is a generic type, in most cases a more specific POJO will be better):
class JsonData extends HashMap<String, Object> { ... }
//JsonbUserType is a custom implementation based on code like the one linked above
class JsonDataUT extends JsonbUserType<JsonData > { ... }
Then in package-info.java of the package the user type is in we have this:
#TypeDefs ( {
#TypeDef ( name = "JsonDataUT ", typeClass = JsonDataUT.class, defaultForType = JsonData.class ),
...
})
package our.package;
And our entities then just contain this:
#Column( name = "data_column")
private JsonData data;
One advantage of this is that we don't have to bother with more complex mappings, especially if types are dynamic.
One (major) disadvantage, however, is that you can't use that property in query conditions since Hibernate wouldn't know how to filter in a json column (we're using Postgres so it would really be a jsonb typed column, hence the usertype name) and afaik there's not reasonable way to provide custom functions etc. to enable things like where data.someFlag is true in HQL.

Related

Duplicating elements in a ManyToMany relation

I have a ManyToMany relationship between Profile and ProfileExperience that is mapped as follows:
#ManyToMany
#JoinTable(name = "profile_experience_relations",
joinColumns = {
#JoinColumn(name = "profile_id")
},
inverseJoinColumns = {
#JoinColumn(name = "profile_experience_id")
})
private List<ProfileExperience> experiences;
I have added localization support inside of ProfileExperience, following this guide like so:
ProfileExperience Class
#OneToMany(mappedBy = "profileExperience", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
#MapKey(name = "localizedProfileExperiencePk.locale")
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
private Map<String, LocalizedProfileExperience> localizations = new HashMap<>();
LocalizedProfileExperience Class
#Entity
#Getter
#Setter
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class LocalizedProfileExperience {
#EmbeddedId
private LocalizedProfileExperiencePk localizedProfileExperiencePk;
#ManyToOne
#MapsId("id")
#JoinColumn(name = "profileExperienceId")
private ProfileExperience profileExperience;
private String value;
}
Composite PK Class
#Embeddable
#Getter
#Setter
public class LocalizedProfileExperiencePk implements Serializable {
private static final long serialVersionUID = 1L;
private String profileExperienceId;
private String locale;
public LocalizedProfileExperiencePk() {
}
Before adding the localization, there was no duplicate entries in the responses, however - everything retrieved is now duplicated.
I can solve the issue by using a Set, however I'm curious as to why this happened. What is the explanation? Can I solve it without using a set? Am I overlooking something incredibly simple?
The problem is that you are probably using join fetch or an entity graph to fetch nested collections. Now, when you look at the JDBC result set, you will see that there are many duplicate result set rows. If you have a profile with 2 profile experiences, and each has 3 localizations, you will see that you have 6 (2 * 3) duplicate rows. Theoretically, Hibernate could try to retain the expected object graph cardinality, but this is not so easy, especially when multiple collections are involved. Also, for certain collection mappings it would simply not be possible to do.
So the short answer to your problem is, never use a List unless duplicity matters to you. In this case, you will have an order column though, so even then it would be safe to use a list.
Implement the equal method of your data class. Hibernate need it.

How to create a HashSet from a Set?

There is a foreign key in my entity :
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "role_code")
private String code;
#Column(name = "role_lib")
private String lib;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "role_menu" , joinColumns = {#JoinColumn(name = "role_code")} , inverseJoinColumns = {#JoinColumn(name = "menu_id")} )
#JsonManagedReference
private Set<Menu> menus = new HashSet<Menu>();
// getters and setters
}
It is said in Hibernate documentation that relationship attributes should be of type Interface. Now my problem is when dealing with an instance of this class and using the getMenus() method :
#Override
#Transactional
public Set<Menu> getListMenu(String role_code) {
return ((Role)get(role_code)).getMenus();
}
I want to cast it to a HashSet , but I got castException of persistent object at runtime. So how to make it to be HashSet ?
You cannot cast if the implementations of Set is not HashSet. But you can create a object:
new HashSet(obj.getMenus);
But it's always better to use interfaces, not the implementation.
Here is a note from Hibernate doc:
Hibernate will actually replace the HashSet with an instance of
Hibernate's own implementation of Set. Be aware of the following
errors:
......
(HashSet) cat.getKittens(); // Error!
And here is why you don't actually need to cast nor to create a new object:
The persistent collections injected by Hibernate behave like HashMap,
HashSet, TreeMap, TreeSet or ArrayList, depending on the interface
type.

Automatic fill Pojo using annotation with orientdb

first point is I am useing orientdb version 2.1.7.
I try to realize a project with orientdb inlcuding jpa. As described in this doc automatic loading, saving and deleting works for simple POJOs.
But there are two points that did not work out.
I want to make a property unique. I know it works when I do it programmatically like this:
OrientVertexType vertexType = graph.createVertexType(vertexName);
vertexType.createProperty("id", OType.STRING);
vertexType.createIndex("ididx", OClass.INDEX_TYPE.UNIQUE, "id");
But is there a way to do this via annotations? The JPA annotaion (#Column(unique = true, nullable = false)) seems not to work.
I have two VertexTypes which are connected by Edges. Also I want that the collection is automatic loaded via annotation. Example (getters and setters are not listed):
The User Object:
public class MyUser implements iMyUser {
private String id;
private String name;
private Set<MyGroup> groups;
...
}
The Group Object:
public class MyGroup implements iMyGroup {
private String name;
private String id;
...
}
In JPA you can add something like #JoinTable(name = "table", joinColumns = { #JoinColumn(name = "colname") }, inverseJoinColumns = { #JoinColumn(name = "colname") }) to the groups property in MyUser and when you call the methode getGroups() you get the groups which have a relation. Is there a annotation in orientdb that supports such an behaviour?
I think this (#Adjacency) might be a solution but till now I didn't have any success in implementing it.
Also is there a list or something which annotations are supported?
Regards,
foo
The second point is possible with tinkerpop frames. #Adjacency annotation for direct relations between objects and #GremlinGroovy for queries. It is a bit strange to have the annotation at the interface, but however it works.
For the first point there might be no solution via annotation, but you can create unique index via the orientdb backend.

Dynamic JPA #JoinColumn Name

I am not very experienced with JPA and was curious if the following is possible.
Say I have a class Project as follows:
#Entity
public class Project {
#Id
private String projectCode;
private String departmentId;
/*
* Is something like this possible with JPA?
*/
if (departmentId == null) {
#JoinColumn(name = "projectCode", referencedColumnName = "assignedProject")
} else {
#JoinColumn(name = "departmentId", referencedColumnName = "id")
}
#OneToMany(targetEntity = Employee.class)
private List<Employee> contributors;
// getters/setters
}
So I would like to populate the contributors list based on the presence of departmentId.
Is this possible with JPA? Or will I have to specify two List<Employee> fields, mapped by both variables, and preform proper checks within my application logic?
Thanks for your help.
/*
* Is something like this possible with JPA?
*/
if (departmentId == null) {
#JoinColumn(name = "projectCode", referencedColumnName = "assignedProject")
} else {
#JoinColumn(name = "departmentId", referencedColumnName = "id")
}
No, this isn't possible with JPA and you'll be glad that it isn't.
You can achieve what you want by using inheritance in Java. Begin by creating an abstract entity that contains all the common fields of your table. Then you can create an entity subclass with a projectCode attribute and another entity subclass with a departmentId attribute.
At the RDBMS level, for a simple object model like the one we just built, a single table can be mapped. In the abstract entity, you would annotate as follows to achieve this:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DTYPE", discriminatorType = STRING, length = 1)
#DiscriminatorValue("?")
#Entity
public abstract class Project {
:
:
}
#DiscriminatorValue("P")
public class ProjectCodeProject extends Project {
:
:
}
Remember that RDBMS has no knowledge or notion of inheritance. Inheritance exists only on the Java side. In the database, inheritance is represented by metadata. The "Discriminator" is a special column (here named "DTYPE") that appears in your database table that informs JPA which subclass a particular column represents. In the above, the code "P" was chosen to represent database records that have a PROJECTCODE attribute rather than a DEPARTMENTID attribute.
Using a class hierarchy like this would enable you to have a table whose rows can have either a departmentId or a projectCode as an attribute (not both). Because rows of the table are all Projects, developing common logic in Java to work with the subtypes ought to be relatively straightforward.

Hibernate: enforcing unique data members

I am having an issue working with Hibernate and enforcing unique data members when inserting.
Here are my abridged Entity objects:
Workflow:
#Entity
public class Workflow {
private long wfId;
private Set<Service> services;
/** Getter/Setter for wfId */
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "workflow_services",
joinColumns = #JoinColumn(name = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "service_id"))
public Set<Service> getServices() {
return services;
}
Service:
#Entity
public class Service {
private long serviceId;
private String serviceName;
/** Getter/Setter for serviceId */
...
#Column(unique=true,nullable=false)
public String getServiceName() {
return serviceName;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "service_operations",
joinColumns = { #JoinColumn(name = "serviceId") },
inverseJoinColumns = { #JoinColumn(name = "operationId") })
public Set<Operation> getOperations() {
return operations;
}
Operation:
#Entity
public class Operation {
private long operationId;
private String operationName;
/** Getter/Setter for operationId */
#Column(unique=true,nullable=false)
public String getOperationName() {
return operationName;
}
My issue:
Although I have stated in each object what is SUPPOSED to be unique, it is not being enforced.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
Currently I am getting repeats within my Services and Operations tables.
I have tried using the annotation:
#Table( uniqueConstraints)
but have had zero luck with it.
Any help would be greatly appreciated
The unique or uniqueConstraints attributes are not used to enforce the uniqueness in the DB, but create the correct DDL if you generate it from hibernate (and for documentation too, but that's arguable).
If you declare something as unique in hibernate, you should declare it too in the DB, by adding a constraint.
Taking this to the extreme, you can create a mapping in which the PK is not unique in the DB, and hibernate will throw an exception when it tries to load one item by calling Session.load, and sudently finding that there are 2 items.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
I think you're asking Hibernate to detect duplicate objects when you add them to the Set, yes? In other words, when you put an object in the Set, you want Hibernate to go look for a persistent version of that object and use it. However, this is not the way Hibernate works. If you want it to "reuse" an object, you have to look it up yourself and then use it. Hibernate doesn't do this.
I would suggest having a helper method on a DAO-like object that takes the parent and the child object, and then does the lookup and setting for you.

Categories

Resources