How to link an entity from one table into another? - spring-boot

How can I link an entity that already exists in another table into my important_table? I could insert the ID but then that would require a query. What I want is that the system automatically maps the element in the people_table to the important_table.
#Entity(name = "important_table")
data class ImportantEntity(
#Id #GeneratedValue #Column(name = "id")
val id: Short = 0,
#Embedded
val person: Person
)
Person Entity
#Entity(name = "person_table")
data class PersonEntity(
#Id #GeneratedValue #Column(name = "id")
val id: Long = 0,
...
)
I tried embedded but that creates a duplicate Person in the db. I want the link so that I can find "important" people easy and still get the same data.

Use a #OneToOne mapping to let Hibernate (or any ORM) knows you are linking the tables together.
#Entity(name = "important_table")
data class ImportantEntity(
#Id #GeneratedValue #Column(name = "id")
val id: Short = 0,
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
val person: Person
)
When you say: I could insert the ID but then that would require a query, indeed, it does... But with the FetchType.EAGER, it's your ORM that will execute the extra query for you... It is transparent.
Nonetheless, be careful with that, if you don't pay attention to your relationships, you can end up loading the entire db in memory...
As explained in this site, and this tuto, #Embedded is used to help having a nice and clean object definition, while storing the data into one table instead of 2... For instance, in your case, you would be storing the data from the class Person into the table ImportantTable.
Hope it helps !

Related

Optimal way of checking if user already upvoted/downvoted a comment on a post - Spring JPA

Post entity:
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "post")
private List<PostComment> postComments;
...
}
PostComment entity:
public class PostComment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_id")
private Post post;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentUpvote> postCommentUpvotes;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentDownvote> postCommentDownvotes;
...
}
PostCommentUpvote entity (PostCommentUpvote and PostCommentDownvote have the exact same fields - these entities act like counters)
public class PostCommentUpvote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_comment_id")
private PostComment postComment;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
...
}
All relations are bi-directional as you can see from the annotations.
The goal: When a user (authenticated) upvotes/downvotes a PostComment I want to do the following:
Check if user already upvoted/downvoted the PostComment.
For this I have Post id (even though this is not needed) and PostComment id and both are indexed.
There are three possible 'states' when User up/downvotes the comment:
User hasn't yet up/downvoted that comment, so it is either new upvote or new downvote
User has already upvoted and if he upvotes again, it will remove the upvote (same with downvote)
User has already upvoted and if he downvotes, upvote is removed and new downvote is added (and vice-versa)
What would be the most optimal way of doing this? Get the PostComment by its id and then loop through the List of PostCommentUpvote/PostCommentDownvote and check the User on every iteration? Or perform a tactical SQL request, which must be faster than looping in Java? If so, what would this SQL query look like? Or any other approach to make this performant. I am open to any suggestion.
Thanks
Assuming you have the post comment id and user id, the following JPA query (or close to it) will return true if the user has upvoted on the post comment and false otherwise:
select case when count(postCommentUpvote) > 0 then 'true' else 'false'
from PostCommentUpvote postCommentUpvote
join postCommentUpvote.postComment postCommnent
where postComment.id = :postCommentId
and user.id = :userId
You would then have to perform the same query using the PostCommentDownVote entity. An alternative would be to remove the up and down vote entities, simply create a PostCommentVote entity which has a boolean attribute that indicates up or down, and helper methods isUpvote() and isDownVote() that would interpret the boolean for you. You could get everything you need with a single query that returns a PostCommentVote if the user has up or down voted and null otherwise.
You did not indicate what you want to do if the user has already commented on the post; ignore the request or update the PostComment. Either way the most optimal way of doing this would be not checking at all. Create a unique index on (user_id, post_comment_id) or drop the the id column and make a composite PK of those columns. Then just insert without checking. Use the On Conflict to either ignore or update the request. You may also want to add an Up/Down vote indicator column.

Best design pattern for Spring Boot CRUD REST API with OneToMany relationships

I'm struggling to find what feels like a good design for a Spring Boot CRUD REST API app that involves several OneToMany relationships w/ join tables. For example, consider this DB structure in MySQL which allows one "Recipe" to be associated with several "Recipe Categories":
create table recipes
(
id int auto_increment primary key,
name varchar(255)
);
create table recipe_categories
(
id int auto_increment primary key,
name varchar(64) not null
);
create table recipe_category_associations
(
id int auto_increment primary key,
recipe_category_id int not null,
recipe_id int not null,
constraint recipe_category_associations_recipe_categories_id_fk
foreign key (recipe_category_id) references recipe_categories (id)
on update cascade on delete cascade,
constraint recipe_category_associations_recipes_id_fk
foreign key (recipe_id) references recipes (id)
on update cascade on delete cascade
);
On the Java side, I'm representing the structures as JPA entities:
#Entity
#Table(name = "recipes")
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
#JsonManagedReference
private Set<RecipeCategoryAssociation> recipeCategoryAssociations;
// ... setter/getters ...
}
#Entity
#Table(name = "recipe_categories")
public class RecipeCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
// ... setter/getters ...
}
#Entity
#Table(name = "recipe_category_associations")
public class RecipeCategoryAssociation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "recipe_category_id", nullable = false)
private RecipeCategory recipeCategory;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "recipe_id", nullable = false)
#JsonBackReference
private Recipe recipe;
// ... setter/getters ...
}
This works OK, but my hang-up is that to persist/save a new Recipe via REST JSON API, the caller needs to know about the join table recipe_category_associations. For example a PUT request w/ this payload could add a new Recipe to the DB associating it with the "category foo" recipe category:
{
"name": "Chicken soup",
"recipeCategoryAssociations": [{
"recipeCategory": {
"id": 123,
"name": "category foo"
}
}]
}
Using this in the controller:
#PutMapping(path = PATH, produces = "application/json")
#Transactional
public #ResponseBody Recipe addNewRecipe(#RequestBody Recipe recipe) {
return recipeRepository.save(recipe);
}
To me, the inclusion of "recipeCategoryAssocations" key in the JSON payload feels weird. From the client POV, it doesn't really need to know the mechanism creating this association is a join table. Really, it just wants to set a list of recipe category ids like:
{
"name": "Chicken soup",
"recipeCategories": [123, 456, ...]
}
Any tips how best to accomplish this in nice way? It'd be nice if I can keep the REST implementation super clean (e.g., like I have now with one recipeRepository.save(recipe); call). Thanks in advance.
When writing software we expect requirement to change. Therefore we want to make sure our code will be flexible and easy to evolve.
Coupling our server response with our DB structure makes our code very rigid. If a client needs a new field or if we want to arrange the DB schema differently everything will change.
There are several different approaches to designing your software correctly. A common approach is called "Clean Architecture" and is outlined in a book by this title by the great Uncle Bob. The Book itself outlines the approach in high level but there are many example projects online to see what it means in action.
For example this article by my favourite Java blog:
[baeldung.com/spring-boot-clean-architecture][1]
If you are looking for something simpler, you can follow the ["3-Tier Architecture"][2] (not really an architecture in my mind). Separate your code in to 3 layer:
Controller/Resource/Client
Service/BusinessLogic
Repository/DataAccess
Each layer will use a different data object. the business logic layer will have the object in it's purest form without constraints regarding who will want to read it and where it is stored and will be mapped/converted to the objects in the other layers as needed.
So in your case you might have 3 (or more) different objects:
RecipeDTO
Recipe
model.Recipe (and model.RecipeCategoryAssociation etc.)
Make sure that the Business level object only have fields that makes sense from a business logic. The code in each layer will use the objects that are relevant to that layer. When a rest controller class for example calls the business logic server it will need to convert the DTO object to the Business level object for example. Very important to maintain this separation between layers

Put Reference from Audit table to Another Table in Hibernate Envers

I'm using Hibernate Envers for Auditing Change Data, I have a Class that store information about companies like this :
#Getter
#Setter
#Entity
#Table(name = "COMPNAY")
#Audited
public class Compnay {
private String name;
private String code;
}
and it's using Envers for keeping the changes of companies.
also, I have a class for Keep the data of items that manufacture in any of this company, the class will be like this :
#Getter
#Setter
#Entity
#Table(name = "COMPNAY")
#Audited
public class Item {
#Column(name = "NAME", nullable = false)
private String name ;
#Column(name = "CODE", nullable = false)
private String code;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COMPANY_ID", nullable = false)
private Compnay compnay;
}
Consider that there is a company in company table like this :
ID
NAME
CODE
1
Apple
100
2
IBM
200
and the data in the item's table will be like this :
ID
NAME
CODE
COMPANY_ID
3
iPhone
300
1
4
iPad
400
1
if I edit the information of Apple company and change the code from 100 to 300 how can I fetch the information of Items that were saved before this change with the previous code? Is there is any way to reference to audit table?
Yes, you can write a HQL query that refers to the audited entities. Usually, the audited entities are named like the original ones, with the suffix _AUD i.e. you could write a query similar to the following:
select c, i
from Company_AUD c
left join Item_AUD i on i.id.revision < c.id.revision
where c.originalId = :companyId

Spring Data + View with Union return duplicate rows

i'm using Spring Boot 2.4.2 and Data module for JPA implementation.
Now, i'm using an Oracle View, mapped by this JPA Entity:
#Entity
#Immutable
#Table(name = "ORDER_EXPORT_V")
#ToString
#Data
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class OrderExportView implements Serializable {
private static final long serialVersionUID = -4417678438840201704L;
#Id
#Column(name = "ID", nullable = false)
#EqualsAndHashCode.Include
private Long id;
....
The view uses an UNION which allows me to obtain two different attributes of the same parent entity, so for one same parent entity (A) with this UNION I get the attribute B in row 1 and attribute C in row 2: this means that the rows will be different from each other.
If I run the query with an Oracle client, I get the result set I expect: same parent entity with 2 different rows containing the different attributes.
Now the issue: when I run the query with Spring Data (JPA), I get the wrong result set: two lines but duplicate.
In debug, I check the query that perform Spring Data and it's correct; if I run the same query, the result set is correct, but from Java/Spring Data not. Why??
Thanks for your support!
I got it! I was wrong in the ID field.
The two rows have the same parent id, which is not good for JPA, which instead expects a unique value for each line.
So, now I introduced a UUID field into the view:
sys_guid() AS uuid
and in JPA Entity:
#Id
#Column(name = "UUID", nullable = false)
#EqualsAndHashCode.Include
private UUID uuid;
#Column(name = "ID")
private Long id;
and now everything works fine, as the new field has a unique value for each row.

How to create join table with extra column with JPA annotations?

I need for a project to join 2 SQL tables implemented like this :
I know that I'm not supposed to implement the table IngredientList as an object cause it's only here for SQL structure.
My code goes like this :
#Entity
#Table(name="recipe")
public class Recipe {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_recipe")
private Long id;
#OneToMany
#JoinTable(name="liste_ingredients", joinColumns = #JoinColumn(name = "id_recette",
referencedColumnName = "id_recette"),
inverseJoinColumns = #JoinColumn(name = "id_ingredient",
referencedColumnName = "id_ingredient"))
List<Ingredient> ingredients;
/* Getter/Setter/Constructor */
}
Which is the classic way but with that I lose the Quantity attribute that I want to associate with ingredient. And I don't get how I can work around this without creating an object IngredientList.
Thanks in advance.
Nevermind that I got my answer gonna edit it soon with code, for anyone with the same question.

Resources