JPA: Is Map<Entity1,Set<Entity2>> possible? - java

I don't see an example anywhere so I am not sure this is possible. But basically, I am trying to see if I can bind a field in an entity to
Map<Skill,Set<Rating>> ratings;
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkillRating (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
Rating INT,
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID),
FOREIGN KEY (Rating) REFERENCES Rating (ID)
);
CREATE TABLE Rating (
ID BIGINT PRIMARY KEY,
score TINYINT NOT NULL,
comments VARCHAR(256)
);
Entities
#Entity
public class Skill {
#Id
private Long id;
private String name;
public Skill(String name) {
this();
this.name = name;
}
public Skill() {
this.id = Math.abs( new Random().nextLong());
}
}
#Entity
public class Worker {
#Id
private Long id;
// The open question
public Map<Skill, Set<Rating>> ratings;
}
#Entity
public class Rating {
#Id
private Long id;
private Byte score;
private String comments;
}

According to the JSR-0038 the JPA spec. When using Map, the following combination are just allowed: Basic Type, Entities and Embeddables.
Map<Basic,Basic>
Map<Basic, Embeddable>
Map<Basic, Entity>
Map<Embeddable, Basic>
Map<Embeddable,Embeddable>
Map<Embeddable,Entity>
Map<Entity, Basic>
Map<Entity,Embeddable>
Map<Entity, Entity>
I don’t think there is pretty much deal to have a possible mapping in the way that you want but that is out of the specs and most of the providers follow them, I think that mapping is not very common at all.
"worker has many skills and he may have been given many ratings on a
single skill. "
Then add to the skill class a Set<Ratings>, instead of nested directly in the map as the value of it.

It might not answer your question with the map but...
It looks like your rating table is unnecessary.
You could instead have
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkill (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
score TINYINT NOT NULL,
comments VARCHAR(256)
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID)
);
Note I moved the rating information to WorkerSkill table.
Then you can map your entities per below
#Entity
public class Skill {
#Id
private Long id;
private String name;
// Getter setters const etc
}
#Entity
public class WorkerSkill {
#Id
private Long id;
private int score;
private String comments;
#ManyToOne
private Skill skill;
#ManyToOne
private Worker worker;
// Getter setters const etc
}
#Entity
public class Worker {
#Id
private Long id;
#OneToMany
public List<WorkerSkill> workerSkills = new ArrayList<>();
// Getter setters const etc
}
Then you can access all worker's skill using worker.getWorkerSkill();

Related

Join column from another table with Foreign Key using JPA

I'm new to JPA and trying to understand if there's a way to make an Entity where one column is coming from another table that is linked by a foreign key. For example, consider the following tables:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `jobs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11),
PRIMARY KEY (`id`),
CONSTRAINT `fk_jobs_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
);
Now I want to make an Entity for the "jobs" table that will include the user.email. I know I can do something like
#Entity
#Table(name = "jobs")
public class JobEntity {
#Id
#Column(name = "id")
private Long id;
#Column(name = "user_id")
private Long userId;
#Formula("(select user.email FROM user WHERE user.id = user_id)")
private String userEmail;
But I feel there's a way I can better leverage the foreign key relationship, but I'm not sure how. I was looking into #JoinColumn but was not seeing the result I wanted since the foreign key is a different column in my Entity. Is there a better way rather than using #Forula to do this?
I don't really understand this. I'm sure #JoinColumn can accomplish the behavior you're looking for.
I was looking into #JoinColumn but was not seeing the result I wanted since the foreign key is a different column in my Entity
Example:
#Entity
#Table(name = "jobs")
public class KronosFileEntity {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumn = "id")
private User user;
}
Then you can access the email like job.getUser().getEmail()
Or add a convenience method if that helps
public String getUserEmail() {
return user.getEmail();
}
Then
job.getUserEmail()

Why this Hibernate Relation isn't working?

I try to Connect Pizzas and Ingredients in a n:m relation while all Pizzas have Ingredients as an Attribute List of Ingredients. But in the Relationstable when I create a new Pizza and try to commit there is an Error with the PizzaID in the Relationtable.
The relational Table:
CREATE TABLE `Pizza_Ingredience_Relation` (
`PizzaID` int(11) NOT NULL,
`IngredientID` int(11) NOT NULL,
`Amount` int(11) NOT NULL,
`Volume_Unit` varchar(1) NOT NULL,
PRIMARY KEY (`PizzaID`,`IngredientID`),
KEY `FKc58en2gx5a8n1swmu9tda345` (`IngredientID`),
CONSTRAINT `FK_IngredienceId` FOREIGN KEY (`IngredientID`) REFERENCES `Zutatenliste` (`ID`),
CONSTRAINT `FKc58en2gx5a8n1swmu9tda345` FOREIGN KEY (`IngredientID`) REFERENCES `Zutatenliste` (`ID`),
CONSTRAINT `FKhghfxg8raskdydyu8o8msxtfn` FOREIGN KEY (`PizzaID`) REFERENCES `Pizza` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
The Ingredient Table:
CREATE TABLE `Zutatenliste` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(20) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
The Pizza Table:
CREATE TABLE `Pizza` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(20) NOT NULL,
`PreisKlein` double NOT NULL,
`PreisMittel` double NOT NULL,
`PreisGroß` double NOT NULL,
`PreisFamilie` double NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
I have two hibernate Entitites, one is a Pizza Entitiy and one the Ingredient Entitiy:
package Model.PizzenDB.SQLConnectionClasses.MySQL;
import Model.PizzenDB.Pizza;
import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.Where;
import javax.persistence.*;
import java.util.LinkedList;
import java.util.Set;
#Entity
#Table(name = "Pizza")
public class MySQLPizzaHibernateEntity {
#Id
#Column(name = "ID")
private int id;
#Column(name = "Name")
private String name;
#Column(name = "PreisKlein")
private double smallPrice;
#Column(name = "PreisMittel")
private double middlePrice;
#Column(name = "PreisGroß")
private double bigPrice;
#Column(name = "PreisFamilie")
private double familyPrice;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "Pizza_Ingredience_Relation",
joinColumns = { #JoinColumn(name = "PizzaID", referencedColumnName = "ID") },
inverseJoinColumns = { #JoinColumn(name = "IngredientID") }
)
private Set<MySQLIngredientWithAmountHibernateEntity> ingredience;
public MySQLPizzaHibernateEntity(String name, double smallPrice, double middlePrice, double bigPrice, double familyPrice) {
this.name = name;
this.smallPrice = smallPrice;
this.middlePrice = middlePrice;
this.bigPrice = bigPrice;
this.familyPrice = familyPrice;
}
public MySQLPizzaHibernateEntity() {
}
}
#Entity
#Table(name = "Zutatenliste")
#SecondaryTable(name = "Pizza_Ingredience_Relation", pkJoinColumns = #PrimaryKeyJoinColumn(name = "IngredientID", referencedColumnName = "ID"))
public class MySQLIngredientWithAmountHibernateEntity {
#Id
#Column(name = "ID")
private int id;
#Column(name = "Name")
private String name;
#Column(table = "Pizza_Ingredience_Relation", name="Amount")
private int amount;
#Column(table = "Pizza_Ingredience_Relation", name = "Volume_Unit")
private char unit;
public MySQLIngredientWithAmountHibernateEntity(String name) {
this.name = name;
}
public MySQLIngredientWithAmountHibernateEntity() {
this("");
}
}
I get the following error message:
20:41:45 [main] [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] ERROR - Field 'PizzaID' doesn't have a default value
20:41:45 [main] [org.hibernate.internal.ExceptionMapperStandardImpl] ERROR - HHH000346: Error during managed flush [org.hibernate.exception.GenericJDBCException: could not execute statement]
I'm not sure what is wrong in detail I guess it has todo with the PizzaID Foreign Key and that it isn't set properly.
For many to many relationship, you are using middle table with extra columns and you need Embeddable key for that which would comprise of Pizza and Ingredient object (names shortened). Something like:
#Embeddable
public class PizzaIngredientPk {
private MySQLPizzaHibernateEntity pizza;
private MySQLIngredientWithAmountHibernateEntity ingredient;
#ManyToOne
public MySQLPizzaHibernateEntity getPizza() {
return pizza;
}
public void setPizza(MySQLPizzaHibernateEntity pizza) {
this.pizza = pizza;
}
#ManyToOne
public MySQLIngredientWithAmountHibernateEntity getIngredient() {
return ingredient;
}
public void setIngredientID(MySQLIngredientWithAmountHibernateEntity ingredient) {
this.ingredient = ingredient;
}
}
Then this would act as Embedded Key in MySQLIngredientWithAmountHibernateEntity as
#EmbeddedId
PizzaIngredientPk pk = new PizzaIngredientPk();
But this won't work with Secondarytable which is used for one-to-one relationship. #SecondaryTable requires mapping to be with a primary key but in this case Embedded ID would become PK. In fact, you have flaw in your design. You are trying to make one side of your many to many relationship as one-to-one.
As per JPA docs There must be only one EmbeddedId annotation and no Id annotation when the EmbeddedId annotation is used.
Try to specify to Hibernate automatically produce primary keys. Put this annotation above your Id fields and re-create your database.
#GeneratedValue(strategy = GenerationType.IDENTITY)
See: https://thoughts-on-java.org/hibernate-tips-use-auto-incremented-column-primary-key/

JPA Join tables Query

I am working on a school project, and I am having trouble with joining tables so I can display output in JSP file using JSTL. I will provide all necessary code. I know that I need to connect entities somehow, but I don't know how.
SQL:
CREATE TABLE IF NOT EXISTS `totelegram`.`contacts` (
`id` INT NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT NULL,
`last_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT NULL,
`phone_number` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `phone_number_UNIQUE` (`phone_number` ASC))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `totelegram`.`messages` (
`id_message` INT NOT NULL AUTO_INCREMENT,
`message` VARCHAR(2000) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT
NULL,
`time` VARCHAR(45) NOT NULL,
`contacts_id` INT NOT NULL,
PRIMARY KEY (`id_message`),
UNIQUE INDEX `id_message_UNIQUE` (`id_message` ASC),
INDEX `fk_messages_contacts_idx` (`contacts_id` ASC),
CONSTRAINT `fk_messages_contacts`
FOREIGN KEY (`contacts_id`)
REFERENCES `totelegram`.`contacts` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Contacts.java
#Entity(name = "contacts")
public class Contacts implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#javax.persistence.Column(name = "first_name")
private String firstName;
#javax.persistence.Column(name = "last_name")
private String lastName;
#javax.persistence.Column(name = "phone_number")
private String phoneNumber;
...getters/setters, constructor, toString...
Messages.java
#Entity(name = "messages")
public class Messages implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#javax.persistence.Column(name = "id_message")
private int id;
private String message;
private String time;
#javax.persistence.Column(name = "contacts_id")
private int contactsId;
...getters/setters, constructor, toString...
MessagesRepository.java
public interface MessagesRepository extends JpaRepository<Messages, Integer> {
//custom query which will output this
//SELECT b.message, b.time, b.contacts_id, a.first_name, a.last_name FROM messages AS b INNER JOIN contacts as A ON (b.contacts_id=a.id) ORDER BY time ASC;
public List<Messages> findAll();
}
I hope I was clear. Thanks to everybody in advance.
As far as i understand, one contact can have N messages and you cannot have a Message without the Contact, right?
Since you have relations between classes, you have to use specific annotations in jpa, for example:
in the Message Class, you should use the #ManyToOne annotation, since you have Many Messages for One Contact. The JoinColumn will input the contacts_id in the Messages Table.
#ManyToOne
#JoinColumn(name = "contacts_id")
private Contacts contact;
in the Contacts Class, you should use #OneToMany annotation, since One Contact has Many Messages. The mappedBy makes a reference in contact at the Message Class.
#OneToMany(mappedBy = "contact")
private List<Messages> messages = new ArrayList<>();
So far you made a Bidirectional reference between Contacts and Messages. Now in your service class, i would recommend you find the Messages through the Contacts, since you cannot have a message without the contact. Its a Repository principle.
Contacts con = repository.findOne(1);
con.getMessages();
btw, sorry for the bad english.

OneToMany bidirectional association example

I'm new to Hibernate and I'm trying to establish a OneToMany/ManyToOne bidirectional relationship between Person and Vehicle classes. In my example a Person can have many Vehicles and a Vehicle belongs to only one Person. I need a join table: PERSON_VEHICLE with PERSON_ID and VEHICLE_ID as columns and a PERSON_ID column in VEHICLE table. Here's my class design:
Person class:
#Entity
public class Person {
#Id
#GeneratedValue
#Column(name = "PERSON_ID")
private int id;
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="person")
private Collection<Vehicle> vehicleList = new ArrayList<>();
Vehicle class:
#Entity
public class Vehicle {
#Id
#GeneratedValue
#Column(name = "VEHICLE_ID")
private int id;
private String name;
#ManyToOne
#JoinColumn(name="PERSON_ID")
#JoinTable(name="PERSON_VEHICLE", joinColumns=#JoinColumn(name="VEHICLE_ID"),
inverseJoinColumns=#JoinColumn(name="PERSON_ID"))
private Person person;
Here are the DDLs generated by Hibernate.
create table Person (
PERSON_ID integer not null auto_increment,
name varchar(255),
primary key (PERSON_ID)
)
create table Vehicle (
VEHICLE_ID integer not null auto_increment,
name varchar(255),
primary key (VEHICLE_ID)
)
create table PERSON_VEHICLE (
PERSON_ID integer,
VEHICLE_ID integer not null,
primary key (VEHICLE_ID)
)
alter table PERSON_VEHICLE
add index FK_h3d046x5uvbo53p8ms41hwqx (PERSON_ID),
add constraint FK_h3d046x5uvbo53p8ms41hwqx
foreign key (PERSON_ID)
references Person (PERSON_ID)
alter table PERSON_VEHICLE
add index FK_mtm2mn29hel3lbpl6i526w40v (VEHICLE_ID),
add constraint FK_mtm2mn29hel3lbpl6i526w40v
foreign key (VEHICLE_ID)
references Vehicle (VEHICLE_ID)
The VEHICLE table doesn't have PERSON_ID column. Something is wrong but I cannot find what the problem is.
Join table is not required for oneToMany relationship. Only two tables Person and Vehicle is sufficient for this mapping. For detailed example, see This Example

Unique items in Hibernate collections

I have defined a collection in Hibernate like this:
...
public class Item {
...
#ElementCollection
List<Object> relatedObjects;
}
It creates a mapping table with colums item_id and object_id.
The problem is that object_id seems to be unique. In other words I can not have two different items being related to the same object. But that is what I want.
I would like the combination of item_id and object_id to be unique. How do I do that?
That's not what I'm experiencing. For the following entity:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue
private Integer id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private Gender gender;
#ElementCollection
private Set<String> nicknames = new HashSet<String>();
private String dept;
// getters, setters
}
The following tables get created:
create table Person (id integer generated by default as identity, dept varchar(255), firstName varchar(255), gender varchar(255), lastName varchar(255), primary key (id))
create table Person_nicknames (Person_id integer not null, nicknames varchar(255))
alter table Person_nicknames add constraint FK24F0D97B19ACB65E foreign key (Person_id) references Person
There is no unique constraint. But I can't say more without seeing your "Object" class (it's an embeddable class, right?).
PS: ElementCollection can't be a ManyToMany, this is more a OneToMany.

Categories

Resources