How to map an attribute like Map<Class, AbstractEntity> with hibernate? - java

Is there any possibility with Hibernate to do the following entity structure?
#Entity
public class Person {
#OneToMany
private Map<Class<? extends PersonRole>, PersonRole> personRoles;
public <T extends PersonRole> T getRole(Class<T> roleClass) {
return roleClass.cast(roles.get(roleClass));
}
}
#Entity
public abstract class PersonRole {
#ManyToOne
private Person person;
}
Basically Hibernate can persist this mapped entity but it is not possible to load it anymore from the database with the following exception:
Exception in thread "main" org.hibernate.HibernateException: null index column for collection: de.his.cs.sys.hibernate.Person.roles
at org.hibernate.persister.collection.AbstractCollectionPersister.readIndex(AbstractCollectionPersister.java:822)
at org.hibernate.collection.internal.PersistentMap.readFrom(PersistentMap.java:277)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1189)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:804)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:655)
at org.hibernate.loader.Loader.doQuery(Loader.java:854)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:293)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:263)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2094)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)
A workaround could be using a "simple" collection and filling the map with an interceptor, but I hope for a possibility achieving this without additional infrastructure.

it is possible implementingh a Hibernate UserType which maps the class to a string and back
#OneToMany
#MapKey(name = "className" type=#Type(type="namespace.classToNameUserType"))
private Map<Class<? extends PersonRole>, PersonRole> personRoles;
see here for an example UserType

The problem basically seems to me, that hibernate needs to rely on a persistent attribute for a map key. Therefore the solution adds a new attribute to the abstract class RersonRole:
private Class<?> className = this.getClass();
Then it is possible to refer to it in the #MapKey annotation in the class Person:
#OneToMany
#MapKey(name = "className")
private Map<Class<? extends PersonRole>, PersonRole> personRoles;
With this mapping hibernate can now fill the Map without further infrastructure.
This from my point of view mostly elegant solution has the drawback of adding a persistent attribute, which is only needed because of hibernate (If I get the root cause of the problem right).

Related

Using Java records as JPA embeddables

I want to use Java records as embeddable objects with JPA. For example I want to wrap the ID in a record to make it typesafe:
#Entity
public class DemoEntity {
#EmbeddedId
private Id id = new Id(UUID.randomUUID());
#Embeddable
public static record Id(#Basic UUID value) implements Serializable {}
}
But If I try to persist it with Hibernate 5.4.32 I get the following error:
org.hibernate.InstantiationException: No default constructor for entity: : com.example.demo.DemoEntity$Id
at org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:85) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.tuple.component.AbstractComponentTuplizer.instantiate(AbstractComponentTuplizer.java:84) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
...
So it looks like Hibernate would treat the record Id like an entity, although it is an #Embeddable.
The same happens with non-id fields and #Embedded:
#Embedded
private Thing thing = new Thing("example");
#Embeddable
public static record Thing(#Basic String value) implements Serializable {}
Is there a way to use #Embeddable records with JPA/Hibernate?
Java records with a single field can be used for custom ID types or any other value object with AttributeConverters.
In the entity class the ID type is used with #Id as usual:
#Entity
public class DemoEntity {
#Id
private Id id = new Id(UUID.randomUUID());
public static record Id(UUID value) implements Serializable {}
}
Note that the record Id doesn't have any annotation.
The converter makes it possible to use records:
#Converter(autoApply = true)
public class DemoEntityIdConverter implements AttributeConverter<DemoEntity.Id, String> {
#Override
public String convertToDatabaseColumn(DemoEntity.Id id) {
return id.value().toString();
}
#Override
public DemoEntity.Id convertToEntityAttribute(String s) {
return new DemoEntity.Id(UUID.fromString(s));
}
}
Don't forget to set autoApply = true to have this converter applied automatically (without referencing it explicitly on the respective field).
Records with more than one field could be mapped with a Hibernate UserType, but that is a bit cumbersome.
Entity or embeddable, in any case the record class wouldn't be suitable here because entities and their fields, including embeddable ones, are modifiable. The only exception would be for Id fields, but that doesn't seem like an important enough case to make this functionality for.
One of the Hibernate developers explains this here

Spring Data JPA - bidirectional relation with infinite recursion

First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.

Spring Boot findByIdIn internal list

Having trouble to create the right itnerface for my Query for this given Problem.
I have this entity:
public class TwoEntity extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/*
* ATTRIBUTE
*/
private String groupName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<OneEntity> oneList;
And the Crud Repositoy:
public interface TwoRepository extends CrudRepository<TwoEntity, Long> {
TwoEntityfindById(Long id);
TwoEntityfindByGroupName(String groupName);
List<TwoEntity> findBy????(OneEntity oe);
My Goal is to get All TwoEntities where OneEntitie is a element in the list of TwoEntity.
I´am using Spring boot and Hibernate to accomplish this. I cant delete the OneEntity Object from my Database because TwoEntity has OneEntity as ForeignKey in the List.
Is there anyway of get this to work with the given Tools from the Interface?
A List of available KeyWords can be found here: spring docs for crud
/E
I Guess I have a wrong Architecture. Currently I have a Unidirectional Relation between this Entities. I guess I have to make those entities bidirectional and delete them manuelle with oneList.setList(null).
BUT I´m not 100% sure. Open for Input.
You could use this:
List<TwoEntity> findByOneList_Id(Long oneEntityId)
But you need to extend from JpaRepository
public interface TwoRepository extends JpaRepository<TwoEntity, Long> {
The method will be translated to
twoEntity.oneList.id
Here official doc
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions

Modify map table name in hibernate

Here's my class:
#Entity (name = "Client")
public abstract class MyClient
{
private Map<String, String> _properties;
}
Hiberate map my properties object into a class named "MyClient_properties".
How can I modify it so it will be mapped to "Client_properties"?
Thanks
Interestingly I thought that is supposed to be the default. Pretty sure the default naming feature is supposed to take the #Entity#name value rather than the class name if it is supplied.
Anyway, to explicitly name the collection table you'd use (oddly enough) the JPA #CollectionTable annotation:
#CollectionTable( name="Client_properties" )
private Map<String, String> _properties;

How do I map a nested collection, Map<Key,List<Values>>, with hibernate JPA annotations?

I have a class I am not sure how to annotate properly.
My goal for Holder::data:
List should maintain order not by comparator but by the natural ordering of the elements in the array. (Which can be an ndx column if that is helpful.)
Holder will have the only reference to data, so Cascade all is probably applicable as well.
I am also open to a different design that removes the map, if that would make for a cleaner design.
#Entity
public class Holder extends DomainObject {
private Map<Enum,List<Element>> data;
}
#Entity
public class Element extends DomainObject {
private long valueId;
private int otherData;
}
#Mappedsuperclass
public class DomainObject {
// provides id
// optimistic locking
// create and update date
}
I don't think it is possible with hibernate(-core) to map any collection of collections:
Collections may contain almost any
other Hibernate type, including all
basic types, custom types, components,
and of course, references to other
entities.
(from the official doc)
Notice the almost and the omission of the collection type.
A workaround: You need to introduce a new type 'in between' the collection holder and the element. This type you can map as an entity or a component and it refers the original content of the map, in this case a list.
Something like:
#Entity
public class Holder extends DomainObject {
#OneToMany
private Map<Enum,InBetween> inBetweens;
}
#Entity
public class InBetween extends DomainObject {
#OneToMany
private List<Element> elements;
}
#Entity
public class Element extends DomainObject {
private long valueId;
private int otherData;
}
#Mappedsuperclass
public class DomainObject {
// provides id
// optimistic locking
// create and update date
}
The rest of the mapping depends on your particular situation, but is rather straightforward.
Here is a blog about collection of collections in hibernate http://blog.xebia.com/2007/10/05/mapping-multimaps-with-hibernate/
Hope it will help. It helped me.
Regards,
Anton
Please note that the referred link to the Hibernate documentation seems out of date, I found the following working: http://docs.jboss.org/hibernate/core/3.5/reference/en/html/collections.html

Categories

Resources