I have a Customers table that has AddressEmbedded in it.
I also have a hardcoded table Countries where I already have a region for every country, country is the primary key.
I want to join AddressEmbedded and Countries so I used the ManyToOne and put CountryEntity in AddressEmbedded.
But I'm getting an error that mapstruct can't generate setCountry.
So the question is, how do I make AddressEmbedded.setCountry(string country)?
It's supposed to do a call to the db to get the corresponding region for that country, but it seems wrong to add a db call in a setter.
Here are the entity definitions:
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Data
#Embeddable
public class AddressEmbedded {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "country")
private CountryEntity country;
}
#Data
#Entity
#Table(name = "Countries")
public class CountryEntity {
#Id
#Column(name = "country")
private String country;
#Column(name = "region")
private String region;
}
#Data
#Entity
#Table(name = "Customers")
public class CustomerEntity {
#Embedded
private AddressEmbedded address;
}
This was solved with mapstruct mappings
#Mappings(
#Mapping(target = "address.country", source = "countryEntity")
)
CustomerEntity fromSubmitCustomerDetailsRequestToCustomerEntity(SubmitCustomerDetailsRequest request, CountryEntity countryEntity);
#Mappings(
#Mapping(target = "address.country", source = "customerEntity.address.country.country")
)
GetCustomerDetailsResponse fromCustomerEntityToGetCustomerDetailsResponse(CustomerEntity customerEntity);
I have the CountryEntity in fromSubmitCustomerDetailsRequestToCustomerEntity because before I call it I validate that I have a country that exists.
Related
I have done the jpa one to many mapping between Book & corresponding Pages as follows.
#Entity
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Book {
#Id
#EqualsAndHashCode.Include
#Column(name = "book_id")
private String bookId;
private String name;
#OneToMany(targetEntity = Page.class, cascade= CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "book_id_fk", referencedColumnName = "book_id")
private List<Page> pages;
}
and
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Page {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer sno;
private Integer number;
}
#Repository
public interface PageRepository extends JpaRepository<Page, Integer> {
List<Page> findAllByBook_Id(String bookId);
}
Now I would like to get all pages using book id from Page table. For that I'm using findAllBook_id method but I'm getting the No property book found for type Page! exception. I tried XXXBook_fkId/XXXBookFkId/XXXBook_fk_id etc. method names but didn't worked out. Kindly help
The problem that you face is that your entity Page does not have a field that relates with Book. So you have unidirectional mapping in JPA layer.
One solution would be to include the following custom query which will return you what you expect to be returned.
#Query( "SELECT pg FROM Book bk join bk.pages pg WHERE bk.bookId = :bookId")
List<Page> findPagesByBookId(#Param("bookId") String bookId);
Hello I saw some similar questions from 2018 but without much information
I have the following classes
Employee
#Entity
#Getter
#Setter
#NoArgsConstructor
#NamedEntityGraph(name = "employee.complete", attributeNodes = {
#NamedAttributeNode(
value = "addresses",
subgraph = "address_city"
)},
subgraphs = {
#NamedSubgraph(
name = "address_city",
attributeNodes = {
#NamedAttributeNode("city")
})
},
subclassSubgraphs = {
#NamedSubgraph(
name = "noUse",
type = Engineer.class,
attributeNodes = {
#NamedAttributeNode("laptop")
})
}
)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "profession")
public class Employee {
#Id
String id;
String name;
String email;
#OneToMany
#JoinColumn(name = "employee_id", updatable = false)
List<Address> addresses;
}
Address
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
public class Address {
#Id
private String addressId;
private String streetName;
private String streetNumber;
#OneToOne
#JoinColumn(name = "city_id")
private City city;
}
City
#Entity
#Setter
#Getter
public class City {
#Id
String id;
String name;
}
Engineer
#Entity
#Getter
#Setter
#DiscriminatorValue("engineer")
public class Engineer extends Employee {
private String seniority;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
Laptop laptop;
}
Laptop
#Entity
#Getter
#Setter
public class Laptop {
#Id
#Column(name = "employee_id")
String employeeId;
String brand;
}
With the NamedEntityGraph I want to achieve in a single query to retrieve all the information of the employee.
The attributeNodes and the subgraph for city are working correctly but the subclassSubgraphs is not working for this example..
my expectation was that I will retrieve the laptop information in one query with a join clause
but actually there are two select statements
select employee0_.id as id2_3_0_, addresses1_.address_id as address_2_0_1_, city2_.id as id1_1_2_, employee0_.email as email3_3_0_, employee0_.name as name4_3_0_, employee0_.seniority as seniorit5_3_0_, employee0_.profession as professi1_3_0_, addresses1_.city_id as city_id5_0_1_, addresses1_.street_name as street_n3_0_1_, addresses1_.street_number as street_n4_0_1_, addresses1_.address_type as address_1_0_1_, addresses1_.employee_id as employee6_0_0__, addresses1_.address_id as address_2_0_0__, city2_.name as name2_1_2_ from employee employee0_ left outer join address addresses1_ on employee0_.id=addresses1_.employee_id left outer join city city2_ on addresses1_.city_id=city2_.id where employee0_.id=? and (employee0_.id is not null)
select laptop0_.employee_id as employee1_4_0_, laptop0_.brand as brand2_4_0_ from laptop laptop0_ where laptop0_.employee_id=?
I cannot find any page that reports a bug about this, am I doing something wrong?
code repo -> https://github.com/dkasiaras/pocs/tree/main/jpa-graph-entity
I have two tables, the first one is TB_RECIPE_DATA, where the PK is the ID_RECIPE field. The second table is TB_RECIPE_ITEM, where the PK is composed of three fields: ID_RECIPE, CD_LOT and CD_PRODUCT. These two tables are related so that a recipe can have multiple items. The problem I'm facing is that when I try to register a recipe with more than one item, I get an error message "InvalidDataAccessApiUsageException: Multiple representations of the same entity". When I register a recipe with just one item, it works.
In the research I've done, many indicate that it's because of Cascade, I've already tried switching to cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} and it didn't work. The mapping of the tables was done this way:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_DATA", schema = "A_SAMPLE")
public class Recipe {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<RecipeItem> items;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_ITEM", schema = "A_SAMPLE")
public class RecipeItem {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#Column(name = "CD_LOT")
private String lot;
#Column(name = "CD_PRODUCT")
private Long code;
#Column(name = "QT_PURCHASE")
private Long purchaseQuantity;
#Column(name = "FL_AVAILABLE")
private Boolean available;
}
The error was happening because when changing, for example, recipe A with its respective items, each item has recipe A as part of the Primary Key, so I would be changing the recipe twice. The solution would be to work the bi-direction for this case. I will share my solution in case anyone experiences a similar problem.
Main class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_DATA", schema = "A_SAMPLE")
public class Recipe {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<RecipeItem> items;
}
Child class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_ITEM", schema = "A_SAMPLE")
public class RecipeItem {
#EmbeddedId
private RecipeItemPk id;
#Column(name = "QT_PURCHASE")
private Long purchaseQuantity;
#Column(name = "FL_AVAILABLE")
private Boolean available;
#ManyToOne
#JoinColumn(name = "ID_RECIPE", insertable = false, updatable = false)
private Recipe recipe;
}
Primary Key class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(of = { "id", "lot", "code" })
#Embeddable
public class RecipeItemPk {
#Column(name = "ID_RECIPE")
private Long id;
#Column(name = "CD_LOT")
private String lot;
#Column(name = "CD_PRODUCT")
private Long code;
}
I work with Spring Boot and have 2 PostgreSQL tables: USERS and CITIES. FOREIGN KEY (USERS.city_id) REFERENCES CITIES (id). CITIES has an unique constraint for city name field. I receive an object in the #PostMapping method of the controller and try to save it via service layer. All is fine while I don't send an object with same city name field, and I don't know how to solve it. Postman JSON example:
*1st attempt*
{
"name": "JHON",
"city": {
"name": **"MOSCOW"**
}
} ---> ALL OK
*2nd attempt*
{
"name": "TOM",
"city": {
"name": **"MOSCOW"**
}
}--->**org.postgresql.util.PSQLException: ERROR: Duplicate key value violates unique constraint "cities_name_key"
Details: The key "(name) = (MOSCOW)" already exists.**
Entity:
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
}
#Entity
#Data
#Accessors(chain = true)
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "city_id")
private City city;
}
Tables:
cities
id SERIAL PRIMARY KEY NOT NULL,
name TEXT UNIQUE
users
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(10),
city_id INT,
CONSTRAINT users_cities_id_fk FOREIGN KEY (city_id) REFERENCES cities (id)
and Service
#Service
#RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User getUser(Long id){
return userRepository.findById(id).get();
}
public User saveUser(User user){
return userRepository.save(user);
}
}
Ok, I found an answer for question.
So if you have fields with unique constraints in the your tables, you have to define a constructor inside the entity for the primary key field. And don't hope that Lombok will do it. It means that annotation like #AllArgsConstructor does not help for this case.
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
//add this *********************
public City(Long id) {
this.id = id;
}
}
I have an entity:
#Entity
#Table(name = "person")
public class Person {
#EmbeddedId
private PersonId id;
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("a_phoneNumberId")
#ManyToOne
private PhoneNumber phoneNumber;
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("b_addressId")
#ManyToOne
private Address address;
...
with embedded id:
#Embeddable
public class PersonId implements Serializable {
private int a_phoneNumberId;
private int b_addressId;
...
Note: a_ and b_ prefixes are used to order columns in primary key.
Everything works as expected and hibernate generates a table with columns: phoneNumber_id and address_id.
Is it possible to rename those columns, as I want to have a snake_case name - phone_number_id?
So far I tried
#AttributeOverride annotation:
#Entity
#Table(name = "person")
public class Person {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "a_phoneNumberId", column = #Column(name = "phone_number_id"))
})
private PersonId id;
#Column annotation for the id:
#Embeddable
public class PersonId implements Serializable {
#Column(name = "phone_number_id")
private int a_phoneNumberId;
but it changed nothing.