Best practise to model custom *relations* with Spring Boot - spring

Whats the best practise in Spring Boot to model custom relations between two entities in projection.
My entity Participation links to Competition, User and Team.
public class Participation
{
#Id
private String id;
#NonNull
#OneToOne
private Competition competition;
#OneToOne
private Team team;
#NonNull
#OneToOne
private User user;
private String info;
}
In my project, I want to link all participating Users for a specific Team to a Competition. To achieve this, I wrote a CompetitionDTO that has a field List<User> participants that is filled by a custom CompetitionService:
public Page<CompetitionDTO> teamParticipations (Team team, Pageable pageable)
{
Page<CompetitionDTO> page = cRep.findTeamParticipation(team, pageable);
page.forEach(competition -> competition.setParticipants(pRep.findParticipants(competition, team)));
return page;
}
I don't like this approach a lot because I assume there is a more elegant way to do this with Spring.

Related

How to translate #ManyToOne relation in Monolith app into a Microservice App?

I have a monolith app where its models are joined to each others(OnetOne, ManyToMany..).
I was able to create the different Microservices, but I got stuck on how to transition these relationships into Microservices.
Here is my first Class:
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "ID",referencedColumnName="ID")
private B b;
//getters and setters
}
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
//getters and setters
}
I also Created a microservice for A (controller,repository, service...) and a separate microservice for B.
I am trying to call the Class Model B from the microservice B. But I am not sure how to do it?
I also wonder if it is write to link two classes by joint in microservices or not ?
Thanks
The join relations such as #OneToOne or #ManyToMany are JPA specific and there is no straightforward way to make them work in microservice world.
In general, in microservice world you give up the ACID transactions for cross-service relations and replace them with BASE transactions (eventual consistency behaviour).
In your example, you can achieve this by implementing one of the following strategies.
Fetch the required entity using rest API from the other service.
As you divide your domain into different bounded contexts (services), you will eventually create two different stores with the following entities:
Service A
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#Column(name = "B_REFERENCE_ID")
private Integer bId;
//getters and setters
}
And Service B:
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ElementCollection
private List<Integer> aIds;
//getters and setters
}
Then, you create your target DTO in the the service (example for service B):
public class BDto{
private int id;
private String name;
private List<ADto> aIds;
//getters and setters
}
Then, you need to fetch the dto you want to expose/consume yourself:
#RestController
public class BController {
private final WebClient webClient;
public BController() {
this.webClient = WebClient.builder()
.baseUrl(SERVICE_A_URL)
.build();
}
#GetMapping(path = "/{id}")
public Mono<BDto> getB(#PathVariable int id) {
Optional<B> bEntity = fetchBFromDatabase();
if (bEntity.isPresent()) {
var b = bEntity.get();
var aEntityIds = b.getaIds();
return webClient
.method(HttpMethod.GET)
.uri(GET_A_URL, aEntityIds)
.exchangeToFlux(response -> response.bodyToFlux(ADto.class))
.collect(Collectors.toList()).map(aDtos -> new BDto(b.getId(), b.getName(), aDtos));
}
return Mono.empty();
}
}
If you are unfimiliar with WebClient and reactive concepts, reference spring boot docs https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html
Of course, the good old RestTemplate can be used here as well.
In order to provide data consistency, you will need to implements eventing system with a message broker in between such as Kafka, Apache Pulsar or RabbitMQ.
As an alternative approach, you can keep both A and B entities in both microservices. In service A, you store only the information of B entity that is required in the service A domain and vice versa. In microservice world it is rare that you will require all the B data in service A.
Then, you can keep your join relations as they are in A and B services for fetching purposes.
Remember that you will still require only single source of truth. So, if data changes in service B, then you will need to update your B_ENTITY data in service A and vice versa. Thus, eventing system will still be required to properly updates states in both your services.
The topic of state management in microservices is a complex one, so I recommend to read more about it to get more comfortable with the topic:
https://phoenixnap.com/kb/acid-vs-base
https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215
https://samnewman.io/books/building_microservices_2nd_edition/
Microservices are meant to be indepedent, so that if one service failed, it will not affect the others.
But if you are using multi-module structure, then add the module using the following code to your pom.xml:
<modules>
<module>module1</module>
<module>module2</module>
</modules>
but I am not sure this will work with Jpa.

Spring MVP Forms overwriting Many to Many ArrayList when updating an object

I have a simple project that has a User model, Sports team model and a Many To Many table where a user can "like" the sports team.
User
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "likes",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "team_id")
)
private List<Team> teamsLiked;
Team
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(min=2, max=30)
private String teamName;
#NotBlank
private String city;
private String sport;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "likes",
joinColumns = #JoinColumn(name = "team_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> likers;
My problem is, when I'm using Spring MVC forms for a user to edit a team, upon submission it completely wipes out existing likes on the Team object under likers. On the edit page, I am using #ModelAttribute and pre populating the existing team object, and have tried to put the likers as a hidden attribute so the data will persist, but that throws an error. I've tried on the #PostMapping backend, to set the origin list of likers before re-saving the DB and that's not working either. Besides using Normal HTML forms to update an object, is there a way I can have the list of users who liked a team persist after updating? Thanks in advance.
What you need here is a DTO and map that onto an existing entity. I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Team.class)
#UpdatableEntityView
public interface TeamDto {
#IdMapping
Long getId();
String getTeamName();
void setTeamName(String teamName);
String getCity();
void setCity(String city);
String getSport();
void setSport(String sport);
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TeamDto a = entityViewManager.find(entityManager, TeamDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<TeamDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
And in your case of saving data, you can use the Spring WebMvc integration
that would look something like the following:
#Transactional
#PostMapping("/teams")
void save(#RequestBody TeamDto dto){
repository.save(dto);
}

Need help regarding JPA entity mapping

I'm fairly new to ORM. I'm having trouble deciding how exactly I should map the following entities.
DiscussionThread
Post
User
AnonymousUser
DiscussionThread would be something similar to the ones we see in bulletin boards online. It would contain a list of Post which would be posted by User. However, I do not want the User to reveal his/her identity while posting in the DiscussionThread.
In order to achieve that I created a list of proxy usernames denoted by the entity AnonymousUser. Thus, whenever a User decides to make a Post in a DiscussionThread, he would be posting as an AnonymousUser. Any further Post made by the same User in that DiscussionThread would be linked to the same AnonymousUser.The User will have different AnonymousUser names in different DiscussionThreads. An instance of AnonymousUser may be used by two different users on two different threads.
In simpler words, there will be one AnonymousUser for one User in each DiscussionThread.
I have created the following POJO entities, but I'm stuck in how I should map them to each other.
public class AnonymousUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String displayPicture;
//Not sure how to make relationships here
private Set<DiscussionThread> discussionThreads;
private Set<User> users;
}
public class DiscussionThread {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String description;
}
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String username;
}
Any help will be appreciated.
Thank you!
Well, you basically described:
Don't know if it's right or not but this is one way you could diagram and think about such problems. This is Chen's database notation in Visio.

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

spring security datamodel

I'm currently using the spring-security libraries and I asked myself the following question: How should I combine my database model with the spring-security tables?
As you know spring-security needs two tables (users and authorities) to define an authentication manager in the database. From my pov there are now two possibilities where I store my additional user-information (like email, lastname, last-logged-on, ....)
I could have a plain user-table for authentication purposes and another one for the rest (linked by the username)
I extend the user-table of spring-security with my necessary attributes.
What is the best design from your perspective? What are your experiences?
Lomu
I created a POJO User which represents the User entity as conceived by the Spring Security library, and secondly I created a POJO ProfiledUser to represent a specialized type of user of my application. It is called ProfiledUser because I needed a user associated to a profile. Of course, a similar approach can be applyied for every type of user you need to represent. Basically, if you need more than one type of user you can make your classes to extend the User POJO.
In the following you find the class, with the JPA annotations.
#Entity
#Table(name="USERS")
#Inheritance(strategy=InheritanceType.JOINED)
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private long id;
private String username;
private String password;
private boolean enabled = true;
Set<Authority> authorities = new HashSet<Authority>();
//...getters & setters
}
#Entity
#Table(name="PROFILED_USERS")
public class ProfiledUser extends User{
private static final long serialVersionUID = 1L;
//some custom attributes
private PersonalData personalData;
private ContactData contactData;
private AddressData addressData;
//...getters & setters
}
If you need to represent only one type of user, I think it should work to add attributes to the User class. However, I prefer to separate the abstract concept of user defined by the Spring Security framework from my business logic. So I'd recommend to implement your own SomethingUser and extend the User class.
A person is a person and you should have a class/table representing a person†.
A user is a user, and is different from a person (hence the two different words), and you should have a class/table representing a user.
Can a person exist without a user? Yes
Can a user exist without a person? No, a username belongs to someone.
#Entity
abstract class Party {
#Id
Long id;
String name;
#OneToMany
List<User> usernames = new ArrayList<>();
}
#Entity
class Individual extends Party {
DateTime dateOfBirth;
}
#Entity
class User {
#ManyToOne
Party party;
String username;
String password; //you better use BCrypt/Blowfish hashing!
Boolean enabled = true;
}
You could instead use a #OneToOne relationship if you only want one username per party.
† Actually you should have a more abstract class/table representing a legal party.

Resources