I had an idea to implement UUID as my primary key in the SQL database with spring boot technology. Currently, I have a dilemma on how to implement a custom fixed string with UUID as my primary key. Here is an example:
This is a classic UUID:
08d88683-20fc-4884-a523-8f39a06d037f
But I wanted to my UUID looks something like this:
USER-08d88683-20fc-4884-a523-8f39a06d037f
How could I achieve that with Spring boot and Hibernate?
Here is my user model:
#Data
#Entity
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
#UniqueConstraint(columnNames = "email")
})
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
#Type(type = "uuid-char")
private UUID id;
#NotBlank
#Size(max = 20)
private String username;
#NotBlank
#Size(max = 50)
#Email
private String email;
#NotBlank
#Size(max = 120)
private String password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
}
The best way to implement custom id generator in Hibernate.
Using the following class you can create Custom Generator:
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
import java.util.UUID;
public class CustomUserIdGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
return "USER-"+ UUID.randomUUID();
}
}
And then you can use the above created class as the generator strategy in entity class for the GenricGenerator
#Id
#GenericGenerator(name = "string_based_custom_sequence", strategy = "com.package.CustomUserIdGenerator")
#GeneratedValue(generator = "string_based_custom_sequence")
private UUID id;
Related
I have a few models that has relationship to each other within this object I am tryin to save and fetch from the database, but I can't seem to get it to work properly. The object contains a property that also contains another object. My approach was to save to all related models and for those models, I would also save this current model to them as well so it shows relation on the database table.
Below the models for the app.
#Entity
#Table(name = "daily_entry")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "daily_entry_id", unique = true)
private Long id;
#Column(unique = true)
private LocalDate date;
private int weight;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "daily_macros_id", unique = true)
private DailyMacros dailyMacros;
#OneToMany(mappedBy = "dailyEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Exercise> exercise = new ArrayList<Exercise>();
}
#Entity
#Table(name = "daily_macros")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyMacros {
#Id
#GeneratedValue
#Column(name = "daily_macros_id")
private Long id;
private int calories;
private int protein;
private int fat;
private int carbs;
#OneToOne(mappedBy = "dailyMacros", cascade = CascadeType.ALL, orphanRemoval = true)
private DailyEntry dailyEntry;
}
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name= "exercises")
public class Exercise {
#Id
#GeneratedValue
#Column(name = "exercise_id")
private Long id;
private String name;
private int sets;
private int reps;
private int weight;
#ManyToOne
#JoinColumn(name = "daily_entry_id", unique = true)
private DailyEntry dailyEntry;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
private String role = "user";
private String token;
#Column(unique = true, nullable = false)
protected String username;
#Column(unique = true ,nullable = false)
private String emailAddress;
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "macros_goal_id")
private MacrosGoal macrosGoal;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DailyEntry> dailyEntry = new ArrayList<>();
}
Im tryin to save the model of DailyEntry to the database.
User is the owner of DailyEntry
DailyEntry owns MacrosGoal (#OneToOne) and Exercise (#OneToMany). How exactly can I save and fetch this model into the database?
This is what I've tried -
For my PutMapping, I get this error : Cannot call sendError() after the response has been committed
My GetMapping just returns a empty response although http status code 200
DailyEntryController file
#RequestMapping(path = "/api/v1/")
#RequiredArgsConstructor
public class DailyEntryController {
#Autowired
DailyEntryService dailyEntryService;
#GetMapping("/getDailyEntry")
public ResponseEntity<DailyEntry> getDailyEntry(#RequestParam("username") String username, #RequestParam String date) throws ResponseStatusException {
DailyEntry dailyEntry = dailyEntryService.getDailyEntry(username, date);
return new ResponseEntity<>(dailyEntry, HttpStatus.OK);
}
#PutMapping("/addDailyEntry")
public ResponseEntity<DailyEntry> addDailyEntry(#RequestBody DailyEntry dailyEntry, #RequestParam("username") String username) throws ResponseStatusException {
DailyEntry dailyEntryInfo = dailyEntryService.createDailyEntry(dailyEntry, username);
return new ResponseEntity<>( dailyEntryInfo, HttpStatus.OK);
}
}
#Slf4j
#Service
public class DailyEntryServiceImpl implements DailyEntryService {
#Autowired
DailyEntryRepository dailyEntryRepository;
#Autowired
ExerciseRepository exerciseRepository;
#Autowired
UserRepository userRepository;
#Autowired
DailyMacrosRepository dailyMacrosRepository;
#Override
public DailyEntry addExercise(Exercise exercise) {
return null;
}
#Override
public DailyEntry getDailyEntry(String username, String date) {
DailyEntry entry = null;
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
LocalDate localDate = LocalDate.parse(date);
List<DailyEntry> dailyEntryList = user.getDailyEntry();
for(DailyEntry e : dailyEntryList) {
if(e.getDate() == localDate){
entry = e;
}
}
return entry;
}
#Override
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
DailyEntry entry = new DailyEntry();
// Find user
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
entry.setUser(user);
entry.setDate(LocalDate.parse(dailyEntry.getDate().toString()));
entry.setDailyMacros(dailyEntry.getDailyMacros());
entry.setWeight(dailyEntry.getWeight());
entry.setExercise(dailyEntry.getExercise());
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(entry);
dailyMacrosRepository.save(dailyMacros);
List<Exercise> exercise = dailyEntry.getExercise();
for (Exercise e : exercise) {
e.setDailyEntry(entry);
exerciseRepository.save(e);
}
List<DailyEntry> dailyEntryList = user.getDailyEntry();
dailyEntryList.add(entry);
user.setDailyEntry(dailyEntryList);
userRepository.save(user);
return entry;
}
I also tried with Query inside repository, but I may have done it wrong.
You should probably add #Transactional to createDailyEntry.
I don't really understand what's happening in createDailyEntry but I think it should look something like this:
#Override
#Transactional
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
User user = userRepository
.findByUsername(username)
.get();
dailyEntryRepository.save(dailyEntry);
user.getDailyEntry().add(dailyEntry);
dailyEntry.setUser(User);
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(dailyEntry);
dailyMacrosRepository.save(dailyMacros);
dailyEntry.getExcercise()
.forEach( e -> {
e.setDailyEntry(dailyEntry);
exerciseRepository.save(e);
});
// I don't think you need this because user is already managed
// and userFromDatabase.get() will throw an exception
// if the user does not exist.
// userRepository.save(user);
return dailyEntry;
}
I think getDailyEntry doesn't work because you are using e.getDate() == localDate and it's always going to be false.
One solution is to add a method to the DailyEntryRepository that accept username and date:
interface DailyEntryRepository extends JpaRepository<DailyEntry, Long> {
#Query("select de from DailyEntry de where de.user.username = :username and de.date = :localDate")
DailyEntry findByUsernameAndDate(#Param("username") String username, #Param("localDate") LocalDate date);
}
...
#Override
public DailyEntry getDailyEntry(String username, String date) {
LocalDate localDate = LocalDate.parse(date);
return dailyEntryRepository.findByUsernameAndDate(username, localdate);
}
This solution will run a query and load only the entry you need.
But, if you need to validate the username, this should work:
#Override
public DailyEntry getDailyEntry(String username, String date) {
User user = userRepository
.findByUsername(username)
.get();
LocalDate localDate = LocalDate.parse(date);
for(DailyEntry e : user.getDailyEntry()) {
if (localDate.equals(e.getDate)){
// No need to continue the loop, you've found the entry
return entry;
}
}
// daily entry not found
return null;
}
Another thing that's missing is that in your model you are not handling the bidirectional association during convertion to JSON.
The solution is to use I end up using #JsonManagedReference and #JsonBackReference where you have the association.
I am using spring boot application. The application has one POST endpoint. There are many bidirectional entities in the application.
I tried to save the Questionnaire (one of the entity ) I get the following error on the POSTMAN.
{ "message": "Content type 'application/json;charset=UTF-8' not supported",
"httpStatus": "INTERNAL_SERVER_ERROR"}
And on the application console I get the following error.
[nio-8080-exec-2] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.QuestionnaireDTORequest]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (`java.util.Set<com.entity.Questionnaire>`) not compatible with managed type (com.entity.Questionnaire)
The Questionnaire entity is as follows:
public class Questionnaire {
#Id
#Column(name = "id")
#GeneratedValue
private UUID id;
#Column(name = "description")
#NotNull
private String description;
#Column(name = "created_date")
#NotNull
private LocalDate createdDate;
#Column(name = "approval_status")
#NotNull
private String approvalStatus;
#Column(name = "version")
#NotNull
private String questionnaireVersion;
#Column(name = "is_active")
#NotNull
private boolean isActive = false;
#JsonManagedReference
#ManyToMany
#JoinTable( name = "questionnaire_question",
joinColumns = #JoinColumn(name = "questionnaire_id"),
inverseJoinColumns = #JoinColumn(name = "question_id"))
private Set<Question> questionSet = new HashSet<>();
#OneToMany(mappedBy = "questionnaire")
private Set<AnsweredQuestionnaire> answeredQuestionnaireSet = new HashSet<>();
}
public class AnsweredQuestionnaire {
#Id
#Column(name = "id")
#GeneratedValue
private UUID id;
#Column(name = "comment")
private String comment;
#ManyToOne
#JoinColumn(name = "questionnaire_id")
private Questionnaire questionnaire;
#OneToOne
private Process process;
#ManyToMany
#JoinTable( name = "answer",
joinColumns = #JoinColumn(name = "answered_questionnaire_id"),
inverseJoinColumns = #JoinColumn(name = "possible_answer_id"))
private Set<PossibleAnswer> possibleAnswerSet = new HashSet<>();
}
public class Question {
#Id
#Column(name = "id")
#GeneratedValue
private UUID id;
#Column(name = "text")
#NotNull
private String text;
#Column(name = "weight")
#NotNull
private Integer weight;
#Column(name = "minimal_bcm_class")
private Integer minimalBcmClass;
#Column(name = "comment")
private String comment;
#JsonBackReference
#ManyToMany(mappedBy = "questionSet")
private Set<Questionnaire> questionnaireSet = new HashSet<>();
#ManyToMany(mappedBy = "questionSet")
private Set<PossibleAnswer> possibleAnswerSet = new HashSet<>();
}
I am not sure what is causing this error, any suggestion will be helpful.
#PostMapping(
produces = MediaType.APPLICATION_JSON_VALUE ,
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE}
)
public String createQuestionnaire(#RequestBody QuestionnaireDTORequest questionnaireDTORequest){
Questionnaire questionnaire = mapToQuestionnaire(questionnaireDTORequest);
Questionnaire createdQuestionnaire = questionnaireService.createQuestionnaire(questionnaire);
if(createdQuestionnaire != null)
return "Questionnaire created successfully";
else
return "Questionnaire cannot be created";
}
The POSTMAN request is as follows
{
"description": "Third questionnaire",
"createdDate": "2022-06-23",
"approvalStatus": "Approved",
"questionnaireVersion": "V1",
"isActive": false,
"questionSet": [
{
"text": "Question text",
"possibleAnswerSet": []
},
{
"text": "Question text",
"possibleAnswerSet": []
}
]
}
Please let me know if you need any other information.
I've been trying to set up inheritance in my Spring boot project, but I failed to do so. I've tried using the superclass mappings, joined table, single table but still I think I'm missing something. Here is how the classes look like:
Person class:
#Entity
#MappedSuperclass
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_person", nullable = false)
private Integer id;
#Column(name = "first_name", nullable = false, length = 200)
private String firstName;
#Column(name = "last_name", nullable = false, length = 200)
private String lastName;
#Column(name = "PESEL", nullable = false)
private Integer pesel;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "person")
private Driver driver;
//setters and getters below
}
Driver class:
#Entity
#Table(name = "driver")
public class Driver {
#Id
#Column(name = "id_driver", nullable = false)
private Integer id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_driver")
private Person person;
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "license", nullable = false, length = 200)
private String license;
#OneToMany(mappedBy = "idDriver")
private Set<Ride> rides = new LinkedHashSet<>();
//setters and getters below
}
Here's how it is joined in the database (mysql):
I would be very thankful if you could at least point me in the right direction (like which inheritance type will suit this simple case the best)
First remove the #Entity and #Table annotations, as well as the relationship, from your superclass:
#MappedSuperclass
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_person", nullable = false)
private Integer id;
#Column(name = "first_name", nullable = false, length = 200)
private String firstName;
#Column(name = "last_name", nullable = false, length = 200)
private String lastName;
#Column(name = "PESEL", nullable = false)
private Integer pesel;
//setters and getters below
}
Secondly, remove the duplicated id column and remove the relationship between the mapped superclass and your derived classes, and actually extend the superclass:
#Entity
#Table(name = "driver")
public class Driver extends Person{
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "license", nullable = false, length = 200)
private String license;
#OneToMany(mappedBy = "idDriver")
private Set<Ride> rides = new LinkedHashSet<>();
//setters and getters below
}
I have three entity include bridge entity:
Team Entity:
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private Integer id;
#Column(name = "teamname", length = 128, nullable = false, unique = true)
private String teamname;
#Column(name = "delete_date", length = 128, nullable = true)
private Date delete_date;
#Column(name = "description", nullable = true, length = 240)
private String description;
#Column(name = "active", length = 64, nullable = false)
private int active;
#OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
---getter setter constructur
}
User Entity:
#Entity
#Table(name = "tblUsers")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "Username", length = 128, nullable = false, unique = true)
private String username;
#Column(name = "FirstName", nullable = false, length = 45)
private String firstName;
#Column(name = "LastName", nullable = false, length = 45)
private String lastName;
#Column(name = "Password", length = 64, nullable = false)
private String password;
#Column(name = "Email", length = 128, nullable = false, unique = true)
private String email;
#Column(name = "Phone", length = 64, nullable = false, unique = true)
private String phoneNumber;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
---getter setter constructur
}
TeamUsers - Bridge Entity with extra column(active):
#Entity
#Table(name = "team_users")
public class TeamUsers implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "TEAM_ID")
private Team team;
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#Column(name = "active")
private Integer active;
---getter setter constructur
}
In the Team repository I have code:
package com.crmbackend.allService.teamService.repo;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.crmbackend.entity.Team;
public interface TeamRepository extends PagingAndSortingRepository<Team, Integer> {
#Query("select t from Team t")
public List<Team> getAllTeamAndDetails();
}
If I call the getAllTeamAndDetails() method in Junit Test, the result is all team informations:
It basically tells me how many team I have, and team users object who belong to which team.
Now, my question is which I want to get all team information and team user information,
but only their active = 1 in the bridge table.
which means if Team User record has active = 0, then this user should not showing in the result.
How this query should be looks like or what is the best approach?
Thanks
This is not possible with the plain JPA/Hibernate or Spring Data JPA tools available. You have to use a DTO for this purpose. 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)
public interface TeamDto {
#IdMapping
Integer getId();
String getDescription();
#Mapping("team_users[active = 1].user")
Set<UserDto> getUsers();
#EntityView(User.class)
interface UserDto {
#IdMapping
Integer getId();
String getUsername();
}
}
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!
I have a User model that contains a list of achievements
#Table(name = "user")
#Entity
#NamedEntityGraph(name = "User.achievements",
attributeNodes={
#NamedAttributeNode("achievements")
})
#Data
public class User {
#Id
#NotNull
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ElementCollection(fetch = FetchType.LAZY, targetClass = Achievement.class)
private List<Achievement> achievements = new ArrayList<>();
}
Here's the achievement model
#Entity
#Data
#Table(name = "achievement")
public class Achievement {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String achievementId;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "achieved", columnDefinition="BOOLEAN DEFAULT false", nullable = false)
private boolean achieved = false;
user_achievements table generated from #ElementCollection mapping, which atm only contains user and achievement foreign keys
I am looking to move the boolean achieved value to the user_achievements table, ideally without having to create a separate model User_Achievements
I am fairly new to using Jpa, but i feel like this scenario is too basic so there must be a straight forward way to do that i cant seem to locate it
#Entity
class UserAchievement {
#EmbeddableId
UserAchievementId id;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="user_username", insertable=false, updatable=false)
User user;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="achivement_achivement_id", insertable=false, updatable=false)
Achivement achivement;
// and other fields
}
class User {
// ...
#OneToMany(mappedBy="user")
List<UserAchievement> userAchievements;
}
and you need to define UserAchievementId