Reuse a composite key for a child + a new field - postgresql

I use spring boot, with jpa (hibernate) and postgresql
I use composite key.
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
#GeneratedValue
private Integer id;
#Id
private int year;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
...
}
public class SamplingsPK implements Serializable {
private Integer id;
private int year;
public SamplingsPK(Integer id, int year) {
this.id = id;
this.year=year;
}
private SamplingsPK(){
}
#PrePersist
public void prePersist() {
year = LocalDate.now().getYear();
}
}
#Entity
public class Samples {
#Id
#SequenceGenerator(name = "samples_id_seq", sequenceName = "samples_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "samples_id_seq")
private Integer id;
private String sampleLetter;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")
})
private Samplings sampling;
}
That work fine
Instead of having an sequence in samples, I would like to have a composite key... SamplingsPK + sampleLetter.
Is it possible to do it, how to save a sample?

This is a "derived identity", so Samples could be mapped with an #IdClass like this:
#Entity
#IdClass(SamplesPK.class)
public class Samples {
#Id
#ManyToOne
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")
})
private Samplings sampling;
#Id
private String sampleLetter;
}
public class SamplesPK {
SamplingsPK sampling; // matches name of attribute and type of Samplings PK
String sampleLetter; // matches name and type of attribute
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Related

JPA Criteria: CriteriaBuilder over NestedObject field

Here my entity:
public class QdCF implements Diffable<QdCF> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "QDCF_ID")
private Integer id;
// #Column(name = "QDCF_AMBIT")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "QDCF_AMBIT")
private String ambit;
}
Now, I've changed ambit field to ManyToOne:
public class QdCF implements Diffable<QdCF> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "QDCF_ID")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "QDCF_AMBIT")
private Ambit ambit;
}
Above code works fine.
However, I've a custom JPA criteria like this:
public static Specification<QdCF> likeCodi(String codi) {
return (root, criteriaQuery, criteriaBuilder) -> {
return criteriaBuilder.like(root.get(QdCF_.ambit).as(String.class), "%" + codi + "%");
};
}
Above code doesn't works now since:
Caused by: org.hibernate.QueryException: Expression to CAST cannot be an entity : qdcf0_.QDCF_AMBIT
Any ideas about how to troubleshot it?

Unable to delete entity using Spring Data Jpa

I have a bi-directional mapping between Customer, Order and LineItems. When trying to delete using deleteById method of Spring Data JPA, the entities are not getting deleted and I do not see any exception.
#Entity
#Table(name = "customers")
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Data
public class Customer implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty(message = "customer name cannot be empty")
private String name;
#Column(unique = true)
#Email(message = "customer email address should be valid email address")
private String email;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private Set<Order> orders;
private String password;
#Data
#NoArgsConstructor
#Builder
#AllArgsConstructor
#EqualsAndHashCode(exclude = {"customer", "lineItems"})
#ToString(exclude = {"customer", "lineItems"})
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Min(value = 2000, message = "Min order value should be 2000 INR")
#Max(value = 10_000, message = "Max order value can be 10000 INR")
private double price;
#PastOrPresent(message = "Order date cannot be in future")
private LocalDate date;
#ManyToOne
#JoinColumn(name="customer_id", nullable = false)
#JsonBackReference
private Customer customer;
#OneToMany(mappedBy = "order",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true
)
private Set<LineItem> lineItems;
//scaffolding code
// set the bidirectional mapping
public void addLineItem(LineItem lineItem){
if(this.lineItems == null){
this.lineItems = new HashSet<>();
}
this.lineItems.add(lineItem);
lineItem.setOrder(this);
}
}
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "order")
#ToString(exclude = "order")
#Builder
#AllArgsConstructor
#Entity
#Table(name="line_items")
public class LineItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private int qty;
private double price;
private String name;
#JsonIgnore
#ManyToOne
#JoinColumn(name="order_id", nullable = false)
private Order order;
}
this.orderRepository.deleteById(orderId);
Error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.classpath.ordersapi.model.Customer.orders, could not initialize proxy - no Session\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:612)\r\n\tat org.hibernate.collection.internal

Project data from different tables to a model

I defined my model classes like below.
#Entity
#Table(name = "my_employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "emp_address_mapping", joinColumns = #JoinColumn(name = "emp_id"), inverseJoinColumns = #JoinColumn(name = "address_id"))
private List<Address> addresses = new ArrayList<Address>();
.......
.......
}
#Entity
#Table(name = "my_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String country;
.....
.....
}
public class EmployeeDetails {
private int empId;
private String name;
private String country;
......
......
}
How can I write a query using #Query annotation to populate all the EmployeeDetails.
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
#Query("SELECT new com.sample.app.model.EmployeeDetails......")
List<EmployeeDetails> getEmployeeDetails();
}
Create the constructor in EmployeeDetails
public EmployeeDetails(int id,String name,String country){
this.id=id;
this.name=name;
this.country=country;
}
Try this query
To get all employee details:
SELECT new com.sample.app.model.EmployeeDetails(e.id,e.name,a.country) from Employee e,Address a

JPA Entity mapping for join columns

I have three tables EmployeeDepartment, EmployeeGroup and EmpplyeeDetails Table. EmployeeDepartment table has primary key departmentId and a column groupId, EmployeeGroupTable has primary key groupid which should be generated from databse sequence GroupIdGenerator.
EmployeeDetails have two primary keys as groupid and employeeid. Groupid should be same as of the previous table
These values in all table should insert in one transaction.
Can you help me with correct JAP Entity mapping?
I already tried with different combination of Generated value and Sequence generator but not able to save the data into table.
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
}
#Coulmn(name="GROUP_ID")
private long groupId;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false ,
updatable=false)
private EmployeeDepartment employeeDepatment;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
#Column(name = "GROUP_ID")
#Nonnull
private Long groupId;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
}
public class EmployeeId{
private Long groupId;
private Long employeeId;
public EmployeeId(final Long groupId, final Long employeeId) {
this.groupId = groupId;
this.employeeId = employeeId;
}
public EmployeeId() {
}
}
Expected result in these 3 tables should have proper values like.
Table EmployeeDepartment
DepartmentID GroupId
1 1
Table EmployeeGroup
GroupID
1
Table EmployeeDetail
GroupId EmployeeId
1 1
1 2
1 3
Actual results are below
Table EmployeeDepartment
DepartmentID GroupId
1 0
Table EmployeeGroup
GroupID
1
Table EmployeeDetail
GroupId EmployeeId
2 1
3 2
4 3
The important annotation is #MapsId("groupId")
Your mapping should be something like this:
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
#Column(name="GROUP_ID")
private long groupId;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false, updatable=false)
private EmployeeDepartment employeeDepatment;
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#MapsId("groupId")
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private Long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
}
#Column(name="GROUP_ID")
private EmployeeGroup group;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private Long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false ,
updatable=false)
private EmployeeDepartment employeeDepatment;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#Id
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
You also need to change the EmployeeId accordingly:
public class EmployeeId{
private EmployeeGroup employeeGroup;
private Long employeeId;
public EmployeeId(final EmployeeGroup employeeGroup, final Long employeeId) {
this.employeeGroup= employeeGroup;
this.employeeId = employeeId;
}
However I haven't use composite keys this way before. If it doesn't work, then change EmployeeId to an embeddedId:
#Embeddable
public class EmployeeId implements Serializable{
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private EmployeeGroup employeeGroup;
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
public EmployeeId(final EmployeeGroup employeeGroup, final Long employeeId) {
this.employeeGroup= employeeGroup;
this.employeeId = employeeId;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
public class EmployeeDetail {
#EmbeddedId
private EmployeeId id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
}
If it still doesn't work, then please attach the code snippet where you create the entities.

java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST

I have two 2 classes in relation many to many.
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies;
I would like to create a new recipie this way:
List<Ingredient> ingredientsList = new ArrayList<>();
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
ingredientsList.add(ingredient);
.....
recipie.setIngredients(ingredientsList);
recipiesDao.addRecipie(recipie);
If ingredient doesn't exist in database, occur errors like this
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Is there any way to Ingredient objects created in the table automatically?
I try add CascadeType.PERSIST but It also doesn't work
#ManyToMany(mappedBy = "ingredients", cascade = CascadeType.PERSIST)
private List<Recipie> recipies;
First of all, for a bidirectional relationship, both sides need to be updated, so:
recipe.getIngredients().add(ingredient);
ingredient.getRecipes().add(recipe);
Then, you can set the cascade to PERSIST on the side of the relationship which you are passing to save(). So if you are saving the recipe, you should mark the Recipe.ingredients with
#ManyToMany(cascade = CascadeType.PERSIST)
(Side note, it's spelled "recipe", not "recipie")
As mentioned by #Gimby, you need to assign both sides of the relationship.
When dealing with #Many... sided relationships I always initialise the collection (which you've done on one side):
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
...
}
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies = new ArrayList<>();
...
}
And then a slight variation in your logic:
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
...
// Don't forget to assign both sides of the relationship
recipe.getIngredients().add(ingredient);
ingredient.getRecipies().add(recipe);
recipiesDao.addRecipe(recipe);
This should then cascade persist/update correctly.
The real fun will begin when you try to figure out how to associate a quantity with the ingredient...