Spring, Question regarding Query on Many-to-Many(Association with Extra Columns) in web development - spring

I have 3 class that using Many to Many relationship in Entity level (herbibate)
They are Teams,Users and TeamUsers.
Since I am using hibernate they are all Fetch Lazy
Team:
Users:
TeamUsers:
and we have 3 interface (using by #Autowired in Service or repo layer) which is:
1.TeamRepository extends PagingAndSortingRepository<Team, Integer>
2.UserRepository extends PagingAndSortingRepository<User, Integer>
3.TeamUserRepository extends PagingAndSortingRepository<TeamUsers, Integer>
And here is the core code for many to many relationship mapping:
Team class:
#OneToMany(mappedBy = "team", fetch = FetchType.EAGER)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
User class:
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
TeamUsers class:
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "TEAM_ID")
private Team team;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "USER_ID")
private User user;
Now, my questions is, what is the best practice to play around this data?
for example:
1. I need all team information and team member from my DB
In restful controller get mapping function, what should I return? should I use TeamUser repository (or maybe one more service layer) to return findAll()? if do so the data will come as object collection of User and Team right(maybe also contain extra column in that table)? Can Json successfully contain that data?
2. I am on webpage and I want to delete a user from a team.
At this timepoint I know the TeamID and UserID from HTML, when I send the Post request to API, should I get User object by UserID, then get Team object by TeamID, then get(Query) TeamUsers object by giving User object and Team Object? or maybe just query by id(from TeamUsers on html) send to API and simply remove this TeamUsers entity?
I'm new to spring and frontend development and I am much apprecaite for your help!

I would suggest the following
If you want to return all the teams and for each team all its users, you should have a TeamService which call findAll method of Team repository this way you will get a list of Teams and for each team a set with its users. And if instead of that you want all of your users and for each one what are its teams you should do the other way, call the findAll method of User repository
Both solutions will serialize to json without problem as long as the objects have its constructor and getter and setter methods
If you want to delete only the relation between user and team you could have a method in your TeamEntity Spring Data repository like this
long deleteByTeamIdAndUserId(long teamId, long userId);
And spring will create a method to perform this action or you can write the query you like just above the method name to be more specific
#Modifying
#Query("delete from TeamUsers t where t.userId=:userId and t.teamId = :teamId")
long deleteByTeamIdAndUserId(long teamId, long userId);

Related

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

architecture microservice spring boot

I am working with Spring cloud (microservices) and I have implemented security with JWT token.
in my security application, I have entities like User, Role and UserRole.
so Every request first comes to the ZOOL service and it calls Authentication service and Authentication service creates/returns JWT token.
Also, I have another microservice-rest application (Questions-app) that needs JWT token.
in the Questions-app I have a Question entity that contains authorId field.
#Entity
#Table(name="QUESTION")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", updatable = false, nullable = false)
private long id;
#Column(name = "AUTHOR")
private long authorId;
#Column(name = "TITLE")
private String title;
}
Now, it is not clear for me, is it right to set authorId long type or I should create User, Role, UserRole entities (just simple copy from AUTH project) in the questions-app and set "AUTHOR" column like that
#OneToOne
#JoinColumn(name="AUTHOR")
private User user;
I know that in the first option when I need show question and user's name on the webpage, then I should call 2 services (one from question-app (fetch question) and another from auth service (fetch user information by author id)
I would like to know what is the best practises?
If you have common database for all of these microservices and you need User related information based on question id.
Then instead of doing another database call for user, you can directly do #OneToOne to User.
From your question it is better to go for 2 option.

Spring Boot many to many post method not updating data

My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);

Pattern for accessing data outside of transaction

I have a Spring Boot App with Spring Data JPA with hibernate and MySQL as the data store.
I have 3 layers in my application:
API Service
Application Service
Domain Service ( with Repository)
The role of Application Service is to convert hibernate-backed POJOs to DTOs given some business logic.
POJO
SchoolClass.java
#Column
Long id;
#Column
String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "schoolClass")
List<Book> books;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "schoolClass")
List<Student> students;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "schoolClass")
List<Schedule> schedules;
Domain Service - My transaction boundary is at the Domain Service layer.
SchoolClassService.java
#Autowired
private SchoolClassRepository repository;
#Transactional(readOnly = true)
public SchoolClass getClassById(Long id) {
return repository.findById(id);
}
Application Service
SchoolClassAppService.java
#Autowired
private SchoolClassService domainService;
public SchoolClassDto getClassById(Long id) {
SchoolClass schoolClass = domainService.getClassById(id);
// convert POJO to DTO;
return SchoolClassDto;
}
My problem is that at times the child entities on SchoolClass are empty when I try to access them in SchoolClassAppService. Not all of them, but out of the three, two would work fine but the third one would be empty. I tried to mark the children lists to be eagerly fetched, but apparently only two collections can be eagerly fetched before Hibernate starts throwing exceptions and it also does not sound like good practice to always load all the objects. I do not get LazyInitializationException, just the list is empty.
I have tried to just call the getter on all lists in the domain service method before returning it just to load all data for the POJO but that does not seem like a clean practice.
Are there any patterns available which keep the transaction boundaries as close to the persistence layer as possible while still make it viable to process the data even after the transaction has been closed?
Not sure why your collections are sometimes empty, but maybe that just how the data is?
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for your example could look like this
#EntityView(SchoolClass.class)
interface SchoolClassDto {
String getName();
List<BookDto> getBooks();
}
#EntityView(Book.class)
interface BookDto {
// Whatever data you need from Book
}
Querying could look like this
List<SchoolClassDto> dtos = entityViewManager.applySetting(
EntityViewSetting.create(SchoolClassDto.class),
criteriaBuilderFactory.create(em, SchoolClass.class)
).getResultList();
Just keep in mind that DTOs shouldn't just be copies your entities but should be designed to fit your specific use case.

I don't understand why Hibernate is creating 2 sessions?

I've been scratching my head for a while and thought I'd get some help :)
I'm working with a legacy database which I cannot change. I have the following domain:
#Entity
public class Institution {
#Id
private Long id;
#OneToMany(mappedBy="institution", fetch=FetchType.EAGER)
private List<Subscription> subscriptions = new ArrayList<Subscription>();
}
#Entity
public class Subscription {
#Id
private Long id;
#ManyToOne
#JoinColumn(name="sub_id", referencedColumnName="ins_sub_id", insertable=false, updatable=false)
private Institution institution;
}
For the sake of brevity I have excluded showing the getters/setters. There is no join table.
So;
1) Is the mapping correct? I want a bidirectional association and I want the Institution to be the owner of the relationship.
2) If I load a Institution, create a new Subscription() and add the subscription to the subscriptions collection...
#RequestMapping(value="/add/{institutionId}", method=RequestMethod.POST)
public String submitSubscriptionForm(#ModelAttribute SubscriptionForm form) {
Institution institution = institutionService.getById(form.getInstitutionId());
Subscription subscription = new Subscription();
//...set properties on subscripton from data in the form
subscription.setInstitution(institution);
institution.getSubscriptions().add(subscription);
institutionService.saveOrUpdateInstitution(institution);
}
...when I save the Institution...
institutionService.saveOrUpdateInstitution(institution); just delegates to a DAO which extends HibernateDaoSupport.
...I get the following error:
org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions; nested exception is org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:679)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:411)
at org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374)
at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:737)
at com.f1000.dao.hibernate.InstitutionDaoImpl.saveOrUpdate(InstitutionDaoImpl.java:161)
I'm using the Spring and am making use of the OpenSessionInViewFilter and I can't figure out why a second session is created?
There is an issue in your association,
#ManyToOne
#JoinColumn(name="sub_id", referencedColumnName="ins_sub_id")
private Institution institution;
The is a insertable=false, updatable=false set on your institution inside the Subscription. Either you need to remove it or create a new property as below an set that, to the new Subscriptions.
private Long institutionId;
and replace subscription.setInstitution(institution); this by,
subscription.setInstitutionId(institution.getId());
Read here on more about the insertable=false, updatable=false mappings.

Resources