JPA Join tables Query - java

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.

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()

How to map a OneToOne with ZeroOrOneToOne back reference in JPA?

I have a legacy app which has an entity relationship that looks like this. I changed the names of the fields to less realistic values and reduced to only the relevant fields.
CREATE TABLE `billing_target` (
`billingTargetID` int(11) NOT NULL,
`targetType` char(5) NOT NULL,
`targetID` int(11) NOT NULL
PRIMARY KEY (`billingTargetID`)
);
CREATE TABLE `Client` (
`clientID` int(11) NOT NULL,
`name` varchar(200),
`color` varchar(200),
`shape` varchar(200)
PRIMARY KEY (`clientID`),
CONSTRAINT `fk_client_billingTarget`
FOREIGN KEY (`clientID`)
REFERENCES `billing_target` (`targetID`)
);
My most current attempt which causes an issue when saving as it gives a null entity key exception.
#Data
#Entity
public class BillingTarget implements Serializable {
#Id
#Column(name = "billingTargetID")
private Integer id;
#Column(name = "targetID", nullable = false)
private Integer targetID;
#Column(name = "targetType", nullable = false)
private String type;
}
#Data
#Entity
public class Client implements Serializable {
#Id
#Column(name = "clientID")
private Integer id;
#OneToOne
#JoinColumn(name = "clientID")
#MapsId("targetID")
private BillingTarget billingTarget;
private String name;
private String color;
private String shape;
}
Here's the PlantUML code if interested
#startuml
hide circle
entity BillingTarget {
* billingTargetID <<generated>>
--
* targetType
* targetID <<unique>>
}
entity Client {
* clientID <<fk>>
--
* name
color
shape
}
BillingTarget ||--o| Client : "targetID:clientID"
#enduml
I was already thinking of using a MappedSuperclass but right now it is only one type (though it could be more). Second the billing target may be zero and not null as there's a NOT NULL constraint already present.

How do I properly map entities where a primary key is composed of two foreign keys, one of which is composite itself?

I'm having a hard time trying to figure out how to properly do ORM on a certain database design.
My schema consists of three tables: a user table, a review table, and a vote table. Users can publish reviews for albums, and they can also assign a positive or negative rating to any review. Albums are provided from an external API, so their table is missing from the schema, but their IDs are referenced.
A user primary key simply consists of their username. A review primary key is composed of the reviewer's username, which is a foreign key, and the reviewed album ID. Finally, a vote primary key is composed of the voter's username, again a foreign key, and the voted review's primary key, consisting, as said earlier, of the reviewer's username and the reviewed album ID.
A user can publish a review for each individual album, and also can assign a vote for each individual review.
This is the ER model representing the schema:
To map the entities IDs, I'm using the #IdClass annotation, but I'm not sure I'm headed in the right direction. I also tried using the #EmbeddedId annotation, but the result is the same.
This is what my entities classes look like so far:
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1;
#Id #Column(name = "username")
private String username;
#Column(unique = true, nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Temporal(TemporalType.TIMESTAMP) #Column(name="signUpDate", nullable = false)
private Date signUpDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(ReviewId.class)
public class Review implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "reviewerUsername", referencedColumnName = "username")
private User reviewer;
#Id #Column(name = "reviewedAlbumId")
private Long reviewedAlbumId;
#Column(name = "content", nullable = false, length = 2500)
private String content;
#Column(name = "rating", nullable = false)
private Integer rating;
#Temporal(TemporalType.TIMESTAMP) #Column(name = "publicationDate", nullable = false)
private Date publicationDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(VoteId.class)
public class Vote implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "voterUsername", referencedColumnName = "username")
private User voter;
#Id #ManyToOne #JoinColumns({
#JoinColumn(name = "reviewerUsername", referencedColumnName = "reviewerUsername"),
#JoinColumn(name = "reviewedAlbumId", referencedColumnName = "reviewedAlbumId")
})
private Review review;
#Column(name = "vote") // #todo add attribute nullable = false
private Boolean vote;
// empty constructor, getters, setters, equals and hashCode implementations
}
These are my ID classes:
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private User reviewer;
private Long reviewedAlbumId;
// empty constructor, getters, setters, equals and hashCode implementations
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private User voter;
private Review review;
// empty constructor, getters, setters, equals and hashCode implementations
}
And here is the content of the MySQL script used to generate the schema:
DROP SCHEMA IF EXISTS albumReviewsDatabase;
CREATE SCHEMA albumReviewsDatabase;
USE albumReviewsDatabase;
CREATE TABLE user (
username VARCHAR(20) PRIMARY KEY,
email VARCHAR(254) NOT NULL UNIQUE,
password CHAR(60) NOT NULL,
signUpDate TIMESTAMP NOT NULL DEFAULT now()
) ENGINE = INNODB;
CREATE TABLE review (
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
content TEXT NOT NULL,
rating SMALLINT UNSIGNED NOT NULL,
publicationDate TIMESTAMP NOT NULL DEFAULT now(),
CHECK (rating >= 0 AND rating <= 10),
PRIMARY KEY (reviewerUsername, reviewedAlbumId),
FOREIGN KEY (reviewerUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
CREATE TABLE vote (
voterUsername VARCHAR(20) NOT NULL,
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
vote BOOLEAN NOT NULL,
PRIMARY KEY (voterUsername, reviewerUsername, reviewedAlbumId),
FOREIGN KEY (voterUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (reviewerUsername, reviewedAlbumId) REFERENCES review(reviewerUsername, reviewedAlbumId)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
I'm currently using OpenJPA as the persistence provider on a TomEE webprofile instance, and the used JPA version is 2.0.
Clearly I am misunderstating something about JPA's ORM, because when I deploy my application containing those entities I get the following exception:
<openjpa-2.4.2-r422266:1777108 fatal user error> org.apache.openjpa.util.MetaDataException: The id class specified by type "class application.model.Review" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "reviewer"
The exception is thrown because of the Review class mapping, and not the Vote class; however, I am sure that by solving the issue on the Review class, the same will reappear for Vote.
I'd prefer to get away with using the #IdClass annotation instead of #EmbeddedId, but whichever of the two I will end up using is not an issue.
These relationships are "derived identities"; so your ID classes should look like this (note the types of the foreign key fields differ from the types of their corresponding entity fields):
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private String reviewer; // matches name of #Id attribute and type of User PK
private Long reviewedAlbumId;
// ...
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private String voter; // matches name of #Id attribute and type of User PK
private ReviewId review; // matches name of #Id attribute and type of Review PK
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Also, as a side note, #IdClass is a bit Old School while #EmbeddedId is cleaner, eliminating the code duplicated across the entity and its key.

Spring boot REST CRUD - how to POST an entitiy with a one-to-one relationship?

I have a really simple domain model: An 'Alert' has one 'Type' and one 'Status'.
This is my schema:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
Now, I'm going to show the respective entity classes:
Alert.java:
// imports omitted
#Entity
#Table(name = "price_alert")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
#OneToOne
#JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
#OneToOne
#JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
#Column(nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date creationDate;
#Column(nullable = true)
#Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
#Column(nullable = true)
private byte priceReachedNotifications;
#Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
#Entity
#Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "status_name")
#NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
#Entity
#Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "type_name")
#NotBlank
private String name;
//getters and setters omitted
}
Repositories:
AlertRepository.java:
//imports omitted
#Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
#Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
#Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
Now, the main controller:
AlertController.java:
#RestController
#RequestMapping("/api")
public class AlertController {
#Autowired
AlertRepository alertRepository;
#Autowired
StatusRepository statusRepository;
#Autowired
TypeRepository typeRepository;
#GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
#PostMapping("/alert")
public Alert createAlert(#Valid #RequestBody Alert alert) {
return alertRepository.save(alert);
}
#GetMapping("/alert/{id}")
public Alert getAlertById(#PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
#PutMapping("/alert/{id}")
public Alert updateAlert(#PathVariable(value = "id") Long alertId,
#Valid #RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
#DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(#PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
So, I have two questions:
How can I create an alert, via POST, and associate existing status and type?
For example, this would be my cURL. I'm trying to indicate that I want to associate to this new alert the 'Status' and 'Type' existing objects, passing their respective IDs:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
Like the first question, how can I update an Alert, associating new existing 'Status' and 'Type' objects?
Thanks!
I think there is no out-of-the-box way to achieve this with a single POST request. The approach I see used most of the time is making an initial request to create the Alert, and subsequent requests to associate Status and Type.
You could take a look at how Spring Data Rest approaches the problem here:
https://reflectoring.io/relations-with-spring-data-rest/
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource
I'm not the biggest fan of Spring Data Rest though, since it forces some things (like hateoas) down your throat
,but you can easily implement the same approach manually.
You could argue that it's overkill to have separate calls to set the status and type of an alert, being both actually part of the alert, and I may agree actually. So if you don't mind slightly deviating from the rigidity of what people mostly call REST APIs (but are more like CRUD interfaces exposing your data model), it could make sense to take an AlertDto (with status and type ids) in your alert creation endpoint, retrieve status and type with these ids and create the Alert object you will eventually store.
Having said all of the above, I would avoid having tables for Status and Type if all they have is a name. I would have these names in the Alert itself and no relationships at all. Yes it may occupy more space on the database, but disk space is hardly a problem nowadays, and I'm guessing status and type are usually short strings.
I admit I am specially biased against this id-name lookup table pattern because we have dozens of these in one of our projects at work and they do nothing but generate a lot of useless code and complicate the DB schema.

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

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();

Categories

Resources