How to Map a dto to an entity with avoiding complexs objects - java

I try to map dto to an entity with avoiding complex objects but I have a null exception when I try to save my article entity??
in my ArticleRequest instead of using
Department & ArticleCategory as a full object I just put their ids (uid) .
Here is my ArticleRequest:
#Slf4j
#Builder
#Data
public class ArticleRequest {
private Long id;
#Size(min = 32, message = "uid should have at least 32 characters")
private String uid;
#Size(min = 2, message = "title should have at least 2 characters")
private String title;
#NotBlank(message = "content should not be empty value")
private String content;
#NotNull(message = "article category id should not be null value")
private String articleCategoryUid;
#NotNull(message = "department id should not be empty value")
private String departmentUid;
#JsonIgnore
private List<ArticleVote> articleVoteList;
public static ArticleRequest fromEntity(Article article) {
if (article == null) {
log.warn("Class: ArticleRequest || Method: fromEntity() || Error: article is null!!!");
return null;
}
return ArticleRequest.builder()
.id(article.getId())
.uid(article.getUid())
.title(article.getTitle())
.content(article.getContent())
.articleCategoryUid(article.getArticleCategory().getUid())
.departmentUid(article.getDepartment().getUid())
.build();
}
public static Article toEntity(ArticleRequest articleRequest) {
if (articleRequest == null) {
log.warn("Class: ArticleRequest || Method: toEntity() || Error: articleRequest is null!!!");
return null;
}
Article article = new Article();
article.setId(articleRequest.getId());
article.setUid(articleRequest.getUid());
article.setTitle(articleRequest.getTitle());
article.setContent(articleRequest.getContent());
article.getArticleCategory().setUid(articleRequest.getArticleCategoryUid()); // i have null exeption here !! because ArticleCategory already null
article.getDepartment().setUid(articleRequest.getDepartmentUid()); // i have null exeption here !! because Department already null
return article;
}
}
here is my baseEntity
#Data
#NoArgsConstructor
#AllArgsConstructor
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
//region Simple Properties
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Long id;
#NotNull
#Size(min = 32, message = "uid should have at least 32 characters")
#Column(name = "uid", nullable = true,updatable = false)
private String uid;
//endregion
}
here is my article category class
#Data
#Entity
#Table(name = "article_categories", schema = "public")
public class ArticleCategory extends BaseEntity {
//region Simple Properties
#NotNull
#Size(min = 2, message = "name should have at least 2 characters")
#Column(name = "name")
private String name;
#NotNull
#Size(min = 2, message = "slug should have at least 2 characters")
#Column(name = "slug")
private String slug;
#NotNull
#Size(min = 2, message = "description should have at least 2 characters")
#Column(name = "description")
private String description;
//endregion
//region Complex Properties
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "articleCategory", orphanRemoval = true)
private List<Article> articleList;
//endregion}
here is my department class
#Data
#Entity
#Table(name = "departments", schema = "public")
public class Department extends BaseEntity {
//region Simple Properties
#Size(min = 2,message = "name should have at least 2 characters")
#Column(name = "name")
private String name;
#Enumerated(EnumType.STRING)
#Column(name = "status")
private DepartmentStatusEnum status;
//endregion
//region Complex Properties
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
private List<Article> articleList;
}
and here is my service
#Override
public ArticleRequest saveArticle(ArticleRequest articleRequest) {
if (articleRequest == null) {
throw new InvalidEntityException(CustomErrorMessage.ARTICLE_CAN_NOT_BE_NULL.getMessage(), CustomErrorCodes.ARTICLE_NOT_VALID);
}
articleRequest.setUid(UUID.randomUUID().toString());
Article saveArticle=ArticleRequest.toEntity(articleRequest);// null exception
Article newArticle = articleRepository.save(saveArticle);
ArticleRequest newArticleRequest = ArticleRequest.fromEntity(newArticle);
return newArticleRequest;
}
so how I can save my article entity & pass uid of articleCategory and department with the right way!
thanks in advance.

Informations given dont give a great idea , you should past the class Department and Article in your question
Probably your problem is here :
article.getArticleCategory().setUid(articleRequest.getUid());
article.getDepartment().setUid(articleRequest.getUid());
You should set the ArticleCategory and the Department of your article , creating a new objtects and setting them .
I think the solution is replacing these lines with :
article.setArticleCategory(new ArticleCategory());
article.getArticleCategory().setUid(articleRequest.getArticleCategoryUid());
article.setDepartment(new Department());
article.getDepartment().setUid(articleRequest.getDepartmentUid());
TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
You should save or persist the Properties values in the database ( instances ) before the persistance of the Article object, so the code will be like that :
ArticleCategory ac = new ArticleCategory();
ac.setUid(articleRequest.getArticleCategoryUid());// if you need you can set the other properties of the ac , get them from the articleRequest
articleCategoryRepository.save(ac);
Department dep = new Department();
dep.setUid(articleRequest.getDepartmentUid());
departmentRepository.save(dep);
article.setArticleCategory(ac);
article.setDepartment(dep);
articleRepository.save(article);

article.getArticleCategory() //this gives you the NPE
Initialize articleCategory before you call the getter method of it.
E.g:
article.setArticleCategory(new ArticleCategory());
Its the same for department. Initialize department object before you call getDepartment()

Related

How to prevent duplicate entries Spring Jpa

I'm learning how Spring framework works and as an example I'm trying to save cities and countries which users can log using the API endpoints. However, I can't figure out how to prevent duplicate entries.
For example I'm adding 2 cities in a country using the endpoint (photo below) but in the Country table I get duplicate values. How can I prevent duplicate values ? Thanks in advance.
#Getter
#Setter
#Entity
#Table(name = "COUNTRY")
public class CntCountry {
#Id
#SequenceGenerator(name = "CntCountry", sequenceName = "CNT_COUNTRY_ID_SEQ")
#GeneratedValue(generator = "CntCountry")
private Long id;
#Column(name = "COUNTRY_NAME", length = 30, nullable = false)
private String countryName;
#Column(name = "COUNTRY_CODE", length = 30, nullable = false)
private String countryCode;
}
#Getter
#Setter
#Table(name = "CITY")
#Entity
public class CtyCity {
#Id
#SequenceGenerator(name = "CtyCity", sequenceName = "CTY_CITY_ID_SEQ")
#GeneratedValue(generator = "CtyCity")
private Long id;
#Column(name = "CITY_NAME", length = 30, nullable = false)
private String cityName;
#Column(name = "PLATE_NUMBER", length = 30, nullable = false)
private Long plateNumber;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "FK_COUNTRY")
private CntCountry country;
}
EDIT:
#PostMapping("/city")
public ResponseEntity<CtyCityDto> save(#RequestBody CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCityDto ctyCityDto = ctyCityService.save(ctyCitySaveRequestDto);
return ResponseEntity.ok(ctyCityDto);
}
#Service
#AllArgsConstructor
public class CtyCityService {
private CtyCityDao ctyCityDao;
public CtyCityDto save(CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCity ctyCity = CtyCityMapper.INSTANCE.convertToCtyCity(ctyCitySaveRequestDto);
ctyCity = ctyCityDao.save(ctyCity);
CtyCityDto ctyCityDto = CtyCityMapper.INSTANCE.convertToCtyCityDto(ctyCity);
return ctyCityDto;
}
}
public interface CtyCityDao extends JpaRepository<CtyCity,Long> {
}
#Data
public class CtyCityDto {
private Long id;
private String cityName;
private Long plateNumber;
private CntCountry country;
}
I'm not really following your naming conventions, and I think your DTO classes are just complicating things for you at this point... But in general terms, because the entities you're sending have no id value associated with them, JPA assumes they are different objects and adds them to the database with new id's because it hasn't been told anywhere that similar items might in fact be the same object, it needs to be told.
I can think of 2 ways to prevent entity duplication in your database.
1. The easiest way would be to set your Country and City names (or other attributes) to be "unique", you can do this in your entity classes simply by adding unique = true to the column data on the item you wish to be unique.
//In Country.java
#Column(name = "COUNTRY_NAME", length = 30, nullable = false, unique = true)
private String countryName;
//In City.java
#Column(name = "CITY_NAME", length = 30, nullable = false, unique = true)
private String cityName;
Although, you will then need to handle exceptions thrown if a duplicate is provided, in Spring Boot the best way to handle this is with a #ControllerAdvice but that's another subject.
2. Check if the entity exists by name or some other value. A common approach might be something like the following:
//In your service
public Object saveCountry(Country country){
Country existingCountry = countryRepository.findByName(country.getName()).orElse(null);
if(existingCountry == null){
//Country does not already exist so save the new Country
return countryRepository.save(country);
}
//The Country was found by name, so don't add a duplicate
else return "A Country with that name already exists";
}
//In your Country repository
Optional<Country> findByName(countryName);
In case my answer doesn't make sense, I have thrown together an example following my first suggestion (using the unique column attribute and a controller advice) which you can view/clone from here

"Provided id of the wrong type for" error when using save() method of an #Embeddable class

I am still working on my very first solo spring boot project. It is suppose to be a Rest API using the MariaDB example database Nation. There is the country_languages table which receives two foreign keys that also are the primary keys and has another regular field. First foreign key is the id from countries table and the second one is the id from languages table. When I use the save() method in order to create a new tuple I get this error:
org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer; nested exception is java.lang.IllegalArgumentException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer
This is the country_languages table from the MariaDB example:
create table country_languages(
country_id int,
language_id int,
official boolean not null,
primary key (country_id, language_id),
foreign key(country_id)
references countries(country_id),
foreign key(language_id)
references languages(language_id)
);
I am using an #Embeddable class CountryLanguagesId in order to make a composite key as I found in this reference.
#Embeddable
public class CountryLanguagesId implements Serializable {
#Column(name = "country_id")
private Integer countryId;
#Column(name = "language_id")
private Integer languageId;
public CountryLanguagesId() {
}
public CountryLanguagesId(Integer countryId, Integer languageId) {
this.countryId = countryId;
this.languageId = languageId;
}
// + getters and setters
After that I created the entity for the country_languages table and its repository:
#Entity
#Table(name = "country_languages")
public class CountryLanguages {
#EmbeddedId
CountryLanguagesId countryLanguagesId = new CountryLanguagesId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("countryId")
#JoinColumn(name = "country_id")
private Countries countries;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("languageId")
#JoinColumn(name = "language_id")
private Languages languages;
#Column(name = "official", length = 1, nullable = false)
private Integer official;
public CountryLanguages() {
}
public CountryLanguages(Countries country, Languages language, Integer official) {
this.countries = country;
this.languages = language;
this.official = official;
}
// + getters and setters
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, Integer> {
}
There are the countries and languages entities:
#Entity
#Table(name = "countries")
public class Countries {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id", length = 11, nullable = false)
private Integer countryId;
#Column(name = "name", length = 50, nullable = true)
private String name;
#Column(name = "area", nullable = false)
private BigDecimal area;
#Column(name = "national_day", nullable = true)
private java.sql.Date nationalDay;
#Column(name = "country_code2", length = 2, nullable = false)
private String countryCode2;
#Column(name = "country_code3", length = 3, nullable = false)
private String countryCode3;
#OneToMany(mappedBy = "countries", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Countries() {
}
public Countries(String name, BigDecimal area, Date nationalDay, String countryCode2, String countryCode3) {
this.name = name;
this.area = area;
this.nationalDay = nationalDay;
this.countryCode2 = countryCode2;
this.countryCode3 = countryCode3;
}
#Entity
#Table(name = "languages")
public class Languages {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", length = 11, nullable = false)
private Integer languageId;
#Column(name = "language", length = 50, nullable = false)
private String language;
#OneToMany(mappedBy = "languages", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Languages() {
}
public Languages(String language) {
this.language = language;
}
public Integer getLanguageId() {
return languageId;
}
These are the entries I do when get the error:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class ICountryLanguagesJpaRepositoryTest {
#Autowired
private ICountriesJpaRepository countries;
#Autowired
private ILanguagesJpaRepository languages;
#Autowired
private ICountryLanguagesJpaRepository repository;
#Test
public void shouldSaveAndRemoveContinents() {
Countries patu = new Countries("Patu", new BigDecimal(67822.34), new Date(12321233232L), "PU", "PTU");
countries.save(patu);
Languages patuano = new Languages("Patuano");
languages.save(patuano);
CountryLanguages pLanguages = new CountryLanguages(patu, patuano, 0);
repository.save(pLanguages);
assertEquals(1, repository.findAll().size());
System.out.println(repository.findAll());
repository.deleteById(1);
assertEquals(0, repository.findAll().size());
}
I am doing this using a H2 database. Here is the complete debug console output. Sorry but cant paste it here due characters limitation.
Thanks!
Your repository definition is wrong. You should specify the embeddable type as primary key type:
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, CountryLanguagesId> {
}

Spring Boot Data JPA detached entity passed to persist on #OneToMany and #OneToOne

Problem
I am trying to store an object in my Postgres database. This consists of the Order.class, (List) OrderDevice.class, and a Department.class.
The important thing is that the OrderDevices are always stored new in the DB, but a Department may already exist.
When I try to save the object to my database using save I get the following error message: (shown below)
I get the error message "detached entity passed to persist: com.niclas.model.OrderDevice" if the department does not exist yet, if the department exists the error message looks like this: "detached entity passed to persist: com.niclas.model.Department".
Solution attempts
This solution cannot be used because I do not use bidirectional mapping.
(I don't want to use a bidirectional mapping because I want to access the departments without an order.)
I also tried to change the Cascade types to MERGE like in this solution
I also tried using #Transactional on the method
I also tried to save the children in the database first and then the parent like this:
departmentRepository.save(order.getDepartment()); orderDeviceRepository.saveAll(order.getDevices()); orderRepository.save(order);
I hope I have described my good enough and I am happy about suggestions for solutions
Error.log
The log can be viewed here. (The formatting did not work here)
Order.class
#Entity
#Table(name = "orders")
public class Order extends AuditModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "order_id")
private String orderId;
#Column(name = "department_id")
private long departmentId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "department", referencedColumnName = "id")
private Department department;
#JsonProperty("deviceList")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "order_id")
private List<OrderDevice> devices;
#JsonProperty("forename")
#Column(name = "sender_forename")
private String senderForename;
#JsonProperty("surname")
#Column(name = "sender_surname")
private String senderSurname;
#Column(name = "notes", columnDefinition = "TEXT")
private String notes;
#Column(name = "month")
private int month;
#Column(name = "year")
private int year;
public Order() {
}
... Getter/Setters
}
OrderDevice.class
#Entity
#Table(name = "order_devices")
public class OrderDevice extends AuditModel{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column( name = "order_id", insertable = false, updatable = false )
private String orderId;
#Column(name = "device_id")
private long deviceId;
#Column(name = "device_name")
private String deviceName;
#Column(name = "priceName")
private String priceName;
#Column(name = "price")
private double price;
#Column(name = "count")
private int count;
public OrderDevice() {
}
... Getters/Setters
}
Department.class
#Entity
#Table(name = "departments")
public class Department {
//TODO add Form Validation
//TODO better Naming for From Attributes on Frontend and Backend
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "department_name")
private String department;
#Column(name = "contact_person_forename")
private String forename;
#Column(name = "contact_person_surname")
private String surname;
#Column(name = "contact_person_mail")
private String mail;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private String houseNumber;
#Column(name = "location")
private String location;
#Column(name = "postal_code")
private int postalCode;
#Column(name = "country")
private String country;
#Column(name = "auto_send_invoice")
private boolean autoSend;
#Column(name = "registered")
private boolean registered;
public Department() {
}
... Getter/Setters
}
OrderController.class
#Slf4j
#RestController
public class OrderController {
private final DepartmentRepository departmentRepository;
private final OrderRepository orderRepository;
private final OrderDeviceRepository orderDeviceRepository;
public OrderController(OrderRepository orderRepository, DepartmentRepository departmentRepository,
OrderDeviceRepository orderDeviceRepository) {
this.orderRepository = orderRepository;
this.departmentRepository = departmentRepository;
this.orderDeviceRepository = orderDeviceRepository;
}
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody Order order) throws JsonProcessingException {
order.setOrderId(order.generateOrderId());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
order.getDevices().forEach(orderDevice -> {
orderDevice.setOrderId(order.getOrderId());
});
//departmentRepository.save(order.getDepartment());
//orderDeviceRepository.saveAll(order.getDevices());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
Update
If the objects are created in this way, no error will occur and the order will be successfully saved in the database.
However, I don't understand why it works this way and not via ObjectMapper. Does anyone know why?
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody JsonNode jsonNode) throws JsonProcessingException {
Order order = new Order();
JsonNode departmentJson = jsonNode.get("department");
Department department;
if ( departmentJson.get("id").canConvertToInt() ) {
department = departmentRepository.findDepartmentById(departmentJson.get("id").asInt());
} else {
department = new Department();
department.setDepartment(departmentJson.get("department").asText());
department.setForename(departmentJson.get("forename").asText());
department.setSurname(departmentJson.get("surname").asText());
department.setMail(departmentJson.get("mail").asText());
department.setStreet(departmentJson.get("street").asText());
department.setHouseNumber(departmentJson.get("houseNumber").asText());
department.setLocation(departmentJson.get("location").asText());
department.setPostalCode(departmentJson.get("postalCode").asInt());
department.setCountry(departmentJson.get("country").asText());
department.setAutoSend(departmentJson.get("autoSend").asBoolean());
department.setRegistered(departmentJson.get("registered").asBoolean());
}
order.setDepartment(department);
order.setOrderId(order.generateOrderId());
order.setDepartmentId(department.getId());
List<OrderDevice> orderDevices = new ArrayList<>();
JsonNode devices = jsonNode.get("deviceList");
for (JsonNode node : devices) {
//TODO replace this mess with objectMapper
if (node.has("count") && node.get("count").asInt() != 0){
OrderDevice device = new OrderDevice();
device.setOrderId(order.getOrderId());
device.setDeviceId(node.get("id").asLong());
device.setDeviceName(node.get("deviceName").asText());
device.setPriceName(node.get("priceName").asText());
device.setPrice(node.get("price").asDouble());
device.setCount(node.get("count").asInt());
orderDevices.add(device);
}
}
order.setDevices(orderDevices);
order.setSenderForename(jsonNode.get("forename").asText());
order.setSenderSurname(jsonNode.get("surname").asText());
order.setNotes(jsonNode.get("notes").asText());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
You can try to use instead of orderRepository.save(order) use orderRespostiory.saveOrUpdate(order).

Hibernate persists only first nested entity

My problem is that Hibernate does not persist nested entities given in entity.
Consider following entities:
PollEntity
#Table(name = "\"poll\"")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#EqualsAndHashCode(of = "id")
#Builder
public class PollEntity {
#Transient
public OptionEntity addOption(OptionEntity pollOptionEntity) {
if(options == null)
options = new HashSet<>();
options.add(pollOptionEntity);
pollOptionEntity.setPoll(this);
return pollOptionEntity;
}
#Transient
public OptionEntity dropOption(OptionEntity pollOptionEntity) {
options.remove(pollOptionEntity);
pollOptionEntity.setPoll(null);
return pollOptionEntity;
}
#Id
#Column(name = "id")
private UUID id;
#Column(name = "author")
#UUIDv4
private UUID author;
#Column(name = "poll_question")
#Size(max = 1000)
private String pollQuestion;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "poll", cascade = CascadeType.MERGE)
#Builder.Default
#Valid
private Set<OptionEntity> options;
}
OptionEntity
#Table(name = "\"option\"")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#EqualsAndHashCode(of = "id")
#Builder
public class OptionEntity {
#Id
#GeneratedValue(generator = "UUID")
#Column(name = "id")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
#JoinColumn(name = "poll_id")
#ManyToOne
private PollEntity poll;
#Column(name = "option")
#Size(max = 1000)
#NotNull
private String option;
}
And here's service method:
#Transactional(rollbackFor = Throwable.class)
public void createPoll(#Valid PollEntity pollEntity) throws ValidationException {
validationService.validateOrThrow(pollEntity);
if (pollRepository.findById(pollEntity.getId()).isPresent())
throw new ValidationException("Invalid id", Map.of("id", "Poll with id (" + pollEntity.getId() + ") already exists"));
pollEntity = validationService.validateAndSave(pollRepository, pollEntity);
And corresponding test:
#Test
public void createPollTest() throws ValidationException {
var uuid = UUID.randomUUID();
var pollOption1 = OptionEntity.builder()
.option("Test option 1")
.build();
var pollOption2 = OptionEntity.builder()
.option("Test option 2")
.build();
var pollOption3 = OptionEntity.builder()
.option("Test option 3")
.build();
var poll = PollEntity.builder()
.id(uuid)
.pollQuestion("Test question")
.author(UUID.randomUUID())
.build();
poll.addOption(pollOption1);
poll.addOption(pollOption2);
poll.addOption(pollOption3);
pollService.createPoll(poll);
}
Which gives following output in database
poll
2e565f50-7cd4-4fc9-98cd-49e0f0964487 feae5781-ff07-4a21-9292-c11c4f1a047d Test question
option
c786fe5d-632d-4e94-95ef-26ab2af633e7 fc712242-8e87-41d8-93f2-ff0931020a4a Test option 1
and rest options ended up unpersisted.
I've also used to create options in separate method
#Transactional(propagation= Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
public Set<OptionEntity> createOptions(#Valid Set<OptionEntity> pollOptionsEntities) throws ValidationException {
for (var pollOption : pollOptionsEntities) {
validationService.validateAndSave(pollOptionsRepository, pollOption);
}
return pollOptionsEntities;
}
and option entities were getting produced but had to switch to persisting from built-in entity methods due to errors with persisting poll entity.
Database schema looks like this:
CREATE TABLE "poll"
(
"id" UUID PRIMARY KEY,
"author" UUID NOT NULL,
"poll_question" VARCHAR(1000) NOT NULL
)
CREATE TABLE "option"
(
"id" UUID PRIMARY KEY,
"poll_id" UUID NOT NULL REFERENCES "poll" ("id") ON DELETE CASCADE
"option" VARCHAR(1000) NOT NULL
)
What are the possible approaches to try?
UPD 1
Having considered various looking-alike questions (1,2,3,4,5)
I've came up with this addition to entity which suppose make entities persistence regardless of actual value and still having only one option in output. What was done wrong?
#Override
public boolean equals(Object object) {
if ( object == this ) {
return false;
}
if ( object == null || object.getClass() != getClass() ) {
return false;
}
final OptionEntity other = OptionEntity.class.cast( object );
if ( getId() == null && other.getId() == null ) {
return false;
}
else {
return false;
}
}
#Override
public int hashCode() {
final HashCodeBuilder hcb = new HashCodeBuilder( 17, 37 );
if ( id == null ) {
while (getOptions().iterator().hasNext())
hcb.append( getOptions().iterator().next() );
}
else {
hcb.append( id );
}
hcb.append( options );
return hcb.toHashCode();
}
So the answer were quite trivial all the way long.
In order to overcome this the only thing that should be done is to change container: Set<OptionEntity> to List<OptionEntity>.
Hope this will not produce some hard-to-tackle bugs but if it can - please add comment.
Because in my case uniqueness was not strict requirement, ended up with this:
PollEntity:
#Table(name = "\"poll\"")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Builder
public class PollEntity {
#Transient
public OptionEntity addOption(OptionEntity pollOptionEntity) {
options.add(pollOptionEntity);
pollOptionEntity.setPoll(this);
return pollOptionEntity;
}
#Transient
public OptionEntity dropOption(OptionEntity pollOptionEntity) {
options.remove(pollOptionEntity);
pollOptionEntity.setPoll(null);
return pollOptionEntity;
}
#Id
#Column(name = "id")
private UUID id;
#Column(name = "status")
#Enumerated(EnumType.STRING)
#NotNull
private Status status;
#Column(name = "author")
#UUIDv4
private UUID author;
#Column(name = "poll_question")
#Size(max = 1000)
private String pollQuestion;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "poll", cascade = CascadeType.MERGE)
#Builder.Default
#Valid
private List<OptionEntity> options = new ArrayList<>();
}

Fetch result in case of many to many relationship

I have three tables
employee: storing employee information
role_description: storing types of role i have like admin, zone manger, country manager
employee_role_details: having many to many relationship from employee and role description with some extra fields.
Now I am facing an issue in fetching result that contains (employee_id, email) from employee table, (role_ownership, role_id) from employee_role_details and (role_name and role_description)from role_description corresponding to role_id in role_description.
Employee.java
#Entity
#Table(name = "employee", uniqueConstraints= #UniqueConstraint(columnNames={"employee_email"}))
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employee_id")
private long employeeId;
#Column(name = "employee_name")
private String employeeName;
#Column(name = "employee_email", unique = true, nullable = false)
private String employeeEmail;
#Column(name = "contact_no")
private String contactNo;
#Column(name = "password")
private String password;
#Column(name = "is_active")
private Boolean isActive = true;
#Transient
private long EmployeeRoleId;
#Transient
private String roleName;
#Transient
private int totalpage;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL)
//#JsonIgnore
#JsonManagedReference(value = "employeeRoleRecord-employee")
#LazyCollection(LazyCollectionOption.FALSE)
private List<EmployeeRoleRecord> roleRecords = new ArrayList<>();
#OneToMany(mappedBy = "assignedTo", cascade = CascadeType.ALL)
#JsonManagedReference(value = "complaint-treatment-employee")
#LazyCollection(LazyCollectionOption.FALSE)
private List<ComplaintsCategory> complaintCategoryAssignedTo = new ArrayList<>();
EmployeeDescription.java
#Entity
#Table(name = "role_description")
public class RoleDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private long roleId;
#Column(name = "role_name")
private String roleName;
#Column(name = "role_description")
private String roleDescription;
#Column(name = "status")
private boolean status;
#OneToMany(mappedBy = "description", cascade = CascadeType.ALL)
#JsonManagedReference(value = "employeeRoleRecord-roleDescription")
#LazyCollection(LazyCollectionOption.FALSE)
private List<EmployeeRoleRecord> roleRecords = new ArrayList<EmployeeRoleRecord>();
EmployeeRoleRecord
#Entity
#Table(name = "employee_role_record")
public class EmployeeRoleRecord {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employee_role_id")
private long employeeRoleId;
#Column(name = "role_ownership")
private String roleOwnership;
#Column(name = "status")
private boolean status = true;
#ManyToOne(cascade=CascadeType.ALL)
#JsonBackReference(value = "employeeRoleRecord-employee")
#JoinColumn(name = "employee_id")
private Employee employee;
#ManyToOne(cascade=CascadeType.ALL)
#JsonBackReference(value = "employeeRoleRecord-roleDescription")
#JoinColumn(name = "role_id")
private RoleDescription description;
#Transient
private long roleId;
Code inside a dao
public Employee getEmployeeByEmail(String email) {
return (Employee) sessionFactory.getCurrentSession()
.createCriteria(Employee.class)
.add(Restrictions.eq("employeeEmail", email)).uniqueResult();
}
services:
public Employee getSurveyData(String employeeEmail){
Employee loggedInEmployee =
loginDao.getEmployeeByEmail(employeeEmail);
Employee loggedInEmployeeRecord = new Employee();
loggedInEmployeeRecord.setEmployeeId(loggedInEmployee.getEmployeeId());
loggedInEmployeeRecord.setEmployeeName(loggedInEmployee.getEmployeeName());
List<EmployeeRoleRecord> employeeRoleRecords = loggedInEmployee.getRoleRecords();
List<EmployeeRoleRecord> employeeRoleRecord = new ArrayList<>();
for(EmployeeRoleRecord record : employeeRoleRecords){
EmployeeRoleRecord employeeRole = new EmployeeRoleRecord();
employeeRole.setRoleId(record.getDescription().getRoleId());
employeeRole.setRoleName(record.getDescription().getRoleName());
employeeRole.setRoleOwnership(record.getRoleOwnership());
employeeRole.setStatus(record.isStatus());
employeeRoleRecord.add(employeeRole);
}
loggedInEmployeeRecord.setRoleRecords(employeeRoleRecord);
return loggedInEmployee;
}
Output:
{
"employeeId": 1,
"employeeName": "Dhyanandra Singh",
"employeeEmail": "admin",
"contactNo": "9893651872",
"password": "123456",
"isActive": true,
"roleRecords": [
{
"employeeRoleId": 1,
"roleOwnership": "overall project",
"status": true,
"roleId": 0,
"roleName": null
}
],
}
problem:
json output i'm getting is not containing role id and role name in role records list.
The problem is:
Your roleId attribute on EmployeeRoleRecord classe is mapped as #Transient, which means that it is not persisted on database, so Hibernate is not able to fetch the result of this field.
Looking at your classes modeling I couldn't figure out exactly what you are trying to map with the roleId, I suggest two possible approaches:
Do not return directly your query on getEmployeeByEmail method. First get the results, then iterate it and set manually the desired roleId you want on each record;
Check your model scheme and make the correct mapping for roleId, it probably should reference another entity, so make sure to map the relationship correctly to get your results on a single query.
Good luck.
added transient roleId and roleName in EmployeeRoleRecord then having some modification in services. like iterating list setting roleId and roleName in it.
public Employee getSurveyData(String employeeEmail){
Employee loggedInEmployee =
loginDao.getEmployeeByEmail(employeeEmail);
Employee loggedInEmployeeRecord = new Employee();
loggedInEmployeeRecord.setEmployeeId(loggedInEmployee.getEmployeeId());
loggedInEmployeeRecord.setEmployeeName(loggedInEmployee.getEmployeeName());
loggedInEmployeeRecord.setEmployeeEmail(loggedInEmployee.getEmployeeEmail());
loggedInEmployeeRecord.setContactNo(loggedInEmployee.getContactNo());
List<EmployeeRoleRecord> employeeRoleRecords = loggedInEmployee.getRoleRecords();
List<EmployeeRoleRecord> employeeRoleRecord = new ArrayList<>();
for(EmployeeRoleRecord record : employeeRoleRecords){
EmployeeRoleRecord employeeRole = new EmployeeRoleRecord();
employeeRole.setEmployeeRoleId(employeeRole.getEmployeeRoleId());
employeeRole.setRoleId(record.getDescription().getRoleId());
employeeRole.setRoleName(record.getDescription().getRoleName());
employeeRole.setRoleOwnership(record.getRoleOwnership());
employeeRole.setStatus(record.isStatus());
employeeRoleRecord.add(employeeRole);
}
loggedInEmployeeRecord.setRoleRecords(employeeRoleRecord);
return loggedInEmployeeRecord;
}

Categories