Hibernate One to Many relationship with attributes on the relationship - java

Basic Hibernate question.
I have a class called Song and a class called Artwork, both exist independently. Then an instance of Song can contain multiple Artworks and when they do there are attribute particular to that relationship so I have created another class called CoverArt that links between the two. I'm using annotations for the hibernate stuff and having problems.
If I annotate all three classes as #Entity when I build the database I get the error >'org.hibernate.MappingException: Could not determine type for: Artwork, at table: CoverArt, for columns: [org.hibernate.mapping.Column(artwork)]'
If I change CoverArt to #Embeddable, as it only exists in the context of a Song I get the error
'org.hibernate.annotations.common.AssertionFailure: Declaring class is not found in the inheritance state hierarchy: com.jthink.songlayer.CoverArt'
I can't work out what these messages are saying, what I have wrong. Here is the relevant code from the three classes
Song:
#Entity
public class Song
{
#Id
#GeneratedValue
private Integer recNo;
#ElementCollection(fetch=FetchType.EAGER)
#IndexColumn(name = "POSITION")
private List<CoverArt> coverArt;
.....
CoverArt:
#Embeddable
public class CoverArt
{
private String imageType;
private String description;
private Artwork artwork;
#Id
#GeneratedValue
private Integer id;
public CoverArt()
{
}
public String getImageType()
{
return imageType;
}
public void setImageType(String imageType)
{
this.imageType = imageType;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public Artwork getArtwork()
{
return artwork;
}
public void setArtwork(Artwork artwork)
{
this.artwork = artwork;
}
}
Artwork:
#Entity
public class Artwork
{
public Artwork()
{
}
public Artwork(byte[] imageData)
{
this.imageData=imageData;
}
#Id
#GeneratedValue
private Integer id;
#Lob
private byte[] imageData;
private String mimeType;
private int width;
private int height;
public byte[] getImageData()
{
return imageData;
}
public void setImageData(byte[] imageData)
{
this.imageData = imageData;
}
public String getMimeType()
{
return mimeType;
}
public void setMimeType(String mimeType)
{
this.mimeType = mimeType;
}
public int getWidth()
{
return width;
}
public void setWidth(int width)
{
this.width = width;
}
public int getHeight()
{
return height;
}
public void setHeight(int height)
{
this.height = height;
}
}

The CoverArt class should be an entity.
The Song has a list of CoverArt instances, you should thus have
#OneToMany
#JoinColumn(...)
private List<CoverArt> coverArts; // note the final s, since it's plural
Each CoverArt links to an Artwork, so you should also have an association. It's not clear if it's a ManyToOne or a OneToOne, tough. I'll suppose it's a OneToOne:
#OneToOne
#JoinColumn(...)
private Artwork artwork;
It's pretty simple. Each time an entity has a reference to another entity, or a collection of another entity instances, you have an assosiation. And an association can be a OneToMany, OneToOne, ManyToOne or ManyToMany. You have to tell Hibernate which one it is. If you don't tell it, it assumes it's a simple Column which is wrong.

First of all You should tell us how do You want all that to look like in database.
I assume You want something like that:
table 'songs'
table 'artworks'
table 'cover_arts' with fkeys: song_id and artwork_id
So a Song "has many" CoverArts and each CoverArt "has one" Artwork.
If this is correct, then:
Annotate CoverArt with #Entity instead of #Embeddable
inside CoverArt class annotate field 'artwork' with #ManyToOne
replace #ElementCollection on field 'coverArt' inside Song class with #OneToMany. It would be nice to rename field 'coverArt' to 'coverArts' as it is a collection, not a single instance.

Related

Java Spring Neo4jRepository unable to read a property of abstract type

I have just started experimenting with Neo4J/Java and expect this is an easy one I'm missing, and probably phrasing my queries wrong.
I have some model classes as follows:
#Node
public class Garment {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = "DESIGNED_BY")
private Entity designer;
// Other properties getters/setters removed for readibility
public Entity getDesigner() {
return designer;
}
public void setDesigner(Entity designer) {
this.designer = designer;
}
}
public abstract class Entity {
#Id
#GeneratedValue
Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Node
public class Person extends Entity {
}
#Node
public class Company extends Entity {
}
And a corresponding repository
public interface Garment extends Neo4jRepository<Garment, Long> {
Garment findByName(String name);
}
I have no problem inserting, using repository.save(), this correctly adds everything; nodes, relationships. Fine. It gives Designers of type Person labels of Person, and Designers of type Company the label Company.
However, when I do a find, e.g. findByName(), findAll(). it is not matching the designer and just saying designer is null, according to the cipher being executed/logged it looks like it's trying to build a relationship there with nodes with an Entity label, which there are none.
How can I get my repository to return Garments with designers of Person and Companys. I expect this is going to be as simple as an annotation, in order to fix.
(Note I've tried adding a #Node on the entity type with Person and Company as labels, however it just results in every node being added as both a Person and a Company).

Vaadin Table - AddNestedContainerProperty

public class LocationBasedRole extends AbstractEntity{
#ManyToMany(fetch=FetchType.LAZY)
private Set<Role> roles=new HashSet<Role>();
#ManyToMany(fetch=FetchType.LAZY)
private Set<Location> locations=new HashSet<Location>();
}
public class Role extends AbstractEntity{
private String name;
}
public class Location extends AbstractEntity{
private String location;
}
I have an entity named locationBasedRole which has 2 properties named roles and locations. Both roles and locations have a #ManyToMany relation with locationBasedRole.
Now I want to have one property of each in a Vaadin Table. It should be something like this,
public class UserForm extends OgsAbstractForm<User>{
MTable<LocationBasedRole> locationBasedRoleTable = new MTable<LocationBasedRole>().withHeight("100%").withWidth("100%");
#Override
protected Component createContent() {
Set<LocationBasedRole> lbRoles=new HashSet<LocationBasedRole>();
roles.addAll(locationBasedRoleFasade.findAll());
BeanItemContainer<LocationBasedRole> bean=new BeanItemContainer<LocationBasedRole>(LocationBasedRole.class);
//It returns an error on the next both lines and I know the reason, but don't know how to solve it.
// If it was no ManyToMany relation and the properties weren't a collection, it would work
bean.addNestedContainerProperty("roles.name");
bean.addNestedContainerProperty("locations.location");
bean.removeContainerProperty("persistent");
bean.removeContainerProperty("id");
bean.addAll(lbRoles);
locationBasedRoleTable.setContainerDataSource(bean);
return new VerticalLayout(locationBasedRoleTable);
}
}
When I remove the properties from the NestedContainerProperties it shows me at least something in the table.
bean.addNestedContainerProperty("roles");
bean.addNestedContainerProperty("locations");
I could use any help!
Thanks in advance!
So if I understand your question right, you want to have the Collections of your BeanItemContainer-Entity displayed in one column each?
I see two possibilities for that.
Option 1 - use a wrapper class for your Sets and use addNestedContainerBean
One possibility would be to not use Sets inside your LocationBasedRole but to use a wrapper class that extends HashSet.
Then you could use the addNestedContainerBean method.
I created a small example with the BeanItemContainer-Entity Team
public class Team {
private String teamName;
private Members teamMembers;
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Members getTeamMembers() {
return teamMembers;
}
public void setTeamMembers(Members teamMembers) {
this.teamMembers = teamMembers;
}
}
Which consists of a name and teamMembers. The latter is of type Members:
public class Members extends HashSet<TeamMember> {
public String getMembers() {
return this.stream()
.map(member -> member.getFirstName() + " " + member.getLastName())
.collect(Collectors.joining(","));
}
}
Which is a simple wrapper for the Set that contains instances of TeamMember:
public class TeamMember {
private String firstName;
private String lastName;
private Integer age;
// getters and setters
}
As you can see in the Members class, there is a method getMembers which returns a String, containing a comma separated list of the team members names.
If we now use addNestedContainerBean("teamMembers") Vaadin tries to display all properties contained in the class Members. Vaadin will think getMembers is a getter for a String property called members and so generate a column for it.
Vaadin will also display a column "empty" because it will find the isEmpty method of Set and think empty is a property to display in a column. So we tell Vaadin to remove that column.
The final code of my example looks like:
protected Component createContent() {
Set<Team> teams=new HashSet<>();
for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
Team team = createTeam();
addMembersToTeam(5, team);
teams.add(team);
}
BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);
bean.addNestedContainerBean("teamMembers");
bean.removeContainerProperty("teamMembers.empty");
bean.addAll(teams);
teamTable.setContainerDataSource(bean);
return new VerticalLayout(teamTable);
}
The result looks like:
Option 2 - create fake getters and use addNestedContainerProperty
The only thing you have to do for this is extend your BeanItemContainer-Entity (LocationBasedRole) and create a fake getter for each Set you want to be displayed in a column. In your example those two fake getters could be public String getTheRoles() and public String getTheLocations(). Then you can use bean.addNestedContainerProperty("theRoles") and bean.addNestedContainerProperty("theLocations").
In my example my TeamMember class (the counterpart to your Role / Location classes) would still look like in the option above:
public class TeamMember {
private String firstName;
private String lastName;
private Integer age;
// getters and setters
}
And my Team class (your LocationBasedRole) would look like:
public class Team {
private String teamName;
private Set<TeamMember> teamMembers;
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Set<TeamMember> getTeamMembers() {
return teamMembers;
}
public void setTeamMembers(Set<TeamMember> teamMembers) {
this.teamMembers = teamMembers;
}
public String getMembers() {
if (teamMembers != null) {
return teamMembers.stream()
.map(member -> member.getFirstName() + " " + member.getLastName())
.collect(Collectors.joining(","));
} else {
return "No members";
}
}
}
Now you can tell vaadin to add the (not existing) property "members" and Vaadin will find the getter getMembers and use this for generating the column. We also have to tell vaadin not to display the original "teamMembers" property. So the final code is:
protected Component createContent() {
Set<Team> teams=new HashSet<>();
for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
Team team = createTeam();
addMembersToTeam(5, team);
teams.add(team);
}
BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);
bean.addNestedContainerProperty("members");
bean.removeContainerProperty("teamMembers");
bean.addAll(teams);
teamTable.setContainerDataSource(bean);
return new VerticalLayout(teamTable);
}
and the result looks like:

How to design database model and use save method of JpaRepository to save entity if an entity has List<Items> in it?

JAVA SPRING :I am exploring JPA and am not sure of optimized way to design db and using save() of repository to save entity data right away into DB. Specifically, I have a basic class viz. Movie -
package com.kurshit.moviesmgr.vo;
import java.util.List;
public class Movie {
long movieId;
String title;
String yearOfRelease;
List<String> genere;
public Movie(long movieId, String title, String yearOfRelease, List<String> genere) {
super();
this.movieId = movieId;
this.title = title;
this.yearOfRelease = yearOfRelease;
this.genere = genere;
}
public Movie() {
// TODO Auto-generated constructor stub
}
public long getMovieId() {
return movieId;
}
public void setMovieId(long movieId) {
this.movieId = movieId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getYearOfRelease() {
return yearOfRelease;
}
public void setYearOfRelease(String yearOfRelease) {
this.yearOfRelease = yearOfRelease;
}
public List<String> getGenere() {
return genere;
}
public void setGenere(List<String> genere) {
this.genere = genere;
}
}
Each movie has list of Genere - List- it falls under, like - Action, Comedy, etc.
I am trying to create an interface that extends JpaRepository and use the inbuilt save method to save the Movie Data into DB.
I am not sure about how I should design my DB - As in, Questions like -
1. Shall I create two different tables for Movie and Genere wherein Movie table references to Genere ?
2. Shall I create just onw table and store all Genere's list as a single CSV in one column ?
3. Can I use repository's save() right away to save and map this data into respective tables.
Would really appreciate if someone can share any sources or sample code to refer or can offer any help.
Thanks much!
First of all, you should search look up #Entity annotation so that you can tell your ORM to create the necesary table for that entity.
Secondly, you need to ask yourself, how this application will work. It would be best in my opinion to create a genre entity as well, linked to Movie through a #ManyToMany relationship.
Try looking over the simple entity example here
https://spring.io/guides/gs/accessing-data-jpa/
First variant - with 'genre' as enum (if your genre is a fixed list):
#Data // it's Lombok annotation: https://projectlombok.org/features/Data
#NoArgsConstructor
#Entity
public class Movie implements Serializable {
#Id #GeneratedValue
private Integer id;
private String title;
private Integer yearOfRelease;
#ElementCollection
#Enumerated(EnumType.STRING)
#Column(name = "genre")
private List<Genre> genres;
public Movie(String title, Integer yearOfRelease, List<Genre> genres) {
this.title = title;
this.yearOfRelease = yearOfRelease;
this.genres = genres;
}
}
public enum Genre {
ACTION, COMEDY, ...;
}
public interface MovieRepo extends JpaRepository<Movie, Integer> {
}
In this case you create your movie like this:
Movie movie = new Movie("Title", 2000, Arrays.asList(ACTION, COMEDY));
movieRepo.save(movie);
Second variant - 'genre' as independent entity:
#Data
#NoArgsConstructor
#Entity
public class Movie implements Serializable {
// the same as previous one...
#ManyToMany
private List<Genre> genres;
}
#Data
#NoArgsConstructor
#Entity
public class Genre implements Serializable {
#Id private String name;
public Genre(String name) {
this.name = name
}
}
public interface MovieRepo extends JpaRepository<Movie, Integer> {
}
public interface GenreRepo extends JpaRepository<Genre, String> {
}
In this case you first create genres:
List<Genre> genres = genreRepo.saveAll(Arrays.asList(
new Genre("Action"),
new Genre("Comedy"),
));
Then create movie:
Movie movie = new Movie("Title", 2000, genres);
movieRepo.save(movie);
More info to read: Hibernate ORM User Guide - Collections

Defining inner classes in AppEngine Datastore (Objectify)

In AppEngine I need to have an entity Diagram that contains an id, title and a variable list of elements of inner class Box, each one with id and description.
Please find below the definition. However, at time of defining the EntityProxy List getter and setter: "The type java.util.List<Box> cannot be used here".
DIAGRAM.java
#Entity
public class Diagram extends DatastoreObject {
public class Box {
private String boxId;
private String description;
public String get_id() {
return boxId;
}
public void set_id(String boxId) {
this.boxId = boxId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Indexed private String diagramId; // Primary key
#Indexed private String title;
#Embedded private List<Box> boxes;
public String get_id() {
return diagramId;
}
public void set_id(String diagramId) {
this.diagramId = diagramId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setBoxes(List<Box> boxes) {
this.boxes = boxes
}
public List<Box> getBoxes() {
return boxes;
}
}
DIAGRAMPROXY.java
[...]
List<Box> getBoxes();
void setBoxes(List<Box> boxes);
[...]
Your inner class must be static. Nonstatic inner classes have an implicit link to an instance of the outer class, which would be really confusing from the perspective of loading and saving entities to the datastore.
Confusing, you have a Collection<Box> in the Box class? Doesnt sound right.. Anyways the inner Box class must be market static or be moved to a different file. Use the #Embed (version 4.0) annotation on the Box class.
Also, assuming DatastoreObject is the base of all your entities, you can make DatastoreObject as an #Entity and all its sub classes as an #EntitySubClass (index = true). Obviously all sub entities would be be saved under the same 'kind' (DatastoreObject) in the datastore.

Hibernate annotation for base java class

I would like to put into db a class that have java.awt.geom.Point2D field. Is it possible?
Here is my code.
#Entity
#Table(name = "my_class_table")
public class MyClass {
private String aliasId;
private Point2D field;
public Point2D getField() {
return field;
}
public void setFieldPoint2D field) {
this.field = field;
}
public String getAliasId() {
return aliasId;
}
public void setAliasId(String aliasId) {
this.aliasId = aliasId;
}
}
And the reason of the exception which is thrown:
Could not determine type for: java.awt.geom.Point2D, at table: my_class_table, for columns: [org.hibernate.mapping.Column(field)]
Of course, the reason is quite obvious. My question is: how should I annotate the class to be able to use a field of Point2D class? Is it possible at all?
The simplest way is to use a java.awt.Point that extends Point2D and is a Serializable class. This way hibernate will automatically map it with SerializableType and you don't need to do anything more. The point object will be saved in its serialized form in a blob database table column.
You have also the option to define a custom hibernate type for the Point2D class. Here is a link of how to define a custom hibernate type.
You can't add annotations to existing classes.
But you can define a CompositeUserType to tell Hibernate how to map a Point2D.
Thanks guys for response. Unfortunatelly java.awt.Point class uses Integer, so it is useless in my case. The easiest way to solve it would be to use Point2D.Double which implements Serializable (but definition of UserType or CompositeUserType is more convenient if you don't want to change class definition). So, the simple solution:
#Entity
#Table(name = "my_class_table")
public class MyClass {
private String aliasId;
private Point2D.Double field;
public Point2D.Double getField() {
return field;
}
public void setField(Point2D.Double field) {
this.field = field;
}
public String getAliasId() {
return aliasId;
}
public void setAliasId(String aliasId) {
this.aliasId = aliasId;
}
}
But my final goal was to create a class with ordered list of points. If anybody is interested here is an example of the class representing line:
#Entity
public class Line {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idDb", unique = true, nullable = false)
private int id;
#ElementCollection
#CollectionTable(name="points_table", joinColumns = #JoinColumn(name="idDb"))
#IndexColumn(name = "idx")
#Column(name="point_val")
private List<Point2D.Double> points = new ArrayList<Point2D.Double>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<Point2D.Double> getPoints() {
return points;
}
public void setPoints(List<Point2D.Double> points) {
this.points = points;
}
}

Categories