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
Related
I have a database with some entities, ( in parent child relationship )I can say and when When I try to make a query to child table to get all the rows, I only get the fields which are not foreign keys. How can I include the foreign key in the response json. Can anyone help me figure this out ?
Parent Class which is working fine when I try to repository.findAll() and it works as expected.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}
Child class:
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "emp_id", nullable = false)
#JsonBackReference
private Employee employee;
}
Here is the repository class for Address Entity
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
}
When I try AddressRepository.findAll()
What I get:
[{
"id": 1,
"city": "new york",
"state": "new york"
}]
what I want to get:
"id": 1,
"city": "new york",
"state": "new york",
"emp_id": 1 //which is the foreign key referring to Employee table
}]
What I tried is I updated my Employee column in Address Entity as follow but no luck
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.EAGER)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "client_id", scope = Client.class)
#JsonIdentityReference(alwaysAsId = true)
#JoinColumn(name = "client_id", nullable = false)
#JsonBackReference
#JsonProperty("clientId")
private Employee employee;
}
You could use a JPA projection:
public class AddressDto {
private long id;
private String city;
private String state;
private long employeeId;
public AddressDto(long id, String city, String state, Employee employee) {
this.id = id;
this.city = city;
this.state = state;
this.employeeId = employee.getId();
}
// getters etc..
}
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
List<AddressDto> findAllProjectedBy();
}
Use #JsonProperty on each Foreign Key.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#JsonProperty
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#JsonProperty
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}
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 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.
I want to use projections to fetch only specific fields from my database with Spring Data JPA.
This is my (shortened) data model:
#Entity
#Table(name = "data")
#Data
public class DataEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#Fetch(value = FetchMode.SUBSELECT)
#OneToMany(mappedBy = "data", fetch = FetchType.LAZY)
#Builder.Default
private List<OwnerEntity> owners = new ArrayList<>();
}
#Entity
#Table(name = "owner")
#Data
public class OwnerEntity {
#EmbeddedId
public OwnerId id = new OwnerId();
#Fetch(value = FetchMode.JOIN)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="userId", insertable = false, updatable = false)
private UserEntity user;
#ManyToOne
#JoinColumn(name="dataId", insertable = false, updatable = false)
private InterfaceEntity iface;
}
#Embeddable
#Data
public class OwnerId implements Serializable {
private Integer dataId;
private String userId;
}
#Entity
#Table(name = "users")
#Data
public class UserEntity {
#Id
private String id;
private String name;
private String mail;
}
This is my projection:
public interface DataProjection {
String getName();
String getDescription();
List<UserEntity> getOwners();
}
Finally, this is my DAO:
public interface DataDao extends CrudRepository<DataEntity, Integer> {
#Query("select d.name as name, " +
" d.description as description, " +
" o.user as owners " +
"from DataEntity d " +
"left join d.owners o " +
"order by d.name")
List<DataProjection> getData();
}
It generally works but it returns one row for each owner resulting in multiple same DataProjections with a list containing only one of the owners.
A similar problem was mentioned in this question but as mentioned in the solutions comments this would make it an open projection loading all columns.
Is there a solution other than mapping the resulting rows programmatically?
I have the entity following entities:
Employee extends Person
Company has List<Employee> (lazy load).
When I try to initialize the employee list with Hibernate.initialize(company.getEmployees()); I receive an error since hibernate doesn't understand that Employee is a Person.
Person.java
#Entity
#Table(name = "person")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Person implements java.io.Serializable {
#Column(name = "person_id")
protected Long personId;
#Column(name = "name")
protected String name;
#Column(name = "age")
protected String age;
}
Employee.java
#Entity
#Table(name = "employee")
#PrimaryKeyJoinColumn(name = "person_id")
public class Employee extends Person {
#Column(name = "employee_number")
private String number;
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
}
Company.java
#Entity
#Table(name = "company")
public class Company implements java.io.Serializable {
#Column(name = "company_id")
protected String id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company", cascade = CascadeType.ALL)
#OrderBy(clause = "name desc")
protected List<Employee> employees = new ArrayList<Employee>();
}
Hibernate.initialize(company.getEmployees());
Exception:
org.postgresql.util.PSQLException: ERROR: column employee0_.name does not exist
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
Is there any solution with lazy load for this case?
There is certainly one to one relationship between Employee and Person. It makes sense by splitting the objects in Java but not in terms of table. I think Employee table should have a name and other columns defined in person table to simplify the design.