Hibernate - #ManyToOne on Entities that extends from Abstract class - spring

I have a similar structure already existing in my database and it has many data:
#MappedSuperclass
public abstract class Person {
#Id
Long id;
#Column(name="COMMONFIELD")
String commonField;
}
#Entity
#Table(name = "WOMAN")
public class Woman extends Person {
.....
}
#Entity
#Table(name = "MAN")
public class Man extends Person {
.....
}
This code will work with two different tables WOMAN and MAN where both will contain the common fields defined in the abstract class Person.
Now I will like to have a class Car that has an owner which can be either a woman or a man. A person can also be the owner of several cars. So I declare it like this:
#Entity
#Table(name = "CAR")
public class Office {
....
#ManyToOne
#JoinColumn(name = "IDPERSON")
Person owner;
}
The issue I encounter is that I cannot create an attribute linked to a Mapped-Superclass.
Of course, I could solve this issue simply by defining Person as an entity and declaring it to be InheritanceType.SINGLE_TABLE and Woman and Man with a discriminator this way they could share the same table and it will work. However, this solution is not possible for me because the data is already there and there are many dependencies from it, so I cannot change the WOMAN or MAN table.
Is there a way to use a discriminator in this case too, so that Hibernate will know in which table it has to look for the person?
Thank you very much in advance.

So I couldn't find any way to tell hibernate to use a discriminator on a specific attribute.
So what I did, I created a Gender Enum
enum Gender {
WOMAN, MAN;
}
And I added it as an attribute of a Car. I also wired the woman and the man service and created a getter method for the owner that will decide which service to use based on gender.
#Entity
#Table(name = "CAR")
public class Office {
....
#Column(name = "PERSONID")
Long personId;
#Column(name = "GENDER")
Gender gender;
#Autowired
#Transient
WomanService womanService;
#Autowired
#Transient
ManService manService;
Optional<Person> getOwner(){
switch(gender){
case WOMAN: return womanService.findById(personId);
case MAN: return manService.findById(personId);
default: return Optional.empty();
}
}
}
I'm aware this solution sucks but it's the best I could come up with to avoid redefining other entities.

Related

How to update other table when inserting with OneToOne using Spring JPA

I have two tables joined with a OneToOne relationship, one side exists in the database. When I insert the other side I want the first side's foreign key column to update so that it knows about the relationship without having to do it manually. Is this possible.
Here's my simplified example, I am using #MappedSuperclass because I have some shared fields in most of my Entities I included it here just in case it's causing an issue.
The base entity
#MappedSuperclass
#Data
public abstract class BaseEntity {
//defines some common fields I have in all my entities such Id
}
Abstract Image class
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
#Data
public abstract class Image extends BaseEntity {
//defines some other fields
public abstract UUID getTypeId();
}
UserProfilePhoto
#Entity
#Data
public class UserProfilePhoto extends Image {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userProfilePhoto")
private Profile profile;
#Override
public UUID getTypeId() {
return user.getId();
}
}
Profile
#Entity
public class Profile extends Base {
//defines some other fields
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn()
private UserProfilePhoto profilePhoto;
}
I'm looking for the following behavior:
When UserProfilePhoto is saved to image table (with Profile ID) the corresponding ImageId is updated in Profile
When Profile is Deleted the corresponding UserProfilePhoto is deleted from image table.
When UserProfilePhoto is deleted Profile remains but the foreign key column is nulled.
From researching this I think it's possible but it's a matter of getting the annotations correct. I've tried different combinations with no luck. Is what I'm looking for even possible?
No, it is not possible the way you describe it.
Any bidirectional relationship in JPA is controlled exclusively by the side indicated by mappedBy, so you need to update that side in your code, in order to have it persisted.
You can do that by invoking the other side in the setter, or by editing the other side in the first place.

Spring: combine JPA Derived query methods and query by example

Is it possible to use JPA derived methods and query by example at the same time?
Let's imagine i have two entities like this:
#Entity
#Data
public class Person {
#Id
#GeneratedValue
Long id
String name;
String surname;
#OneToMany
List<Dog> dogs;
}
#Entity
#Data
public class Dog{
#Id
#GeneratedValue
Long id
String name;
}
I'd like to be able to do something like this (just an example):
Person p = new Person ();
p.setName("Mario");
personRepository.findDistinctByDogsIsNotNull(Example.of(p));
The Example.of(p) only works if i do findAll, but it doesn't work if i define inside the repository a method like this
private interface PersonRepository extends JpaRepository<Person, Long>{
List<Person> findDistinctByDogsIsNotNull(Example<Person> example)
}
The error it gives me is something like this:
Failed to create query for method public abstract java.util.List dev.cele.test.repository.PersonRepository.findDistinctByDogIsNotNull(org.springframework.data.domain.Example)! At least 1 parameter(s) provided but only 0 parameter(s) present in query.
So my question is: is it possible to do a query by example in a JPA derived query method?
And if it's not possible how can i create some sort of parametrizable query that also has a predetermined condition?

how to write the JpaRepository for tables which has composite keys

Please refer attached screenshot to understand the table structure.
Empd_Id is the primary key in 'Employee' table which in turn becomes as a part of composite key along with 'product_id' in table called 'product'.
Any employee can have multiple products so in that case it becomes 'One-to-Many' relationship between 'Employee-Product' tables. Now I'm confused whether I need to write just 1 JpaRepository interface i.e. for employee or 2 JpaRepository interfaces (1 for Employee and another for Product). My gut feeling is just 1 interface for Employee table but how???
Following is my code snippet:-
1st JPA repository interface
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
Entity:-
#Entity
#Table(name="product")
public class Product{
#EmbeddedId
private EmpProd empProd;
#Column(name="product_name")
private String commerceUserId;
#Column(name="description")
private String description;
For composite keys:-
#Embeddable
public class EmpProd implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull
#Column(name="emp_id")
private String empId;
#NotNull
#Column(name="product_id")
private String productId;
2nd Jpa repository interface
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
Entity class:-
#Entity
#Table(name="employee")
public class Employee{
#Id
#NotNull
#Column(name="emp_id")
private String empId;
#Column(name="first_name")
private String firstName;
Though, I have written 2 separate JPA repositories, I strongly believe there will be need for just 1, the main one i.e.
public interface MyMainDataRepository extends JpaRepository {
}
But I do not know to related both entity classes and fetch data from using single Jpa repository as I'm new to Spring Data JPA. I would really appreciate if someone can help me here. Thanks
The two entities Product and Employee don't have any connection as far as JPA is concerned. Therefore you can't access both through a single repository.
If for example, Product would have an actual reference to an Employee you could use a ProductRepository to load Products and navigate from there to the referenced Employees.
But even if that might be feasible, I'd guess that Product and Employee should be considered different aggregates and therefore, should have their own repository each. See Are you supposed to have one repository per table in JPA? for more information on that question.
Given the entities, your repositories look just fine. Note that the entities do look atypical due to the use of String productId instead of Product product.
If you wanted to fetch the employee details, you need the following interface,
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
If you wanted to fetch the product details, you need the following interface,
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
The employee is related to product table, the iteration happens via product and related employees. From this, you can not access the employee table directly and retrieve the employee results from MyRepository interface.

spring jpa projection nested bean

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

Spring JPA one to many denormalized count field

I have two entities, Books and Comments, in a one to many relationship (one book can have many comments). I want to be able to list books and number of comments about a book. I want it denormalized, meaning the books entity will have a counter that has number of comments for that book, and it will be updated every time a comment is entered (just playing with the concept, no need to discuss about the need of denormalizing here).
I think (correct me if I am wrong) this could be easily done with a trigger in the database (whenever a new comment is created, update a counter in the books table to the corresponding bookId), but for the sake of learning I want to do it through JPA, if it makes sense.
What I have so far: //omitted some annotations, just general info
Boks entity:
#Entity
public class Books {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String author;
private Long numComments;
// getters and setters...
}
Comments entity:
#Entity
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String comment;
private Long authorId;
private Long bookId;
// getters and setters...
}
Books repository: I added here a query to perform the update
/**
* Spring Data JPA repository for the Books entity.
*/
public interface BooksRepository extends JpaRepository<Books,Long> {
#Modifying
#Query("UPDATE Books v SET v.numComments = v.numComments + 1 WHERE v.id = :bookId")
int updateCounter(#Param("bookId")Long bookId);
}
And now the question: What next? I think I can put the update of the Books entity annotating with #PostPersist a method of the entity Comments, but I have been unsuccessful so far. I can imagine something like this:
#PostPersist //This function in the entity Comments
protected void updateBooks() {
//Likely some call to the repository here that updates the count
// in books the info we have from current entity.
}
Any idea on how to do this? Some best practices about this kind of denormalization in JPA? Better to use the database triggers?
spring not managed your entity classes and your idea is possible but you must inject BooksRepository in enttiy class then stay at you get Nullpointerexception because spring not managed enttiy classes,The reason your BooksRepository not initlaized, try also read this post Bean injection inside a JPA #Entity and anotate entity class #Configurable after
try this
#PostPersist
protected void updateBooks(Comments comment) {
int totalComment = BooksRepository.updateCounter(comment.getBookId());
System.out.println(totalComment); // see totalComment in console
}
but good aprroach in service classes after call updateCounter when insert comment
example in your CommendService : when try a insert commend after call your updateCounter
if(comment.getBookId() != null) //Simple Control
{
CommentRepository.save(comment);
BooksRepository.updateCounter(comment.getBookId());
}

Resources