How to Map a Table to an Object Attribute? - spring

I have a device with many identical sensors, so I need to mapping as much Entities Attributes as needed, instead of designing a table to each sensor.
How can I do that in Spring Boot?
Like:
#Entity
#Table(name = "device")
public class Devide {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Table(name = "sensor")
private Sensor sensor1; // This is my conceptual problem
#Table(name = "sensor")
private Sensor sensor2; // This is my conceptual problem
.
.
.

As your one Device can have many sensors and one Sensor can be part of may devices, you have to create ManyToMany relation between Sensors and Devices. You can refer the following mapping
#Entity
#Table(name = "Device")
public class Device {
// ...
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "Device_Sensor",
joinColumns = { #JoinColumn(name = "Device_id") },
inverseJoinColumns = { #JoinColumn(name = "Sensor_id") }
)
Set<Sensor> Sensors = new HashSet<>();
// standard constructor/getters/setters
}
#Entity
#Table(name = "Sensor")
public class Sensor {
// ...
#ManyToMany(mappedBy = "Sensors")
private Set<Device> Devices = new HashSet<>();
// standard constructors/getters/setters
}
Device_Sensor will act as a link between other two tables.

Related

How does caching works for many to one mapping in hibernate

I have two entities station and company .
Station has many to one mapping with company. When Iam trying to fetch the list of stations based on company . It always hit the database. Below is my station entity class
#Table(name = "station")
#Setter
#Getter
#Builder
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Station {
public Station() {}
#Id
#SequenceGenerator(name = "mySeqGen", sequenceName = "station_seq",allocationSize = 1)
#GeneratedValue(generator = "mySeqGen")
#Column(name = "station_id ")
private Long id;
Double latitude;
Double longitude;
#ManyToOne(fetch = FetchType.LAZY , cascade = CascadeType.ALL)
#JoinColumn(name = "company_id" , unique =true)
#Fetch(value = FetchMode.JOIN)
Below is my code to find station list based on company
#Override
public List<StationDTO> findStations(final FindStationRequestDTO findStationRequestDTO) {
var company = companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
var stationList = stationRepository.findByCompany(company);
return stationList.stream().map( station -> stationMapper.convertToStationDto(station , new StationDTO())).collect(Collectors.toList());
}
caching is working for companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
public interface StationRepository extends JpaRepository<Station, Long> {
#QueryHints({
#QueryHint(name = HINT_CACHEABLE, value = "true")
})
List<Station> findByCompany(final Optional<Company> company);
}
adding QueryHints made it working

Entity not mapped to a single property error with inherited entites of one table

I have two entities SuperAlbumEntity and AlbumEntity reflecting the same table "albums".
SuperAlbumEntity:
#Entity
#Table(name = "albums")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class SuperAlbumEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//other fields
}
AlbumEntity:
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "albums")
public class AlbumEntity extends SuperEntity{
//some fields
#Column(name = "country")
private String country;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}
AlbumEntity has #OneToMany mapping to CountryEntity:
#Entity
#Table(name = "countries")
public class CountryEntity implements Serializable {
#Id
String id;
String country_name;
//other fields
}
Running my application I get the folowing error:
...
Caused by: org.hibernate.AnnotationException: referencedColumnNames(country) of CountryEntity.countrySet referencing AlbumEntity not mapped to a single property
...
What's interesting is that if I move country field from SuperAlbumEntity to AlbumEntity everything just works fine...
Can someone explain me why I get this error?
I'm not sure but I think is connected with the type of inherence that you used it. Try to modify your superclass to something like this:
SuperAlbumEntity:
#MappedSuperclass
public abstract class SuperAlbumEntity {
}
AlbumEntity:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name = "albums")
public class AlbumEntity extends SuperEntity {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}

Spring data JPA derived query for multiple #OneToMany entities and inner entity localization

I am trying to do a simple task with Spring Data JPA derived queries and am unable to get the desired results from the query. Basically I have a Book which can have one or many Chapters with localization support for the Book as well as the Chapter. I want to create a query which would fetch a language specific book (with chapters) based on the Locale. Here are my four entities.
#Entity
#Getter
#Setter
public class Book {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int noOfPages;
/**
* Both mappings below are unidirectional #OneToMany
*/
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<BookTranslation> bookTranslations;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<Chapter> chapters;
/**
* Constructor for JPA
*/
protected Book() {
}
public Book(int noOfPages, List<BookTranslation> bookTranslations, List<Chapter> chapters) {
this.noOfPages = noOfPages;
this.bookTranslations = bookTranslations;
this.chapters = chapters;
}
}
#Entity
#Getter
#Setter
public class BookTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String name;
/**
* Constructor for JPA
*/
protected BookTranslation() {
}
public BookTranslation(Language language, String name) {
this.language = language;
this.name = name;
}
}
#Entity
#Getter
#Setter
public class Chapter {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int chapterNumber;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "CHAPTER_ID", referencedColumnName = "ID")
private List<ChapterTranslation> chapterTranslations;
/**
* Constructor for JPA
*/
protected Chapter() {
}
public Chapter(int chapterNumber, List<ChapterTranslation> chapterTranslations) {
this.chapterNumber = chapterNumber;
this.chapterTranslations = chapterTranslations;
}
}
#Entity
#Getter
#Setter
public class ChapterTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String title;
/**
* Constructor for JPA
*/
protected ChapterTranslation() {
}
public ChapterTranslation(Language language, String title) {
this.language = language;
this.title = title;
}
}
public enum Language {
EN, FR
}
Below is the sample code, I am using to persist these entities. Ignore the #GetMapping please, this is just a sample.
#GetMapping("/persist-book")
public void persistBook() {
ChapterTranslation enChapter = new ChapterTranslation(Language.EN, "What is Java persistence?");
ChapterTranslation frChapter = new ChapterTranslation(Language.FR, "Qu'est-ce que la persistance Java?");
List<ChapterTranslation> chapterOneTranslation = new ArrayList<>();
chapterOneTranslation.add(enChapter);
chapterOneTranslation.add(frChapter);
Chapter chapterOne = new Chapter(1, chapterOneTranslation);
List<Chapter> chapters = new ArrayList<>();
chapters.add(chapterOne);
BookTranslation enBook = new BookTranslation(Language.EN, "JPA WikiBook in English");
BookTranslation frBook = new BookTranslation(Language.FR, "JPA WikiBook in French");
List<BookTranslation> bookTranslations = new ArrayList<>();
bookTranslations.add(enBook);
bookTranslations.add(frBook);
Book book = new Book(500, bookTranslations, chapters);
bookRepository.save(book);
}
My BookRepository looks as follows:
public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(Language lang1, Language lang2);
}
Sample code I am using to retrieve the result.
#GetMapping("/english-book")
public List<Book> retrieveEnglishBook() {
return bookRepository.findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(
Language.EN, Language.EN
);
}
My expected output is as attached in the image below.
One thing that I noticed from the Hibernate logs is that Hibernate makes a total of four select queries and the first query output is exactly what I need. However, since this a method name based query I don't suppose I can control that.
EDIT 1: Before trying out the answer, I was getting all books with all their locales returned, after changing my query to the one given in the accepted answer I was able to get the Book with the selected locale.
Please note: I also had to change all collections from using a List to a Set, more on this can be read about in the accepted answers link.
What you describe as a desired result is a single database result.
I guess what you mean by that is you expect to get all the books but only with the translations in a single language.
You don't describe what you actually get, so assume you are getting the book with all available translations.
Your desired result is beyond the capabilities of derived queries.
The different predicates of a derived queries all limit the root entities to be returned Book in your case. They should still have all references in tact.
You could achieve your goal with an annotated query like this:
public interface BookRepository extends CrudRepository<Book, Long> {
#Query("SELECT b FROM Book b
JOIN FETCH b.bookTranslations as bt
JOIN FETCH b.chapter as c
JOIN FETCH c.chapterTranslation as ct
WHERE bt.language = :lang
AND ct.language = :lang")
List<Book> findBooksByLanguage(Language lang);
}
See also How to filter child collection in JPQL query?
Side note: query derivation should only be used when the resulting method name is VERY similar to what you would have named the method anyway.

Register data into Many-to-Many Relation Table

I have 'Course' and 'Student' entities. They have many-to-many relation. So, i have COURSE_STUDENT(contains 'student_id' and 'course_id' columns) table. I want to register students to courses with a button.(For example; a student lists courses and click Register button to register a specific course).
When i want to create new courses, i use courseRepository and courseMapper which comes from JHipster by default.
But i don't have repository and mapper files for COURSE_STUDENT. Because it is not actually a main entity. It is created for many-to-many relation.
How can i register students to courses?
Git repo:https://github.com/canberkizgi/monolithic-mucs
My course entity:
#Entity
#Table(name = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description")
private String description;
#ManyToOne
private Instructor instructor;
#ManyToMany(fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="students_id", referencedColumnName="id"))
private Set<Student> students = new HashSet<>();
Student entity:
#Entity
#Table(name = "student")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(unique = true)
private User user;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "students")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Course> courses = new HashSet<>();
For example; Createcourse function with Mapper and Repository
#PostMapping("/courses")
#Timed
public ResponseEntity<CourseDTO> createCourse(#Valid #RequestBody CourseDTO courseDTO) throws URISyntaxException {
log.debug("REST request to save Course : {}", courseDTO);
if (courseDTO.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new course cannot already have an ID")).body(null);
}
Course course = courseMapper.toEntity(courseDTO);
course = courseRepository.save(course);
CourseDTO result = courseMapper.toDto(course);
return ResponseEntity.created(new URI("/api/courses/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
The relationship is owned by the course entity. Thats because on the student side the #ManyToMany annotation has a mappedBy attribute. This means, that the database will reflect the set in the course. You need to add students to that set to save the relationship. That change needs to be done within a transaction.
That being said it would probably be best to follow DDD here. I would create a registerTo method in the student class that would take the course as a parameter. I would then call this.courses.add(course) and course.getStudents().add(this) in that method.

Unable to save data to composite Table Via Spring Data rest json post

I have 3 Tables in db
training
- training_id (pk)
user_profile
- profile_id (pk)
-training_profile (composite table)
- training_id
- profile_id
I have already record in user_profile table having profile_id=44 and want to create new record for training table ,and also to associate this new training with already existing user_profile record which has id 44,but after post data is saved to training table but it is not inserted into lookup table user_training.
My Object Classes Are
- Training Class
#Entity
#Table(name = "training", schema = "public")
public class Training implements java.io.Serializable {
#Id #GeneratedValue
#Column(name = "training_id", unique = true, nullable = false)
private Long trainingId;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "trainings")
private Set<UserProfile> userProfiles = new HashSet<UserProfile>(0);
#Column(name = "training_subject", length = 200)
private String trainingSubject;
public Training() {
}
public Long getTrainingId() {
return this.trainingId;
}
public void setTrainingId(Long trainingId) {
this.trainingId = trainingId;
}
public String getTrainingSubject() {
return this.trainingSubject;
}
public void setTrainingSubject(String trainingSubject) {
this.trainingSubject = trainingSubject;
}
public Set<UserProfile> getUserProfiles() {
return this.userProfiles;
}
public void setUserProfiles(Set<UserProfile> userProfiles) {
this.userProfiles = userProfiles;
}
}
UserProfile
#Entity
#Table(name = "user_profile", schema = "public")
public class UserProfile implements java.io.Serializable {
#Id #GeneratedValue
#Column(name = "profile_id", unique = true, nullable = false)
private Long profileId;
#Column(name = "profile_description")
private String profileDescription;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(name = "user_training", schema = "public", joinColumns = {
#JoinColumn(name = "profile_id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "training_id", nullable = false, updatable = false) })
private Set<Training> trainings = new HashSet<Training>(0);
public UserProfile() {
}
public String getProfileDescription() {
return this.profileDescription;
}
public void setProfileDescription(String profileDescription) {
this.profileDescription = profileDescription;
}
public Set<Training> getTrainings() {
return this.trainings;
}
public void setTrainings(Set<Training> trainings) {
this.trainings = trainings;
}
}
My json post via postman
And Response I get
Response show that new training record inserted in table having training_id as 67
No association found for this new saved training
again it created new record for training and does not associate with existing user profile , I post curl -i -X POST -H "Content-Type:application/json" -d "{ \"trainingSubject\" : \"Oracle\", \"userProfiles\":[\"/userProfiles/44\"] }" http://localhost:8080/api/trainings
You could use the relative url assignment:
{
"trainingSubject": "oracle",
"userProfiles":["/userProfiles/44"]
}
Maybe also try with the full url: http://localhost:8080/api/userProfiles/44
EDITED
If you move the owning site of the ManyToMany relation to Training it will work with the above JSON. So currently the owner is allowed to set the realtions. If you do it like that:
#ManyToMany
#JoinTable(name = "user_training"
, joinColumns = {#JoinColumn(name = "profile_id") }
, inverseJoinColumns = {#JoinColumn(name = "training_id") })
private List<UserProfile> userProfiles = new ArrayList<>();
plus
#ManyToMany(mappedBy = "userProfiles")
private List<Training> trainings = new ArrayList<>();
Training owns the relation within userProfiles.
I think in your case it's the best option for now. Another option would be, when keeping the owner site at UserProfile on transactions, to update the relation there like:
PATCH http://localhost:8080/api/userProfiles/44
{
"trainings": ["trainings/66", "trainings/67"]
}
But with this you would need multible rest calls (1. POST new training and get the new Id 2. GET current training list 3. PATCH trainings list with newly added training)
Last option would be to add the REST-controller on your own.
Complete files for the first approach:
#Entity
#Table
public class Training implements Serializable {
#Id
#GeneratedValue
private Long trainingId;
#ManyToMany
#JoinTable(name = "user_training"
, joinColumns = {#JoinColumn(name = "profile_id") }
, inverseJoinColumns = {#JoinColumn(name = "training_id") })
private List<UserProfile> userProfiles = new ArrayList<>();
#Column(name = "training_subject", length = 200)
private String trainingSubject;
#Entity
#Table
public class UserProfile implements Serializable {
#Id
#GeneratedValue
private Long profileId;
#Column(name = "profile_description")
private String profileDescription;
#ManyToMany(mappedBy = "userProfiles")
private List<Training> trainings = new ArrayList<>();
public interface TrainingRepository extends JpaRepository<Training, Long> {
}
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {
}
With the upper JSON this will work, I tested it. You will not see the correct result directly in the response of curl-POST. To see the added relation you must follow the userProfiles-link like GET http://localhost:8080/transactions/<newId>/userProfiles

Resources