How to annotate a Map where keys are entity classes, and values normal java Objects (Boolean in my case) in an entity class?
I have two #Entity classes: Voter and Poll.
In the Poll class, I want to keep a Map< Voter,Boolean > of the Voters that can vote on this Poll. Boolean marks whether a Voter has voted or not. So the Mapping is Many-to-many in polls to voters.
I have classes:
#Enity
public class Voter {
...some attributes and their getters and setters
private List<Poll> polls;
private int voterId;
#Id
public int getVoterId() {
return voterId;
}
#ManyToMany(mappedBy="voters")
public List<Poll> getPolls() {
return polls;
}
..and setter.
}
#Enity
public class Poll {
...some attributes and their getters and setters
private Map<Voter,Boolean> voters;
#ManyToMany
#JoinColumn(referencedColumnName="voterId")
public Map<Voter,Boolean> getVoters() {
return voters;
}
..and setter.
}
This fails when run and causes AnnotationException.
I have seen annotation #MapKeyJoinColumn used, and tried with it as well (instaed of #JoinColumn), and failed. I haven't found an example like this (key of map an entity, value of map just an object) anywhere yet, so I basically used the try-fail way.
So the question is: what annotations should I put where?
For a map where the values are basic types (or Embeddable types) like Boolean, you need to use the #ElementCollection annotation. JPA will create a join table for the Map.
However, you cannot reference this collection table with a #ManyToMany annotated attribute in the Voter class. For this, you can add another Collection of Voter entities in the Poll class.
You can use #OneToMany and #ManyToMany annotations if you have an entity as the value type. Unfortunately, this is not applicable in your case.
Related
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.
All my JPA entity classes implement an interface called Entity which is defined like this:
public interface Entity extends Serializable {
// some methods
}
Some of the fields of my JPA entity have #Column annotation on top of them and some don't. MyEntity class is defined like below:
#Entity
public class MyEntity implements Entity {
#Id
private Long id; // Assume that it is auto-generated using a sequence.
#Column(name="field1")
private String field1;
private SecureString field2; //SecureString is a custom class
//getters and setters
}
My delete method accepts an Entity.
#Override
public void delete(Entity baseEntity) {
em.remove(baseEntity); //em is entityManager
}
Whenever the delete method is invoked I want three things inside my delete method:
1) Fields of MyEntity that are of type SecureString
2) Column name of that particular field in DB (The field may or may not have #Column annotation)
3) The value of id field
Note that when the delete() method is invoked, we don't know for which entity it is invoked, it may be for MyEntity1, MyEntity2 etc.
I have tried doing something like below:
for (Field field : baseEntity.getClass().getFields()) {
if (SecureString.class.isAssignableFrom(field.getType())) {
// But the field doesn't have annotation #Column specified
Column column = field.getAnnotation(Column.class);
String columnName = column.name();
}
}
But this will only work if the field has #Column annotation. Also it doesn't get me other two things that I need. Any ideas?
Hibernate can use different naming strategies to map property names, which are defined implicitly (without #Column(name = "...")). To have a 'physical' names you need to dive into Hibernate internals. First, you have to wire an EntityManagerFactory to your service.
#Autowired
private EntityManagerFactory entityManagerFactory;
Second, you have to retrieve an AbstractEntityPersister for your class
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
AbstractEntityPersister persister = ((AbstractEntityPersister)sessionFactory.getClassMetadata(baseEntity.getClass()));
Third, you're almost there with your code. You just have to handle both cases - with and without #Column annotation. Try this:
for (Field field : baseEntity.getClass().getFields()) {
if (SecureString.class.isAssignableFrom(field.getType())) {
String columnName;
if (field.isAnnotationPresent(Column.class)) {
columnName = field.getAnnotation(Column.class).name();
} else {
String[] columnNames = persister.getPropertyColumnNames(field.getName());
if (columnNames.length > 0) {
columnName = columnNames[0];
}
}
}
}
Note that getPropertyColumnNames() retrieves only 'property' fields, that are not a part of primary key. To retrieve key column names, use getKeyColumnNames().
And about id field. Do you really need to have all #Id's in child classes? Maybe would better to move #Id to Entity class and mark this class with #MappedSuperclass annotation? Then you can retrieve it just with baseEntity.getId();
Suppose I have 2 JPA entities:
#Entity
public class OwnerEntity {
private List<OwnedEntity> subEntities
// ...
}
#Entity
public class OwnedEntity {
private String quasiUniqueSid;
private OwnerEntity ownerEntity
// ...
}
As you can see they have a many to one relationship: an OwnerEntity can have many OwnedEntitys.
What I want to achieve is to assign each OwnedEntity a unique sid based on its owner. So I can have for example 2 owned entities with the same quasiUniqueSid but they cannot have the same owner. Do Hibernate has some built-in functionality for this kind of problem? I can remember other ORMs (not Java related) which could do this thus my question. I'm using the latest Hibernate version (4.1.8)
It seems you need a composite key, like this:
#Entity
#Table(uniqueConstraints=#UniqueConstraint(columnNames={"quasiUniqueSid","ownerEntity_id"}), name="myUniqueConstraint")
public class OwnedEntity {
...
String quasiUniqueSid;
#ManyToOne
OwnerEntity ownerEntity;
...
}
A full documentation you can find here.
If you wanna explicitly define the column name for ownerEntity, you can use:
#JoinColumn(name="ownerEntity_id")
We use annotations for mapping the entity class with the database table by simply specifying #Entity and more like #Id, table joins and many things. I do not know how these entity variables are getting mapped with database table. Can anyone give a short description for understanding.
Thanks :)
Well the idea is to translate your objects and their connections with other objects into a relational database. These two ways of representing data (objects defined by classes and in tables in a database) are not directly compatible and that is where a so called Object Relational Mapper framework comes into play.
So a class like
class MyObject
{
private String name;
private int age;
private String password;
// Getters and setters
}
Will translate into a database table containing a column name which is of type varchar, age of type int and password of type varchar.
Annotations in Java simply add additional information (so called meta data) to your class definitions, which can be read by any other class (e.g. JavaDoc) and in the case of the Java Persistence API will be used by an ORM framework like Hibernate to read additional information you need to translate your object into the database (your database table needs a primary id and some information - like what type of a relation an object has to another - can't be automatically determined by just looking at your class definition).
Annotations are very well explained here:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/
annotations are just metadata on a class, nothing magical. You can write your own annotations. Those annotations are given retention policies of runtime (which means you have access to that metadata at runtime). When you call persist etc the persistence provider iterates through the fields (java.lang.reflect.Field) in your class and checks what annotations are present to build up your SQL statement. Try writing your own annotation and doing something with it. It won't seem very magical after that.
in your case annotation working means mapping with tablename with entity class is look like as ....
#Entity
#Table(name = "CompanyUser")
public class CompanyUserCAB implements java.io.Serializable
{
private long companyUserID;
private int companyID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "companyUserID")
public long getCompanyUserID()
{
return this.companyUserID;
}
public void setCompanyUserID(long companyUserID)
{
this.companyUserID = companyUserID;
}
#Column(name = "companyID")
public int getCompanyID()
{
return this.companyID;
}
public void setCompanyID(int companyID)
{
this.companyID = companyID;
}
}
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